1use std::sync::Arc;
27use std::time::Duration;
28
29use bytes::Bytes;
30#[cfg(all(test, not(target_arch = "wasm32")))]
31use futures_channel::mpsc::UnboundedReceiver;
32use gen_lsp_types::{
33 DidChangeConfigurationParams, DidChangeNotebookDocumentParams, DidChangeTextDocumentParams,
34 DidChangeWorkspaceFoldersParams, DidCloseNotebookDocumentParams, DidCloseTextDocumentParams,
35 DidOpenNotebookDocumentParams, DidOpenTextDocumentParams, DidSaveNotebookDocumentParams,
36 DidSaveTextDocumentParams, InitializeParams, InitializedParams, NotebookDocumentCellChanges,
37 ProgressToken, Save, ServerInfo, SetTraceParams, TextDocumentItem, TextDocumentSyncKind,
38 TextDocumentSyncOptions, TraceValue, Uri, WillSaveTextDocumentParams,
39 WorkDoneProgressCancelParams, WorkspaceFoldersServerCapabilities,
40};
41use serde::Serialize;
42use tokio_util::sync::CancellationToken;
43use tracing::{Instrument, Span, debug, warn};
44
45use crate::builder::{
46 ConfigureInitialize, InitializeRegistrar, OnExit, OnInitialize, OnInitialized, OnShutdown,
47 ProtocolNotification, Registrations, Server,
48};
49use crate::capability::GeneratedCapabilities;
50use crate::client::ClientHandle;
51use crate::codec::{decode_params, decode_value, encode_body, request_token};
52use crate::context::ServerContext;
53use crate::documents::{Document, DocumentMutationError, Documents};
54use crate::error::Error;
55use crate::failure::{ConnectionDirection, ConnectionFailureCategory, FailureReporter};
56use crate::file_provider::SharedFileProvider;
57use crate::notebooks::{Notebook, Notebooks};
58use crate::progress::{ProgressCancel, ProgressRegistry};
59use crate::raw::{RawMessage, RequestId};
60use crate::runtime::{Runtime, default_runtime, ensure_runtime_available};
61use crate::service::{
62 HandlerTimeout, IncomingCall, ServiceResult, UserLayer, UserService, build_service_stack,
63};
64#[cfg(all(test, not(target_arch = "wasm32")))]
65use crate::session::send_loop as drive_send_loop;
66#[cfg(all(test, not(target_arch = "wasm32")))]
67use crate::session::{CloseSignal, OutboundQueue, OutboundRegistry, enqueue_encoded};
68use crate::session::{
69 INBOUND_CAPACITY_EXHAUSTED, InboundReserveError, ProtocolSession, Reservation, SessionInput,
70 run_handler_with_deadline,
71};
72use crate::telemetry::{Completion, ConnectionTrace, Direction, Instant};
73#[cfg(all(test, not(target_arch = "wasm32")))]
74use crate::transport::TransportWriter;
75use crate::transport::{Transport, TransportError, TransportReader};
76use crate::workspace::Workspace;
77#[cfg(all(test, not(target_arch = "wasm32")))]
78use std::sync::Mutex;
79
80#[derive(Serialize)]
81#[serde(rename_all = "camelCase")]
82struct WireInitializeResult {
83 capabilities: GeneratedCapabilities,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 server_info: Option<ServerInfo>,
86}
87
88use crate::{LspError, Result};
89
90fn validate_sync_changes(
91 kind: TextDocumentSyncKind,
92 changes: &[gen_lsp_types::TextDocumentContentChangeEvent],
93) -> std::result::Result<(), LspError> {
94 if kind == TextDocumentSyncKind::Incremental {
95 return Ok(());
96 }
97 if kind == TextDocumentSyncKind::Full {
98 return if changes.iter().all(|change| {
99 matches!(
100 change,
101 gen_lsp_types::TextDocumentContentChangeEvent::TextDocumentContentChangeWholeDocument(_)
102 )
103 }) {
104 Ok(())
105 } else {
106 Err(LspError::invalid_request(
107 "range changes require incremental document synchronization",
108 ))
109 };
110 }
111 Err(LspError::invalid_request(
112 "document changes are disabled by the configured synchronization kind",
113 ))
114}
115
116fn decode_initialized_params(raw: &Bytes) -> std::result::Result<InitializedParams, LspError> {
124 match serde_json::from_slice::<serde_json::Value>(raw) {
125 Ok(serde_json::Value::Null) => Ok(InitializedParams {}),
126 Ok(serde_json::Value::Object(map)) if map.is_empty() => Ok(InitializedParams {}),
127 _ => Err(LspError::invalid_params(
128 "initialized params must be an empty object",
129 )),
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum Outcome {
141 Exit {
145 code: i32,
147 },
148 TransportClosed,
150 WriterFailed,
153 InitializeFailed,
156}
157
158impl Outcome {
159 pub fn code(&self) -> i32 {
162 match self {
163 Self::Exit { code } => *code,
164 Self::TransportClosed | Self::WriterFailed | Self::InitializeFailed => 1,
165 }
166 }
167}
168
169#[derive(Debug)]
174enum CloseCause {
175 Exit { code: i32 },
177 ReaderEof,
179 ReaderFailed(TransportError),
181 WriterFailed,
184 InitializeFailed,
186}
187
188impl CloseCause {
189 fn writer_failed() -> Self {
190 Self::WriterFailed
191 }
192
193 fn as_str(&self) -> &'static str {
194 match self {
195 Self::Exit { .. } => "exit",
196 Self::ReaderEof => "reader_eof",
197 Self::ReaderFailed(_) => "reader_failed",
198 Self::WriterFailed => "writer_failed",
199 Self::InitializeFailed => "initialize_failed",
200 }
201 }
202
203 fn into_result(self) -> Result<Outcome> {
205 match self {
206 Self::Exit { code } => Ok(Outcome::Exit { code }),
207 Self::ReaderEof => Ok(Outcome::TransportClosed),
208 Self::ReaderFailed(error) => Err(Error::Transport(error)),
209 Self::WriterFailed => Ok(Outcome::WriterFailed),
210 Self::InitializeFailed => Ok(Outcome::InitializeFailed),
211 }
212 }
213}
214
215pub(crate) async fn run<S, T>(server: Server<S>, transport: T) -> Result<Outcome>
219where
220 S: Send + Sync + 'static,
221 T: Transport,
222{
223 ensure_runtime_available()?;
224 let connection_trace = ConnectionTrace::new();
225 let failure_reporter = FailureReporter::new(server.error_hook.clone(), connection_trace.id());
226 let connection_span = connection_trace.span();
227 let (reader, writer) = transport.split();
228 let runtime = default_runtime();
229 let (protocol, client) = ProtocolSession::start(
230 runtime,
231 server.resource_policy,
232 writer,
233 connection_trace,
234 connection_span.clone(),
235 failure_reporter.clone(),
236 CloseCause::writer_failed,
237 ClientHandle::new,
238 );
239 ProtocolEngine::new(server, protocol, client, connection_trace, failure_reporter)
240 .serve(reader)
241 .instrument(connection_span)
242 .await
243}
244
245#[derive(serde::Deserialize)]
246struct CancelParams {
247 id: RequestId,
248}
249
250enum BuiltInGate {
257 RunHook,
259 ProtocolFailure,
261}
262
263enum BuiltInError {
264 Protocol(LspError),
265 Overload(LspError),
266}
267
268struct CellOpenRollback {
269 uri: Uri,
270 previous: Option<Document>,
271}
272
273impl From<LspError> for BuiltInError {
274 fn from(error: LspError) -> Self {
275 Self::Protocol(error)
276 }
277}
278
279impl From<DocumentMutationError> for BuiltInError {
280 fn from(error: DocumentMutationError) -> Self {
281 match error {
282 DocumentMutationError::Capacity(error) => Self::Overload(error),
283 DocumentMutationError::Protocol(error) => Self::Protocol(error),
284 }
285 }
286}
287
288fn gate_progress_cancel(registry: &ProgressRegistry, raw_params: &Bytes) -> BuiltInGate {
299 let params = match decode_params::<WorkDoneProgressCancelParams>(raw_params) {
300 Ok(params) => params,
301 Err(error) => {
302 debug!(%error, "ignoring malformed window/workDoneProgress/cancel");
303 return BuiltInGate::ProtocolFailure;
304 }
305 };
306 match registry.cancel(¶ms.token) {
307 ProgressCancel::Cancelled => {}
308 ProgressCancel::NotCancellable => debug!(
309 token = ?params.token,
310 "ignoring work-done progress cancel for a non-cancellable token"
311 ),
312 ProgressCancel::NotActive => debug!(
313 token = ?params.token,
314 "ignoring work-done progress cancel for an unknown or ended token"
315 ),
316 }
317 BuiltInGate::RunHook
318}
319
320#[cfg(all(test, not(target_arch = "wasm32")))]
321async fn send_loop<W: TransportWriter>(
322 writer: W,
323 out_rx: UnboundedReceiver<RawMessage>,
324 client: ClientHandle,
325 close: CloseSignal<CloseCause>,
326) {
327 drive_send_loop(writer, out_rx, client, close, CloseCause::writer_failed).await;
328}
329
330struct Pending<S> {
334 registrations: Registrations<S>,
335 file_provider: SharedFileProvider,
336 configure_initialize: Option<ConfigureInitialize<S>>,
337 on_initialize: Option<OnInitialize<S>>,
338 on_initialized: Option<OnInitialized<S>>,
339 on_shutdown: Option<OnShutdown<S>>,
340 on_exit: Option<OnExit<S>>,
341 layers: Vec<UserLayer<S>>,
342 concurrency_limit: usize,
343}
344
345enum Lifecycle<S> {
349 Uninitialized(Box<Pending<S>>),
350 Initializing,
351 Running(UserService<S>),
352 ShuttingDown,
353 Exited,
354}
355
356struct ProtocolEngine<S, R> {
362 state: Arc<S>,
363 documents: Documents,
364 notebooks: Notebooks,
365 workspace: Option<Workspace>,
366 document_sync: TextDocumentSyncOptions,
367 notebook_sync: bool,
370 lifecycle: Lifecycle<S>,
371 on_initialized: Option<OnInitialized<S>>,
375 on_shutdown: Option<OnShutdown<S>>,
379 on_exit: Option<OnExit<S>>,
383 protocol: ProtocolSession<R, ClientHandle, CloseCause>,
384 client: ClientHandle,
385 trace: ConnectionTrace,
386 failure_reporter: FailureReporter,
387}
388
389impl<S, R> ProtocolEngine<S, R>
390where
391 S: Send + Sync + 'static,
392 R: Runtime,
393{
394 fn new(
395 server: Server<S>,
396 protocol: ProtocolSession<R, ClientHandle, CloseCause>,
397 client: ClientHandle,
398 trace: ConnectionTrace,
399 failure_reporter: FailureReporter,
400 ) -> Self {
401 let max_inbound_requests = server.resource_policy.max_inbound_requests;
402 Self {
403 state: server.state,
404 documents: Documents::with_resource_policy(server.resource_policy, trace),
405 notebooks: Notebooks::with_resource_policy(server.resource_policy, trace),
406 workspace: None,
407 document_sync: TextDocumentSyncOptions::default(),
410 notebook_sync: false,
413 lifecycle: Lifecycle::Uninitialized(Box::new(Pending {
414 registrations: server.registrations,
415 file_provider: server.file_provider,
416 configure_initialize: server.configure_initialize,
417 on_initialize: server.on_initialize,
418 on_initialized: server.on_initialized,
419 on_shutdown: server.on_shutdown,
420 on_exit: server.on_exit,
421 layers: server.layers,
422 concurrency_limit: max_inbound_requests,
423 })),
424 on_initialized: None,
425 on_shutdown: None,
426 on_exit: None,
427 protocol,
428 client,
429 trace,
430 failure_reporter,
431 }
432 }
433
434 async fn serve<Rd>(mut self, mut reader: Rd) -> Result<Outcome>
441 where
442 Rd: TransportReader,
443 {
444 loop {
445 let msg = match self.protocol.next_input(&mut reader).await {
446 SessionInput::CloseRequested => break,
447 SessionInput::OutboundFailed => {
448 self.protocol.request_close(CloseCause::WriterFailed);
449 break;
450 }
451 SessionInput::Message(message) => message,
452 };
453
454 match msg {
455 Ok(msg) => {
456 self.trace.message(Direction::Inbound, &msg);
457 match self.dispatch(msg).await {
458 Flow::Continue => {}
459 Flow::Close(cause) => {
460 self.protocol.request_close(cause);
461 break;
462 }
463 }
464 }
465 Err(TransportError::Closed) => {
466 warn!("transport closed by peer before exit notification");
467 self.protocol.request_close(CloseCause::ReaderEof);
468 break;
469 }
470 Err(error) => {
471 let category = match &error {
472 TransportError::Malformed(_) | TransportError::OversizedMessage { .. } => {
473 ConnectionFailureCategory::Framing
474 }
475 TransportError::Io(_) | TransportError::Serde(_) => {
476 ConnectionFailureCategory::Transport
477 }
478 TransportError::Closed => unreachable!("closed is handled above"),
479 };
480 self.failure_reporter.report(
481 category,
482 Some(ConnectionDirection::Inbound),
483 None,
484 None,
485 );
486 self.protocol.request_close(CloseCause::ReaderFailed(error));
487 break;
488 }
489 }
490 }
491
492 self.close().await;
493 let cause = self.protocol.final_close_cause();
494 self.trace.connection_closed(cause.as_str());
495 cause.into_result()
496 }
497
498 async fn dispatch(&mut self, msg: RawMessage) -> Flow {
499 match msg {
500 RawMessage::Request { id, method, params } => {
501 let span = self.trace.request_span(method.as_ref(), &id);
502 let reserved = match self.protocol.reserve_inbound(
508 id.clone(),
509 method.as_ref(),
510 method != "initialize",
511 ) {
512 Ok(reserved) => reserved,
513 Err(InboundReserveError::DuplicateId) => {
514 self.failure_reporter.report_unvalidated_inbound_method(
515 ConnectionFailureCategory::Protocol,
516 Some(&id),
517 );
518 self.trace.request_completed(
519 method.as_ref(),
520 &id,
521 Instant::now(),
522 Direction::Inbound,
523 Completion::Rejected,
524 );
525 self.protocol
526 .reject_inbound(id, LspError::invalid_request("duplicate request id"));
527 return Flow::Continue;
528 }
529 Err(InboundReserveError::CapacityExhausted) => {
530 self.trace.request_completed(
531 method.as_ref(),
532 &id,
533 Instant::now(),
534 Direction::Inbound,
535 Completion::Rejected,
536 );
537 self.protocol.reject_inbound(
538 id,
539 LspError::ServerCancelled(INBOUND_CAPACITY_EXHAUSTED.to_string()),
540 );
541 return Flow::Continue;
542 }
543 };
544 let reservation = reserved.reservation;
545 let cancellation = reserved.cancellation;
546
547 if method != "initialize"
550 && matches!(
551 self.lifecycle,
552 Lifecycle::Uninitialized(_) | Lifecycle::Initializing
553 )
554 {
555 self.failure_reporter.report_unvalidated_inbound_method(
556 ConnectionFailureCategory::Protocol,
557 Some(&reservation.id),
558 );
559 self.protocol
560 .complete_inbound(reservation, Err(LspError::ServerNotInitialized));
561 return Flow::Continue;
562 }
563 if matches!(self.lifecycle, Lifecycle::ShuttingDown | Lifecycle::Exited) {
565 self.failure_reporter.report_unvalidated_inbound_method(
566 ConnectionFailureCategory::Protocol,
567 Some(&reservation.id),
568 );
569 self.protocol.complete_inbound(
570 reservation,
571 Err(LspError::invalid_request("invalid request")),
572 );
573 return Flow::Continue;
574 }
575
576 match method.as_ref() {
577 "initialize" => return self.initialize(&span, reservation, params).await,
578 "shutdown" => {
579 let params_result = if params.is_empty() {
580 Ok(())
581 } else {
582 decode_params::<()>(¶ms)
583 };
584 if let Err(err) = params_result {
585 self.failure_reporter.report(
586 ConnectionFailureCategory::Protocol,
587 Some(ConnectionDirection::Inbound),
588 Some("shutdown"),
589 Some(&reservation.id),
590 );
591 self.protocol.complete_inbound(reservation, Err(err));
592 return Flow::Continue;
593 }
594 if let Some(hook) = &self.on_shutdown {
595 let cancellation = cancellation
596 .expect("shutdown is a cancellable non-initialize request");
597 let ctx = ServerContext::for_request(
598 reservation.id.clone(),
599 span.clone(),
600 self.client.clone(),
601 self.established_workspace(),
602 )
603 .with_cancellation(cancellation.clone());
604 if let Err(err) = hook
605 .invoke((Arc::clone(&self.state), ctx, (), cancellation))
606 .instrument(span.clone())
607 .await
608 {
609 self.protocol.complete_inbound(reservation, Err(err));
610 return Flow::Continue;
611 }
612 }
613 self.protocol
618 .complete_inbound(reservation, encode_body(&serde_json::Value::Null));
619 self.protocol.cancel_all_inbound_with_response();
620 self.lifecycle = Lifecycle::ShuttingDown;
621 }
622 _other => {
623 let service = match &self.lifecycle {
625 Lifecycle::Running(service) => Arc::clone(service),
626 _ => {
627 self.protocol.complete_inbound(
628 reservation,
629 Err(LspError::ServerNotInitialized),
630 );
631 return Flow::Continue;
632 }
633 };
634 let params = match decode_value(¶ms) {
635 Ok(params) => params,
636 Err(error) => {
637 self.failure_reporter.report_unvalidated_inbound_method(
638 ConnectionFailureCategory::Protocol,
639 Some(&reservation.id),
640 );
641 self.protocol.complete_inbound(reservation, Err(error));
642 return Flow::Continue;
643 }
644 };
645 let work_done_token =
646 match request_token::<ProgressToken>(¶ms, "workDoneToken") {
647 Ok(token) => token,
648 Err(error) => {
649 self.protocol.complete_inbound(
650 reservation,
651 Err(LspError::invalid_params(error)),
652 );
653 return Flow::Continue;
654 }
655 };
656 let partial_result_token =
657 if crate::partial_result::supports_method(method.as_ref()) {
658 match request_token::<ProgressToken>(¶ms, "partialResultToken")
659 {
660 Ok(token) => token,
661 Err(error) => {
662 self.protocol.complete_inbound(
663 reservation,
664 Err(LspError::invalid_params(error)),
665 );
666 return Flow::Continue;
667 }
668 }
669 } else {
670 None
671 };
672 let method = method.into_owned();
673 let ctx = ServerContext::for_request(
674 reservation.id.clone(),
675 span.clone(),
676 self.client.clone(),
677 self.established_workspace(),
678 )
679 .with_cancellation(
680 cancellation
681 .as_ref()
682 .expect("non-initialize requests are cancellable")
683 .clone(),
684 )
685 .with_work_done_token(work_done_token)
686 .with_partial_result(method.clone(), partial_result_token);
687 self.spawn_service_request(
688 service,
689 reservation,
690 method,
691 params,
692 ctx,
693 cancellation.expect("non-initialize requests are cancellable"),
694 self.protocol.handler_timeout(),
695 );
696 }
697 }
698 }
699 RawMessage::Notification { method, params } => match method.as_ref() {
700 "exit" => {
701 let established = matches!(
709 self.lifecycle,
710 Lifecycle::Running(_) | Lifecycle::ShuttingDown
711 );
712 if !established {
713 self.failure_reporter.report(
714 ConnectionFailureCategory::Protocol,
715 Some(ConnectionDirection::Inbound),
716 Some("exit"),
717 None,
718 );
719 }
720 if established && let Some(hook) = self.on_exit.take() {
721 let span = self.trace.notification_span("exit");
722 let ctx = ServerContext::for_notification(
723 span,
724 self.client.clone(),
725 self.established_workspace(),
726 );
727 hook.invoke((Arc::clone(&self.state), ctx)).await;
728 }
729 let code = match self.lifecycle {
732 Lifecycle::ShuttingDown => 0,
733 _ => 1,
734 };
735 return Flow::Close(CloseCause::Exit { code });
736 }
737 "$/cancelRequest" => {
738 let bytes: &[u8] = if params.is_empty() { b"{}" } else { ¶ms };
739 match serde_json::from_slice::<CancelParams>(bytes) {
740 Ok(cancel) => {
741 if let Some(reservation) = self.protocol.cancel_inbound(&cancel.id) {
742 self.protocol.complete_cancelled(reservation);
743 }
744 }
745 Err(error) => {
746 self.failure_reporter.report(
747 ConnectionFailureCategory::Protocol,
748 Some(ConnectionDirection::Inbound),
749 Some("$/cancelRequest"),
750 None,
751 );
752 debug!(%error, "ignoring malformed $/cancelRequest");
753 }
754 }
755 }
756 "initialized" => {
757 let Lifecycle::Running(_) = &self.lifecycle else {
765 self.failure_reporter.report(
766 ConnectionFailureCategory::Protocol,
767 Some(ConnectionDirection::Inbound),
768 Some("initialized"),
769 None,
770 );
771 debug!("initialized notification outside the running state ignored");
772 return Flow::Continue;
773 };
774 let params = match decode_initialized_params(¶ms) {
775 Ok(params) => params,
776 Err(error) => {
777 self.failure_reporter.report(
778 ConnectionFailureCategory::Protocol,
779 Some(ConnectionDirection::Inbound),
780 Some("initialized"),
781 None,
782 );
783 warn!(%error, "dropping initialized notification with malformed params");
784 return Flow::Continue;
785 }
786 };
787 let Some(hook) = self.on_initialized.take() else {
788 return Flow::Continue;
789 };
790 let span = self.trace.notification_span("initialized");
791 let ctx = ServerContext::for_notification(
792 span,
793 self.client.clone(),
794 self.established_workspace(),
795 );
796 hook.invoke((Arc::clone(&self.state), ctx, params)).await;
797 }
798 other => {
799 let Lifecycle::Running(service) = &self.lifecycle else {
804 self.failure_reporter.report_unvalidated_inbound_method(
805 ConnectionFailureCategory::Protocol,
806 None,
807 );
808 debug!(method = other, "notification outside running state ignored");
809 return Flow::Continue;
810 };
811 let service = Arc::clone(service);
812
813 if let Some(built_in) = ProtocolNotification::from_method(other) {
823 if !self.accepts_protocol_notification(built_in) {
824 debug!(
825 method = other,
826 "document-sync notification disabled and ignored"
827 );
828 return Flow::Continue;
829 }
830 match self.process_protocol_notification(built_in, ¶ms) {
831 Ok(BuiltInGate::RunHook) => {}
832 Ok(BuiltInGate::ProtocolFailure) => {
833 self.failure_reporter.report(
834 ConnectionFailureCategory::Protocol,
835 Some(ConnectionDirection::Inbound),
836 Some(other),
837 None,
838 );
839 return Flow::Continue;
840 }
841 Err(error) => {
842 let (category, error) = match error {
843 BuiltInError::Protocol(error) => {
844 (ConnectionFailureCategory::Protocol, error)
845 }
846 BuiltInError::Overload(error) => {
847 (ConnectionFailureCategory::Overload, error)
848 }
849 };
850 self.failure_reporter.report(
851 category,
852 Some(ConnectionDirection::Inbound),
853 Some(other),
854 None,
855 );
856 warn!(method = other, %error, "protocol validation skipped its hook");
857 return Flow::Continue;
858 }
859 }
860 }
861
862 let params = match decode_value(¶ms) {
866 Ok(params) => params,
867 Err(error) => {
868 self.failure_reporter.report_unvalidated_inbound_method(
869 ConnectionFailureCategory::Protocol,
870 None,
871 );
872 debug!(method = other, %error, "notification params ignored");
873 return Flow::Continue;
874 }
875 };
876 self.dispatch_notification(service, other, params).await;
880 }
881 },
882 RawMessage::Response { id, result } => {
883 let id_num = match &id {
885 RequestId::Number(n) if *n > 0 => Some(*n as u32),
886 _ => None,
887 };
888 let delivered =
889 id_num.is_some_and(|n| self.client.outbound_registry().complete(n, result));
890 if !delivered {
891 self.failure_reporter.report(
892 ConnectionFailureCategory::Protocol,
893 Some(ConnectionDirection::Inbound),
894 None,
895 Some(&id),
896 );
897 debug!(?id, "ignoring response with unknown or non-numeric id");
898 }
899 }
900 RawMessage::ProtocolError { error } => {
901 self.failure_reporter.report(
902 ConnectionFailureCategory::Protocol,
903 Some(ConnectionDirection::Inbound),
904 None,
905 None,
906 );
907 self.protocol.send_protocol_error(error);
908 }
909 }
910
911 Flow::Continue
912 }
913
914 async fn dispatch_notification(
920 &mut self,
921 service: UserService<S>,
922 method: &str,
923 params: serde_json::Value,
924 ) {
925 let span = self.trace.notification_span(method);
926 let ctx = ServerContext::for_notification(
927 span,
928 self.client.clone(),
929 self.established_workspace(),
930 );
931 let result = service
932 .call(IncomingCall::notification(
933 method.to_string(),
934 params,
935 ctx,
936 Arc::clone(&self.state),
937 ))
938 .await;
939 if !matches!(result, ServiceResult::NoResponse) {
940 warn!("notification service attempted to produce a response");
941 }
942 }
943
944 fn process_protocol_notification(
957 &self,
958 built_in: ProtocolNotification,
959 raw_params: &Bytes,
960 ) -> std::result::Result<BuiltInGate, BuiltInError> {
961 match built_in {
962 ProtocolNotification::Open => {
963 let params: DidOpenTextDocumentParams = decode_params(raw_params)?;
964 self.documents.open(params.text_document)?;
965 }
966 ProtocolNotification::Change => {
967 let params: DidChangeTextDocumentParams = decode_params(raw_params)?;
968 validate_sync_changes(
969 self.document_sync
970 .change
971 .unwrap_or(TextDocumentSyncKind::Incremental),
972 ¶ms.content_changes,
973 )?;
974 self.documents.apply_changes(
975 ¶ms.text_document.text_document_identifier.uri,
976 params.text_document.version,
977 params.content_changes,
978 )?;
979 }
980 ProtocolNotification::Close => {
981 let params: DidCloseTextDocumentParams = decode_params(raw_params)?;
982 if self.documents.close(¶ms.text_document.uri).is_none() {
987 debug!(
988 uri = ?params.text_document.uri,
989 "closing a document that was not open"
990 );
991 }
992 }
993 ProtocolNotification::WillSave => {
994 let _: WillSaveTextDocumentParams = decode_params(raw_params)?;
995 }
996 ProtocolNotification::Save => {
997 let params: DidSaveTextDocumentParams = decode_params(raw_params)?;
998 if matches!(
999 &self.document_sync.save,
1000 Some(Save::SaveOptions(options))
1001 if options.include_text == Some(true)
1002 ) && params.text.is_none()
1003 {
1004 return Err(LspError::invalid_request(
1005 "didSave text is required by textDocumentSync.save.includeText",
1006 )
1007 .into());
1008 }
1009 }
1010 ProtocolNotification::NotebookOpen => {
1011 let params: DidOpenNotebookDocumentParams = decode_params(raw_params)?;
1012 let opened = self.open_cell_text_documents(params.cell_text_documents)?;
1018 let superseded = match self.notebooks.try_open(params.notebook_document) {
1019 Ok(superseded) => superseded,
1020 Err(error) => {
1021 self.rollback_cell_opens(opened);
1022 return Err(BuiltInError::Overload(error));
1023 }
1024 };
1025 if let Some(superseded) = superseded {
1026 debug!(
1031 uri = ?superseded.uri(),
1032 "re-opening a notebook that was already open"
1033 );
1034 self.close_departed_cells(&superseded);
1035 }
1036 }
1037 ProtocolNotification::NotebookChange => {
1038 let params: DidChangeNotebookDocumentParams = decode_params(raw_params)?;
1039 self.apply_notebook_change(params)?;
1040 }
1041 ProtocolNotification::NotebookSave => {
1042 let params: DidSaveNotebookDocumentParams = decode_params(raw_params)?;
1043 if !self.notebooks.contains(¶ms.notebook_document.uri) {
1048 debug!(
1049 uri = ?params.notebook_document.uri,
1050 "saving a notebook that is not synchronized"
1051 );
1052 }
1053 }
1054 ProtocolNotification::NotebookClose => {
1055 let params: DidCloseNotebookDocumentParams = decode_params(raw_params)?;
1056 match self.notebooks.close(¶ms.notebook_document.uri) {
1060 Some(removed) => {
1061 for cell in removed.cells() {
1062 self.documents.close(&cell.document);
1063 }
1064 }
1065 None => debug!(
1066 uri = ?params.notebook_document.uri,
1067 "closing a notebook that was not open"
1068 ),
1069 }
1070 for cell in ¶ms.cell_text_documents {
1071 self.documents.close(&cell.uri);
1072 }
1073 }
1074 ProtocolNotification::WorkspaceFolders => {
1075 let params: DidChangeWorkspaceFoldersParams = decode_params(raw_params)?;
1076 self.established_workspace().apply_folder_change(params);
1077 }
1078 ProtocolNotification::Configuration => {
1079 let params: DidChangeConfigurationParams = decode_params(raw_params)?;
1080 self.established_workspace()
1081 .set_configuration(params.settings);
1082 }
1083 ProtocolNotification::Trace => {
1084 let params: SetTraceParams = decode_params(raw_params)?;
1085 if matches!(¶ms.value, TraceValue::Custom(_)) {
1086 return Err(LspError::invalid_params(
1087 "setTrace value must be off, messages, or verbose",
1088 )
1089 .into());
1090 }
1091 self.established_workspace().set_trace(params.value);
1092 }
1093 ProtocolNotification::ProgressCancel => {
1094 return Ok(gate_progress_cancel(
1095 self.client.progress_registry(),
1096 raw_params,
1097 ));
1098 }
1099 }
1100 Ok(BuiltInGate::RunHook)
1101 }
1102
1103 fn apply_notebook_change(
1112 &self,
1113 params: DidChangeNotebookDocumentParams,
1114 ) -> std::result::Result<(), BuiltInError> {
1115 let planned = self.notebooks.plan_change(¶ms)?;
1116 let closed_cells = match params.change.cells {
1117 Some(cells) => self.apply_cell_text_documents(cells)?,
1118 None => Vec::new(),
1119 };
1120 if let Some(superseded) = self.notebooks.commit(planned) {
1124 self.close_departed_cells(&superseded);
1125 }
1126 for uri in closed_cells {
1127 self.documents.close(&uri);
1128 }
1129 Ok(())
1130 }
1131
1132 fn apply_cell_text_documents(
1140 &self,
1141 cells: NotebookDocumentCellChanges,
1142 ) -> std::result::Result<Vec<Uri>, BuiltInError> {
1143 let (opened_cells, closed_cells) = match cells.structure {
1144 Some(structure) => (
1145 structure.did_open.unwrap_or_default(),
1146 structure.did_close.unwrap_or_default(),
1147 ),
1148 None => (Vec::new(), Vec::new()),
1149 };
1150 let opened = self.open_cell_text_documents(opened_cells)?;
1151
1152 let mut edited = Vec::new();
1156 for cell in cells.text_content.unwrap_or_default() {
1157 let previous = self
1158 .documents
1159 .get(&cell.document.text_document_identifier.uri);
1160 match self.documents.apply_changes(
1161 &cell.document.text_document_identifier.uri,
1162 cell.document.version,
1163 cell.changes,
1164 ) {
1165 Ok(()) => edited.extend(previous),
1166 Err(error) => {
1167 for document in edited {
1168 self.documents.restore(document);
1169 }
1170 self.rollback_cell_opens(opened);
1171 return Err(error.into());
1172 }
1173 }
1174 }
1175
1176 Ok(closed_cells.into_iter().map(|cell| cell.uri).collect())
1177 }
1178
1179 fn open_cell_text_documents(
1185 &self,
1186 cells: Vec<TextDocumentItem>,
1187 ) -> std::result::Result<Vec<CellOpenRollback>, DocumentMutationError> {
1188 let mut opened = Vec::new();
1189 for cell in cells {
1190 let uri = cell.uri.clone();
1191 let previous = self.documents.get(&uri);
1192 if let Err(error) = self.documents.open(cell) {
1193 self.rollback_cell_opens(opened);
1194 return Err(error);
1195 }
1196 opened.push(CellOpenRollback { uri, previous });
1197 }
1198 Ok(opened)
1199 }
1200
1201 fn rollback_cell_opens(&self, opened: Vec<CellOpenRollback>) {
1202 for opened in opened.into_iter().rev() {
1203 match opened.previous {
1204 Some(document) => self.documents.restore(document),
1205 None => {
1206 self.documents.close(&opened.uri);
1207 }
1208 }
1209 }
1210 }
1211
1212 fn close_departed_cells(&self, superseded: &Notebook) {
1215 for cell in superseded.cells() {
1216 if self.notebooks.notebook_for_cell(&cell.document).is_none() {
1217 self.documents.close(&cell.document);
1218 }
1219 }
1220 }
1221
1222 fn accepts_protocol_notification(&self, built_in: ProtocolNotification) -> bool {
1223 match built_in {
1224 ProtocolNotification::WorkspaceFolders
1225 | ProtocolNotification::Configuration
1226 | ProtocolNotification::Trace
1227 | ProtocolNotification::ProgressCancel => true,
1228 ProtocolNotification::NotebookOpen
1237 | ProtocolNotification::NotebookChange
1238 | ProtocolNotification::NotebookSave
1239 | ProtocolNotification::NotebookClose => self.notebook_sync,
1240 _ if self.document_sync.change == Some(TextDocumentSyncKind::None) => false,
1241 ProtocolNotification::Open | ProtocolNotification::Close => {
1242 self.document_sync.open_close == Some(true)
1243 }
1244 ProtocolNotification::Change => matches!(
1245 self.document_sync.change,
1246 Some(TextDocumentSyncKind::Full | TextDocumentSyncKind::Incremental)
1247 ),
1248 ProtocolNotification::WillSave => self.document_sync.will_save == Some(true),
1249 ProtocolNotification::Save => matches!(
1250 self.document_sync.save,
1251 Some(Save::Bool(true)) | Some(Save::SaveOptions(_))
1252 ),
1253 }
1254 }
1255
1256 async fn initialize(&mut self, span: &Span, reservation: Reservation, params: Bytes) -> Flow {
1268 if !matches!(self.lifecycle, Lifecycle::Uninitialized(_)) {
1270 self.failure_reporter.report(
1271 ConnectionFailureCategory::Protocol,
1272 Some(ConnectionDirection::Inbound),
1273 Some("initialize"),
1274 Some(&reservation.id),
1275 );
1276 self.protocol.complete_inbound(
1277 reservation,
1278 Err(LspError::ServerError {
1279 code: -32600,
1280 message: "server already initialized".into(),
1281 data: None,
1282 }),
1283 );
1284 return Flow::Continue;
1285 }
1286
1287 let params = match decode_params::<InitializeParams>(¶ms) {
1290 Ok(params) => params,
1291 Err(err) => {
1292 self.failure_reporter.report(
1293 ConnectionFailureCategory::Protocol,
1294 Some(ConnectionDirection::Inbound),
1295 Some("initialize"),
1296 Some(&reservation.id),
1297 );
1298 self.protocol.complete_inbound(reservation, Err(err));
1299 return Flow::Continue;
1300 }
1301 };
1302
1303 let pending = match std::mem::replace(&mut self.lifecycle, Lifecycle::Initializing) {
1306 Lifecycle::Uninitialized(pending) => *pending,
1307 _ => unreachable!("initialize runs only while uninitialized"),
1309 };
1310 let Pending {
1311 registrations,
1312 file_provider,
1313 configure_initialize,
1314 on_initialize,
1315 on_initialized,
1316 on_shutdown,
1317 on_exit,
1318 layers,
1319 concurrency_limit,
1320 } = pending;
1321
1322 let mut registrar = InitializeRegistrar::new(registrations);
1328 let committed = match configure_initialize {
1329 Some(callback) => callback.invoke(¶ms, &mut registrar),
1330 None => Ok(()),
1331 }
1332 .and_then(|()| registrar.commit().map_err(LspError::internal));
1333
1334 let registrations = match committed {
1335 Ok(registrations) => registrations,
1336 Err(_err) => {
1337 self.protocol.complete_inbound(
1340 reservation,
1341 Err(LspError::internal("initialization failed")),
1342 );
1343 return Flow::Close(CloseCause::InitializeFailed);
1344 }
1345 };
1346
1347 let router = Arc::new(registrations.freeze());
1350
1351 self.on_initialized = on_initialized;
1355 self.on_shutdown = on_shutdown;
1356 self.on_exit = on_exit;
1357
1358 let established = Workspace::from_params_with_stores_and_provider(
1366 ¶ms,
1367 self.documents.clone(),
1368 self.notebooks.clone(),
1369 file_provider,
1370 self.client.shared_trace(),
1371 );
1372 let work_done_token = params.work_done_progress_params.work_done_token.clone();
1373 self.workspace = Some(established.clone());
1374
1375 let position_encoding = self.documents.negotiate_position_encoding(¶ms);
1376 let mut capabilities = router.generated_capabilities();
1377 let standard_capabilities = &mut capabilities;
1378 standard_capabilities.position_encoding = Some(position_encoding);
1379 let document_sync = router.document_sync();
1389 self.document_sync = document_sync.options;
1390 standard_capabilities.text_document_sync = Some(document_sync.capability);
1391 self.notebook_sync = router.notebook_sync_enabled();
1396 let mut workspace = standard_capabilities.workspace.take().unwrap_or_default();
1401 workspace.workspace_folders = Some(WorkspaceFoldersServerCapabilities {
1402 supported: Some(true),
1403 change_notifications: Some(true.into()),
1404 });
1405 standard_capabilities.workspace = Some(workspace);
1406
1407 let server_info = match on_initialize {
1410 Some(hook) => {
1411 let ctx = ServerContext::for_request(
1412 reservation.id.clone(),
1413 span.clone(),
1414 self.client.clone(),
1415 established,
1416 )
1417 .with_work_done_token(work_done_token);
1418 match hook
1419 .invoke((
1420 Arc::clone(&self.state),
1421 ctx,
1422 params,
1423 self.protocol.cancellation_child(),
1424 ))
1425 .instrument(span.clone())
1426 .await
1427 {
1428 Ok(server_info) => server_info,
1429 Err(err) => {
1430 self.protocol.complete_inbound(reservation, Err(err));
1435 return Flow::Close(CloseCause::InitializeFailed);
1436 }
1437 }
1438 }
1439 None => None,
1440 };
1441
1442 self.protocol.complete_inbound(
1443 reservation,
1444 encode_body(&WireInitializeResult {
1445 capabilities,
1446 server_info,
1447 }),
1448 );
1449 self.lifecycle = Lifecycle::Running(build_service_stack(
1450 router,
1451 layers,
1452 concurrency_limit,
1453 self.failure_reporter.clone(),
1454 ));
1455 Flow::Continue
1456 }
1457
1458 fn established_workspace(&self) -> Workspace {
1462 self.workspace.clone().expect(
1463 "user dispatch runs only after the initialize transaction establishes the workspace",
1464 )
1465 }
1466
1467 fn spawn_service_request(
1473 &mut self,
1474 service: UserService<S>,
1475 reservation: Reservation,
1476 method: String,
1477 params: serde_json::Value,
1478 ctx: ServerContext,
1479 cancellation: CancellationToken,
1480 default_timeout: Duration,
1481 ) {
1482 let state = Arc::clone(&self.state);
1483 let completion_gate = self.protocol.completion_gate();
1484 let permit = Arc::clone(&reservation._permit);
1485 let trace = self.trace;
1486 self.protocol.spawn(
1487 async move {
1488 let id = reservation.id.clone();
1489 let trace_id = id.clone();
1490 let trace_method = method.clone();
1491 let handler_timeout = HandlerTimeout::new(
1492 default_timeout,
1493 trace,
1494 trace_method.clone(),
1495 trace_id.clone(),
1496 );
1497 let call =
1498 IncomingCall::request(method, id, params, ctx, state, handler_timeout.clone());
1499 let partial_result_scope = call.context().partial_result_scope();
1500 let result =
1501 run_handler_with_deadline(service.call(call), cancellation, handler_timeout)
1502 .await;
1503 if let Some(scope) = partial_result_scope {
1504 scope.finish();
1505 }
1506 let result = match result {
1507 ServiceResult::Response(value) => encode_body(&value),
1508 ServiceResult::Error(error) => Err(error),
1509 ServiceResult::NoResponse => {
1510 Err(LspError::internal("request service returned no response"))
1511 }
1512 };
1513 completion_gate.complete(reservation, result);
1514 },
1515 permit,
1516 );
1517 }
1518
1519 async fn close(&mut self) {
1529 if matches!(self.lifecycle, Lifecycle::Exited) {
1530 return;
1531 }
1532 self.lifecycle = Lifecycle::Exited;
1533 self.protocol.close().await;
1534 self.documents.clear();
1538 self.notebooks.clear();
1539 }
1540}
1541
1542enum Flow {
1543 Continue,
1544 Close(CloseCause),
1548}
1549
1550#[cfg(all(test, not(target_arch = "wasm32")))]
1553fn final_close_cause(close: &CloseSignal<CloseCause>, out_tx: &OutboundQueue) -> CloseCause {
1554 let recorded = close
1555 .take_cause()
1556 .expect("every path out of the read-loop records its close cause");
1557 if out_tx.failure().is_cancelled() {
1558 CloseCause::WriterFailed
1559 } else {
1560 recorded
1561 }
1562}
1563
1564#[cfg(all(test, not(target_arch = "wasm32")))]
1565mod tests {
1566 use gen_lsp_types::ProgressToken;
1567 use tracing_subscriber::layer::SubscriberExt;
1568
1569 use crate::session::{InboundRegistry, TaskGroup};
1570
1571 use super::*;
1572
1573 fn gated_cancel(
1576 registry: &ProgressRegistry,
1577 raw_params: &'static [u8],
1578 ) -> (BuiltInGate, crate::test_util::EventCapture) {
1579 let _capture = crate::test_util::tracing_capture_lock();
1580 let events = crate::test_util::EventCapture::new();
1581 let subscriber = tracing_subscriber::registry().with(events.clone());
1582 let gate = tracing::subscriber::with_default(subscriber, || {
1583 gate_progress_cancel(registry, &Bytes::from_static(raw_params))
1584 });
1585 (gate, events)
1586 }
1587
1588 #[test]
1589 fn a_matching_cancellable_token_fires_and_opens_the_hook_gate() {
1590 let registry = ProgressRegistry::default();
1591 let cancellation = CancellationToken::new();
1592 registry.register(ProgressToken::Int(1), true, cancellation.clone());
1593
1594 let (gate, events) = gated_cancel(®istry, br#"{"token": 1}"#);
1595
1596 assert!(
1597 matches!(gate, BuiltInGate::RunHook),
1598 "a successful decode lets the hook run"
1599 );
1600 assert!(cancellation.is_cancelled());
1601 assert!(
1602 registry.is_active(&ProgressToken::Int(1)),
1603 "cancellation never ends the progress: the token stays registered"
1604 );
1605 assert!(
1606 events.messages().is_empty(),
1607 "a matched cancel logs nothing, got {:?}",
1608 events.messages()
1609 );
1610 }
1611
1612 #[test]
1613 fn non_cancellable_and_inactive_tokens_log_at_debug_and_keep_the_gate_open() {
1614 let registry = ProgressRegistry::default();
1615 let plain = CancellationToken::new();
1616 registry.register(ProgressToken::Int(1), false, plain.clone());
1617
1618 let (gate, events) = gated_cancel(®istry, br#"{"token": 1}"#);
1619 assert!(matches!(gate, BuiltInGate::RunHook));
1620 assert!(!plain.is_cancelled(), "a non-cancellable token never fires");
1621 assert!(
1622 events.contains_at(tracing::Level::DEBUG, "non-cancellable token"),
1623 "got {:?}",
1624 events.messages()
1625 );
1626
1627 let (gate, events) = gated_cancel(®istry, br#"{"token": 99}"#);
1628 assert!(matches!(gate, BuiltInGate::RunHook));
1629 assert!(
1630 events.contains_at(tracing::Level::DEBUG, "unknown or ended token"),
1631 "got {:?}",
1632 events.messages()
1633 );
1634 assert!(
1635 registry.is_active(&ProgressToken::Int(1)),
1636 "an unknown token leaves the registry untouched"
1637 );
1638 }
1639
1640 #[test]
1641 fn malformed_cancel_params_log_at_debug_and_close_the_hook_gate() {
1642 let registry = ProgressRegistry::default();
1643 let cancellation = CancellationToken::new();
1644 registry.register(ProgressToken::Int(1), true, cancellation.clone());
1645
1646 for raw in [
1647 br#"{"token": true}"#.as_slice(),
1648 br#"{}"#.as_slice(),
1649 b"not json".as_slice(),
1650 ] {
1651 let (gate, events) = gated_cancel(®istry, raw);
1652 assert!(
1653 matches!(gate, BuiltInGate::ProtocolFailure),
1654 "malformed params {raw:?} skip the hook"
1655 );
1656 assert!(
1657 events.contains_at(
1658 tracing::Level::DEBUG,
1659 "malformed window/workDoneProgress/cancel"
1660 ),
1661 "got {:?}",
1662 events.messages()
1663 );
1664 }
1665 assert!(
1666 !cancellation.is_cancelled(),
1667 "malformed params never cancel anything"
1668 );
1669 }
1670
1671 fn content_change(with_range: bool) -> gen_lsp_types::TextDocumentContentChangeEvent {
1672 if with_range {
1673 gen_lsp_types::TextDocumentContentChangePartial::new(
1674 gen_lsp_types::Range {
1675 start: gen_lsp_types::Position::new(0, 0),
1676 end: gen_lsp_types::Position::new(0, 1),
1677 },
1678 None,
1679 "replacement".to_string(),
1680 )
1681 .into()
1682 } else {
1683 gen_lsp_types::TextDocumentContentChangeWholeDocument {
1684 text: "replacement".to_string(),
1685 }
1686 .into()
1687 }
1688 }
1689
1690 #[test]
1691 fn sync_kind_validation_accepts_only_compatible_change_shapes() {
1692 assert!(
1693 validate_sync_changes(TextDocumentSyncKind::Full, &[content_change(false)]).is_ok()
1694 );
1695 assert!(
1696 validate_sync_changes(TextDocumentSyncKind::Full, &[content_change(true)]).is_err()
1697 );
1698 assert!(
1699 validate_sync_changes(
1700 TextDocumentSyncKind::Incremental,
1701 &[content_change(true), content_change(false)],
1702 )
1703 .is_ok()
1704 );
1705 assert!(
1706 validate_sync_changes(TextDocumentSyncKind::None, &[content_change(false)]).is_err()
1707 );
1708 }
1709
1710 #[test]
1711 fn the_first_requester_records_the_cause_and_later_ones_do_not_replace_it() {
1712 let close = CloseSignal::new();
1713 assert!(!close.requested().is_cancelled());
1714
1715 close.request(CloseCause::WriterFailed);
1716 close.request(CloseCause::Exit { code: 0 });
1717 close.request(CloseCause::ReaderEof);
1718
1719 assert!(
1720 close.requested().is_cancelled(),
1721 "requesting close wakes the read-loop"
1722 );
1723 assert!(
1724 matches!(close.take_cause(), Some(CloseCause::WriterFailed)),
1725 "the first cause requested is the one reported"
1726 );
1727 assert!(
1728 close.take_cause().is_none(),
1729 "the cause is taken once, by the read-loop that ran the close"
1730 );
1731 }
1732
1733 #[test]
1734 fn a_late_required_enqueue_failure_overrides_an_earlier_close_cause() {
1735 let message = RawMessage::Notification {
1736 method: "test/required".into(),
1737 params: Bytes::new(),
1738 };
1739 let (queue, _rx) = OutboundQueue::bounded(1, usize::MAX);
1740 queue.send(message.clone()).unwrap();
1741 let close = CloseSignal::new();
1742 close.request(CloseCause::InitializeFailed);
1743
1744 assert!(queue.send_required(message).is_err());
1745
1746 assert!(matches!(
1747 final_close_cause(&close, &queue),
1748 CloseCause::WriterFailed
1749 ));
1750 }
1751
1752 #[test]
1753 fn every_cause_maps_to_one_outcome_or_a_transport_error() {
1754 assert_eq!(
1755 CloseCause::Exit { code: 0 }.into_result().unwrap(),
1756 Outcome::Exit { code: 0 }
1757 );
1758 assert_eq!(
1759 CloseCause::ReaderEof.into_result().unwrap(),
1760 Outcome::TransportClosed
1761 );
1762 assert_eq!(
1763 CloseCause::WriterFailed.into_result().unwrap(),
1764 Outcome::WriterFailed
1765 );
1766 assert_eq!(
1767 CloseCause::InitializeFailed.into_result().unwrap(),
1768 Outcome::InitializeFailed
1769 );
1770 assert!(matches!(
1771 CloseCause::ReaderFailed(TransportError::Malformed("bad".into())).into_result(),
1772 Err(Error::Transport(_))
1773 ));
1774 }
1775
1776 #[test]
1781 fn a_stale_reservation_cannot_claim_a_reused_request_id() {
1782 let (out_tx, mut out_rx) =
1783 OutboundQueue::new(crate::ResourcePolicy::default().max_outbound_messages);
1784 let registry = InboundRegistry::new(2);
1785 let id = RequestId::Number(2);
1786 let session = CancellationToken::new();
1787
1788 let first = registry
1789 .reserve(id.clone(), Some(&session))
1790 .expect("the id is free")
1791 .reservation;
1792 assert!(
1793 matches!(
1794 registry.reserve(id.clone(), Some(&session)),
1795 Err(InboundReserveError::DuplicateId)
1796 ),
1797 "an in-flight id is not reserved twice"
1798 );
1799
1800 let cancelled = registry
1802 .claim_cancellation(&id)
1803 .expect("the first request is cancellable");
1804 enqueue_encoded(&out_tx, cancelled.id, Err(LspError::RequestCancelled));
1805 let second = registry
1807 .reserve(id.clone(), Some(&session))
1808 .expect("the id is free once the first request is answered")
1809 .reservation;
1810
1811 registry.complete(&out_tx, first, encode_body(&"race"));
1813 registry.complete(&out_tx, second, encode_body(&"reused"));
1814
1815 assert_eq!(
1816 out_rx.try_recv().unwrap().id(),
1817 Some(&id),
1818 "the cancellation answers the first request"
1819 );
1820 let answer = out_rx.try_recv().expect("the second request is answered");
1821 match answer {
1822 RawMessage::Response {
1823 result: Ok(body), ..
1824 } => assert_eq!(
1825 serde_json::from_slice::<String>(&body).unwrap(),
1826 "reused",
1827 "the second request gets its own result, not the stale one"
1828 ),
1829 other => panic!("expected a success response, got {other:?}"),
1830 }
1831 assert!(
1832 out_rx.try_recv().is_err(),
1833 "the stale reservation enqueued nothing"
1834 );
1835 }
1836
1837 #[test]
1838 fn an_exhausted_registry_stays_bounded_and_cancellation_releases_its_entry() {
1839 let registry = InboundRegistry::new(1);
1840 let session = CancellationToken::new();
1841 let accepted = registry
1842 .reserve(RequestId::Number(2), Some(&session))
1843 .expect("the one slot is available");
1844
1845 for id in 3..=66 {
1846 assert!(matches!(
1847 registry.reserve(RequestId::Number(id), Some(&session)),
1848 Err(InboundReserveError::CapacityExhausted)
1849 ));
1850 }
1851 assert_eq!(
1852 registry.inner.lock().unwrap().entries.len(),
1853 1,
1854 "the flood retained only the admitted registry entry"
1855 );
1856
1857 let cancelled = registry
1858 .claim_cancellation(&RequestId::Number(2))
1859 .expect("the admitted request is cancellable");
1860 assert!(
1861 registry.inner.lock().unwrap().entries.is_empty(),
1862 "cancellation releases the registry entry"
1863 );
1864 drop(cancelled);
1865 assert!(matches!(
1866 registry.reserve(RequestId::Number(67), Some(&session)),
1867 Err(InboundReserveError::CapacityExhausted)
1868 ));
1869 drop(accepted);
1870 assert!(
1871 registry
1872 .reserve(RequestId::Number(67), Some(&session))
1873 .is_ok(),
1874 "capacity returns after the admitted task drops its reservation"
1875 );
1876 }
1877
1878 #[cfg(not(target_arch = "wasm32"))]
1879 #[tokio::test]
1880 async fn a_finished_task_holds_capacity_until_its_handle_is_reaped() {
1881 let registry = InboundRegistry::new(1);
1882 let session = CancellationToken::new();
1883 let accepted = registry
1884 .reserve(RequestId::Number(2), Some(&session))
1885 .expect("the one slot is available");
1886 let permit = Arc::clone(&accepted.reservation._permit);
1887 let (out_tx, _out_rx) =
1888 OutboundQueue::new(crate::ResourcePolicy::default().max_outbound_messages);
1889 registry.complete(&out_tx, accepted.reservation, encode_body(&"done"));
1890
1891 let mut tasks = TaskGroup::new(default_runtime());
1892 tasks.spawn(async {}, permit);
1893 while !tasks.handles[0].handle.is_finished() {
1894 tasks.runtime.yield_now().await;
1895 }
1896 assert_eq!(tasks.handles.len(), 1, "the finished handle is still owned");
1897 assert!(matches!(
1898 registry.reserve(RequestId::Number(3), Some(&session)),
1899 Err(InboundReserveError::CapacityExhausted)
1900 ));
1901
1902 tasks.reap_finished().await;
1903 assert!(tasks.handles.is_empty(), "the finished handle was reaped");
1904 assert!(
1905 registry
1906 .reserve(RequestId::Number(3), Some(&session))
1907 .is_ok(),
1908 "reaping the handle releases its admission permit"
1909 );
1910 }
1911
1912 #[cfg(not(target_arch = "wasm32"))]
1913 #[tokio::test]
1914 async fn aborting_the_task_group_releases_disconnect_capacity() {
1915 let registry = InboundRegistry::new(1);
1916 let session = CancellationToken::new();
1917 let accepted = registry
1918 .reserve(RequestId::Number(2), Some(&session))
1919 .expect("the one slot is available");
1920 let permit = Arc::clone(&accepted.reservation._permit);
1921 let mut tasks = TaskGroup::new(default_runtime());
1922 tasks.spawn(std::future::pending(), permit);
1923
1924 registry.close_all();
1925 drop(accepted);
1926 assert!(registry.inner.lock().unwrap().entries.is_empty());
1927 assert!(matches!(
1928 registry.reserve(RequestId::Number(3), Some(&session)),
1929 Err(InboundReserveError::CapacityExhausted)
1930 ));
1931
1932 tasks.abort_and_join().await;
1933 assert!(tasks.handles.is_empty(), "disconnect joined every task");
1934 assert!(
1935 registry
1936 .reserve(RequestId::Number(3), Some(&session))
1937 .is_ok(),
1938 "joining aborted tasks releases their admission permits"
1939 );
1940 }
1941
1942 #[test]
1943 fn only_a_shutdown_exit_reports_code_zero() {
1944 assert_eq!(Outcome::Exit { code: 0 }.code(), 0);
1945 assert_eq!(Outcome::Exit { code: 1 }.code(), 1);
1946 assert_eq!(Outcome::TransportClosed.code(), 1);
1947 assert_eq!(Outcome::WriterFailed.code(), 1);
1948 assert_eq!(Outcome::InitializeFailed.code(), 1);
1949 }
1950
1951 struct DetectRuntimeTransport;
1956
1957 impl Transport for DetectRuntimeTransport {
1958 type Reader = DetectRuntimeReader;
1959 type Writer = DetectRuntimeWriter;
1960
1961 fn split(self) -> (Self::Reader, Self::Writer) {
1962 (DetectRuntimeReader, DetectRuntimeWriter)
1963 }
1964 }
1965
1966 struct DetectRuntimeReader;
1967
1968 impl TransportReader for DetectRuntimeReader {
1969 async fn recv(&mut self) -> std::result::Result<RawMessage, TransportError> {
1970 Err(TransportError::Closed)
1971 }
1972 }
1973
1974 struct DetectRuntimeWriter;
1975
1976 impl TransportWriter for DetectRuntimeWriter {
1977 async fn send(&mut self, _msg: RawMessage) -> std::result::Result<(), TransportError> {
1978 Ok(())
1979 }
1980
1981 async fn shutdown(self) -> std::result::Result<(), TransportError> {
1982 Ok(())
1983 }
1984 }
1985
1986 #[test]
1987 fn serving_without_a_tokio_runtime_reports_the_missing_runtime() {
1988 let server = Server::builder(()).build().expect("an empty server builds");
1991 let mut serving = Box::pin(server.serve(DetectRuntimeTransport));
1992 let waker = futures_util::task::noop_waker();
1993 let mut cx = std::task::Context::from_waker(&waker);
1994 let outcome = match std::future::Future::poll(serving.as_mut(), &mut cx) {
1995 std::task::Poll::Ready(outcome) => outcome,
1996 std::task::Poll::Pending => panic!("the missing runtime is reported on the first poll"),
1997 };
1998
1999 assert!(
2000 matches!(outcome, Err(Error::RuntimeRequired)),
2001 "serving without a runtime reports the missing runtime, got {outcome:?}"
2002 );
2003 }
2004
2005 enum TestOutboundNotification {}
2008
2009 impl crate::types::notification::Notification for TestOutboundNotification {
2010 type Params = serde_json::Value;
2011 const METHOD: &'static str = "test/outbound-notification";
2012 }
2013
2014 enum TestOutboundRequest {}
2015
2016 impl crate::types::request::Request for TestOutboundRequest {
2017 type Params = serde_json::Value;
2018 type Result = String;
2019 const METHOD: &'static str = "test/outbound-request";
2020 }
2021
2022 fn send_loop_message(tag: u8) -> RawMessage {
2023 RawMessage::Notification {
2024 method: "test/send-loop".into(),
2025 params: Bytes::from(vec![tag]),
2026 }
2027 }
2028
2029 fn encoded_len(message: &RawMessage) -> usize {
2030 crate::transport::envelope::serialize(message)
2031 .expect("the test message encodes")
2032 .len()
2033 }
2034
2035 struct ScriptedWriter {
2039 outbox: Arc<Mutex<Vec<RawMessage>>>,
2040 fail_on_send: Option<usize>,
2041 sends: usize,
2042 }
2043
2044 struct SlowWriter {
2045 started: Arc<tokio::sync::Notify>,
2046 releases: Arc<tokio::sync::Semaphore>,
2047 }
2048
2049 impl TransportWriter for SlowWriter {
2050 async fn send(&mut self, _msg: RawMessage) -> std::result::Result<(), TransportError> {
2051 self.started.notify_one();
2052 self.releases
2053 .acquire()
2054 .await
2055 .expect("the test keeps the release gate open")
2056 .forget();
2057 Ok(())
2058 }
2059
2060 async fn shutdown(self) -> std::result::Result<(), TransportError> {
2061 Ok(())
2062 }
2063 }
2064
2065 impl TransportWriter for ScriptedWriter {
2066 async fn send(&mut self, msg: RawMessage) -> std::result::Result<(), TransportError> {
2067 self.sends += 1;
2068 if self.fail_on_send == Some(self.sends) {
2069 return Err(TransportError::Closed);
2070 }
2071 self.outbox.lock().unwrap().push(msg);
2072 Ok(())
2073 }
2074
2075 async fn shutdown(self) -> std::result::Result<(), TransportError> {
2076 Ok(())
2077 }
2078 }
2079
2080 #[tokio::test]
2081 async fn requests_responses_and_notifications_share_one_depth_counter() {
2082 let (queue, _rx) =
2083 OutboundQueue::new(crate::ResourcePolicy::default().max_outbound_messages);
2084 let client = ClientHandle::new(queue.clone(), OutboundRegistry::default(), None);
2085
2086 client
2087 .notify::<TestOutboundNotification>(serde_json::json!({}))
2088 .unwrap();
2089 enqueue_encoded(
2090 &queue,
2091 RequestId::Number(1),
2092 Ok(Bytes::from_static(b"null")),
2093 );
2094
2095 let pending = client.request::<TestOutboundRequest>(serde_json::json!({}));
2098 futures_util::pin_mut!(pending);
2099 assert!(
2100 futures_util::poll!(pending.as_mut()).is_pending(),
2101 "the request awaits the peer's response"
2102 );
2103 assert_eq!(queue.depth(), 3);
2104 client
2105 .outbound_registry()
2106 .complete(1, Ok(Bytes::from_static(b"\"pong\"")));
2107 let answer = pending.await;
2108 assert_eq!(answer.unwrap(), "pong");
2109
2110 assert_eq!(
2111 queue.depth(),
2112 3,
2113 "a notification, a response, and a request all increment the one counter"
2114 );
2115 }
2116
2117 #[tokio::test]
2118 async fn writer_failure_releases_attempted_and_abandoned_accounting() {
2119 let (queue, rx) = OutboundQueue::new(16);
2120 let client = ClientHandle::new(queue.clone(), OutboundRegistry::default(), None);
2121 for tag in 0..3 {
2122 queue.send(send_loop_message(tag)).unwrap();
2123 }
2124 assert_eq!(queue.depth(), 3);
2125
2126 let outbox = Arc::new(Mutex::new(Vec::new()));
2127 let writer = ScriptedWriter {
2128 outbox: outbox.clone(),
2129 fail_on_send: Some(2),
2130 sends: 0,
2131 };
2132 let close = CloseSignal::new();
2133 send_loop(writer, rx, client, close.clone()).await;
2134
2135 assert_eq!(
2136 queue.depth(),
2137 0,
2138 "writer failure abandons every queued slot"
2139 );
2140 assert_eq!(
2141 queue.encoded_bytes(),
2142 0,
2143 "writer failure abandons every queued byte charge"
2144 );
2145 assert_eq!(
2146 outbox.lock().unwrap().len(),
2147 1,
2148 "only the first send landed on the transport"
2149 );
2150 assert!(
2151 matches!(close.take_cause(), Some(CloseCause::WriterFailed)),
2152 "the writer reports its terminal failure"
2153 );
2154 }
2155
2156 #[tokio::test]
2157 async fn draining_after_close_decrements_every_message_in_order() {
2158 let (queue, rx) = OutboundQueue::new(2);
2159 let client = ClientHandle::new(queue.clone(), OutboundRegistry::default(), None);
2160 for tag in 0..3 {
2161 queue.send(send_loop_message(tag)).unwrap();
2162 }
2163 client.close_outbound();
2164
2165 let outbox = Arc::new(Mutex::new(Vec::new()));
2166 let writer = ScriptedWriter {
2167 outbox: outbox.clone(),
2168 fail_on_send: None,
2169 sends: 0,
2170 };
2171 let close = CloseSignal::new();
2172 send_loop(writer, rx, client, close.clone()).await;
2173
2174 assert_eq!(
2175 queue.depth(),
2176 0,
2177 "draining decrements every queued message, above and below the threshold"
2178 );
2179 assert_eq!(
2180 queue.encoded_bytes(),
2181 0,
2182 "close releases every encoded-byte charge after draining"
2183 );
2184 let tags: Vec<u8> = outbox
2185 .lock()
2186 .unwrap()
2187 .iter()
2188 .map(|msg| match msg {
2189 RawMessage::Notification { params, .. } => params[0],
2190 other => panic!("expected notifications, got {other:?}"),
2191 })
2192 .collect();
2193 assert_eq!(
2194 tags,
2195 vec![0, 1, 2],
2196 "draining never drops or reorders a message"
2197 );
2198 assert!(
2199 close.take_cause().is_none(),
2200 "clean draining is not a writer failure"
2201 );
2202 }
2203
2204 #[tokio::test]
2205 async fn a_slow_reader_cannot_grow_count_or_bytes_past_the_policy() {
2206 let message_bytes = encoded_len(&send_loop_message(0));
2207 let (queue, rx) = OutboundQueue::bounded(2, message_bytes * 2);
2208 let client = ClientHandle::new(queue.clone(), OutboundRegistry::default(), None);
2209 queue.send(send_loop_message(0)).unwrap();
2210 queue.send(send_loop_message(1)).unwrap();
2211
2212 let started = Arc::new(tokio::sync::Notify::new());
2213 let releases = Arc::new(tokio::sync::Semaphore::new(0));
2214 let writer = SlowWriter {
2215 started: started.clone(),
2216 releases: releases.clone(),
2217 };
2218 let close = CloseSignal::new();
2219 let serving = tokio::spawn(send_loop(writer, rx, client.clone(), close));
2220
2221 started.notified().await;
2222 assert_eq!(queue.depth(), 2, "the in-flight write remains accounted");
2223 assert_eq!(queue.encoded_bytes(), message_bytes * 2);
2224 assert!(matches!(
2225 queue.send(send_loop_message(2)),
2226 Err(crate::client::OutboundSendError::Overloaded)
2227 ));
2228 assert_eq!(queue.depth(), 2, "the rejected send consumes no slot");
2229 assert_eq!(
2230 queue.encoded_bytes(),
2231 message_bytes * 2,
2232 "the rejected send consumes no bytes"
2233 );
2234
2235 releases.add_permits(1);
2236 for _ in 0..100 {
2237 if queue.depth() == 1 {
2238 break;
2239 }
2240 tokio::task::yield_now().await;
2241 }
2242 assert_eq!(queue.depth(), 1, "a successful write releases one slot");
2243 assert_eq!(
2244 queue.encoded_bytes(),
2245 message_bytes,
2246 "a successful write releases exactly its encoded bytes"
2247 );
2248
2249 client.close_outbound();
2250 releases.add_permits(1);
2251 serving.await.unwrap();
2252 assert_eq!(queue.depth(), 0, "close drains the remaining message");
2253 assert_eq!(
2254 queue.encoded_bytes(),
2255 0,
2256 "close releases all byte accounting"
2257 );
2258 }
2259}