1use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
4use std::sync::{Arc, Mutex as StdMutex, PoisonError};
5
6use lsp_types::{
7 ErrorCodes, LspErrorCodes, LspRequestMethod, Notification as _, PublishDiagnosticsParams,
8};
9use serde::Serialize;
10use serde::de::DeserializeOwned;
11use serde_json::Value;
12use tokio::sync::{Mutex, mpsc, oneshot};
13use tokio::task::JoinHandle;
14use tokio::time::{Duration, Instant, timeout, timeout_at};
15use tracing::{debug, error, trace, warn};
16
17use crate::config::{LanguageId, LspServerConfig, LspSettings, ServerId};
18use crate::error::{BackgroundTask, Error, Result};
19use crate::lsp::transport::{LspTransport, LspTransportReader};
20use crate::lsp::types::{
21 InboundMessage, JsonRpcError, JsonRpcNotification, JsonRpcOutcome, JsonRpcReply,
22 JsonRpcRequest, JsonRpcResponse, JsonRpcVersion, LspNotification, RequestId,
23};
24use crate::lsp::{DropCounter, DropLog, Lane, NotificationSink, ServerMessage};
25use crate::redaction::{RedactedText, Redactions};
26use crate::util::MAX_ERROR_MESSAGE_CALLER_BYTES;
27
28mod cancel;
29mod notification;
30
31use cancel::{InFlightRequest, PendingRequests, Resolution};
32use notification::OutboundNotification;
33
34const SERVER_CANCELLED_MAX_RETRIES: u32 = 3;
36
37const SERVER_CANCELLED_INITIAL_DELAY_MS: u64 = 500;
39
40const READER_CHANNEL_CAPACITY: usize = 100;
50
51const READER_TASK_ABORT_GRACE: Duration = Duration::from_secs(1);
61
62pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(10);
66
67pub const MAX_CONSECUTIVE_UNDECODABLE_FRAMES: u32 = 64;
76
77const _: () = assert!(MAX_CONSECUTIVE_UNDECODABLE_FRAMES > 0);
78
79const SHUTDOWN_ABORT_SETTLE: Duration = Duration::from_millis(50);
82
83pub const CONTENT_MODIFIED_RETRY_METHODS: &[LspRequestMethod<'static>] = &[
106 LspRequestMethod::TextDocumentSignatureHelp,
107 LspRequestMethod::TextDocumentInlayHint,
108 LspRequestMethod::TextDocumentCompletion,
109 LspRequestMethod::TextDocumentPrepareCallHierarchy,
110 LspRequestMethod::CallHierarchyIncomingCalls,
111 LspRequestMethod::CallHierarchyOutgoingCalls,
112 LspRequestMethod::TextDocumentDiagnostic,
113 LspRequestMethod::TextDocumentHover,
114 LspRequestMethod::TextDocumentDefinition,
115 LspRequestMethod::TextDocumentReferences,
116 LspRequestMethod::TextDocumentImplementation,
117 LspRequestMethod::TextDocumentTypeDefinition,
118 LspRequestMethod::TextDocumentDocumentSymbol,
119 LspRequestMethod::WorkspaceSymbol,
120];
121
122const COMPLETION_TIMEOUT_CAP: Duration = Duration::from_secs(10);
130
131const CODE_ACTION_RESOLVE_TIMEOUT_CAP: Duration = Duration::from_secs(10);
142
143fn spawn_reader_task(
163 mut reader: LspTransportReader,
164) -> (JoinHandle<()>, mpsc::Receiver<Result<InboundMessage>>) {
165 let (tx, rx) = mpsc::channel(READER_CHANNEL_CAPACITY);
166 let handle = tokio::spawn(async move {
167 let mut run = UndecodableRun::default();
168 loop {
169 let message = reader
170 .receive()
171 .await
172 .and_then(|message| run.observe(message));
173 let is_err = message.is_err();
174 if tx.send(message).await.is_err() || is_err {
175 break;
176 }
177 }
178 });
179 (handle, rx)
180}
181
182#[derive(Debug, Default)]
184struct UndecodableRun {
185 in_a_row: u32,
186 drops: DropCounter,
187}
188
189impl UndecodableRun {
190 fn observe(&mut self, message: InboundMessage) -> Result<InboundMessage> {
200 self.observe_at(message, std::time::Instant::now())
201 }
202
203 fn observe_at(
204 &mut self,
205 message: InboundMessage,
206 now: std::time::Instant,
207 ) -> Result<InboundMessage> {
208 if !message.is_undecodable() {
209 self.in_a_row = 0;
210 return Ok(message);
211 }
212 self.in_a_row = self.in_a_row.saturating_add(1);
213 if self.in_a_row >= MAX_CONSECUTIVE_UNDECODABLE_FRAMES {
214 return Err(Error::LspProtocolError(RedactedText::fixed(
215 "too many consecutive undecodable messages",
216 )));
217 }
218 if let Some(suppressed) = self.drops.record(now) {
219 warn!(
220 "Dropped an undecodable LSP message ({suppressed} others dropped since the last \
221 warning); further ones are logged at debug for a minute"
222 );
223 } else {
224 debug!(
225 "Dropped an undecodable LSP message ({} in a row)",
226 self.in_a_row
227 );
228 }
229 Ok(message)
230 }
231}
232
233#[derive(Debug, Clone, Default)]
236pub enum PendingFailure {
237 #[default]
239 Terminated,
240 Restarted(ServerId),
242}
243
244impl PendingFailure {
245 fn error(&self) -> Error {
246 match self {
247 Self::Terminated => Error::ServerTerminated,
248 Self::Restarted(server_id) => Error::ServerRestarted {
249 server_id: server_id.clone(),
250 },
251 }
252 }
253}
254
255#[derive(Debug)]
263pub struct LspClient {
264 config: LspServerConfig,
266
267 state: Arc<Mutex<super::ServerState>>,
269
270 request_counter: Arc<AtomicI64>,
272
273 command_tx: mpsc::Sender<ClientCommand>,
275
276 pending_requests: PendingRequests,
284
285 closing: Arc<AtomicBool>,
288
289 pending_failure: Arc<StdMutex<PendingFailure>>,
292
293 receiver_task: Option<JoinHandle<Result<()>>>,
295
296 redactions: Arc<Redactions>,
298
299 connection_id: ConnectionId,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub struct ConnectionId(u64);
310
311impl ConnectionId {
312 fn next() -> Self {
313 static NEXT: AtomicU64 = AtomicU64::new(1);
314 Self(NEXT.fetch_add(1, Ordering::Relaxed))
315 }
316}
317
318impl Clone for LspClient {
319 fn clone(&self) -> Self {
324 Self {
325 config: self.config.clone(),
326 state: Arc::clone(&self.state),
327 request_counter: Arc::clone(&self.request_counter),
328 command_tx: self.command_tx.clone(),
329 pending_requests: self.pending_requests.clone(),
330 closing: Arc::clone(&self.closing),
331 pending_failure: Arc::clone(&self.pending_failure),
332 receiver_task: None,
333 redactions: Arc::clone(&self.redactions),
334 connection_id: self.connection_id,
335 }
336 }
337}
338
339#[derive(Debug)]
342struct Lanes {
343 notifications: NotificationSink,
344 lifecycle: mpsc::Sender<LspNotification>,
345}
346
347#[derive(Debug)]
350enum Route<'a> {
351 Mailbox(&'a NotificationSink, PublishDiagnosticsParams),
353 Messages(&'a NotificationSink, ServerMessage),
355 Lifecycle(&'a mpsc::Sender<LspNotification>, LspNotification),
357 Floor,
359}
360
361#[derive(Debug, PartialEq, Eq)]
363enum Delivery {
364 Delivered,
366 Dropped(Lane),
368}
369
370#[cfg(test)]
372#[derive(Debug)]
373pub struct CommandProbe(mpsc::Receiver<ClientCommand>);
374
375#[cfg(test)]
376impl CommandProbe {
377 pub(crate) fn next_method(&mut self) -> Option<String> {
379 match self.0.try_recv().ok()? {
380 ClientCommand::SendRequest { request } => Some(request.method),
381 ClientCommand::SendNotification { method, .. } => Some(method),
382 ClientCommand::Shutdown => Some("<shutdown>".to_owned()),
383 }
384 }
385}
386
387#[derive(Debug)]
389enum ClientCommand {
390 SendRequest { request: JsonRpcRequest },
392 SendNotification {
394 method: String,
395 params: Option<Value>,
396 },
397 Shutdown,
399}
400
401impl LspClient {
402 #[must_use]
407 pub fn new(config: LspServerConfig) -> Self {
408 let (command_tx, _command_rx) = mpsc::channel(1); Self {
414 config,
415 state: Arc::new(Mutex::new(super::ServerState::Uninitialized)),
416 request_counter: Arc::new(AtomicI64::new(1)),
417 command_tx,
418 pending_requests: PendingRequests::default(),
419 closing: Arc::default(),
420 pending_failure: Arc::default(),
421 receiver_task: None,
422 redactions: Arc::default(),
423 connection_id: ConnectionId::next(),
424 }
425 }
426
427 #[cfg(test)]
430 pub(crate) fn with_command_channel(
431 config: LspServerConfig,
432 capacity: usize,
433 ) -> (Self, CommandProbe) {
434 let (command_tx, command_rx) = mpsc::channel(capacity);
435 (
436 Self {
437 command_tx,
438 ..Self::new(config)
439 },
440 CommandProbe(command_rx),
441 )
442 }
443
444 #[cfg(test)]
448 pub(crate) fn from_transport(
449 config: LspServerConfig,
450 transport: (LspTransport, LspTransportReader),
451 ) -> Self {
452 Self::spawn(config, transport, None, Arc::default())
453 }
454
455 pub(crate) fn from_transport_with_notifications(
464 config: LspServerConfig,
465 transport: (LspTransport, LspTransportReader),
466 notification_tx: NotificationSink,
467 lifecycle_tx: mpsc::Sender<LspNotification>,
468 redactions: Arc<Redactions>,
469 ) -> Self {
470 let lanes = Lanes {
471 notifications: notification_tx,
472 lifecycle: lifecycle_tx,
473 };
474 Self::spawn(config, transport, Some(lanes), redactions)
475 }
476
477 fn spawn(
479 config: LspServerConfig,
480 transport: (LspTransport, LspTransportReader),
481 lanes: Option<Lanes>,
482 redactions: Arc<Redactions>,
483 ) -> Self {
484 let pending_requests = PendingRequests::default();
485 let pending_failure = Arc::<StdMutex<PendingFailure>>::default();
486
487 let (command_tx, command_rx) = mpsc::channel(100);
488
489 let receiver_task = tokio::spawn(Self::message_loop(
490 transport,
491 command_rx,
492 pending_requests.clone(),
493 Arc::clone(&pending_failure),
494 lanes,
495 Arc::clone(&redactions),
496 Self::shared_settings(&config),
497 ));
498
499 Self {
500 config,
501 state: Arc::new(Mutex::new(super::ServerState::Initializing)),
502 request_counter: Arc::new(AtomicI64::new(1)),
503 command_tx,
504 pending_requests,
505 closing: Arc::default(),
506 pending_failure,
507 receiver_task: Some(receiver_task),
508 redactions,
509 connection_id: ConnectionId::next(),
510 }
511 }
512
513 fn shared_settings(config: &LspServerConfig) -> Option<Arc<LspSettings>> {
515 config.settings.clone().map(Arc::new)
516 }
517
518 pub(crate) const fn connection_id(&self) -> ConnectionId {
520 self.connection_id
521 }
522
523 pub(crate) const fn redactions(&self) -> &Arc<Redactions> {
525 &self.redactions
526 }
527
528 #[must_use]
530 pub const fn language_id(&self) -> &LanguageId {
531 &self.config.language_id
532 }
533
534 pub async fn state(&self) -> super::ServerState {
536 *self.state.lock().await
537 }
538
539 #[must_use]
579 pub const fn request_timeout(&self) -> Duration {
580 self.config.request_timeout_seconds.as_duration()
581 }
582
583 #[must_use]
607 pub fn completion_timeout(&self) -> Duration {
608 self.request_timeout().min(COMPLETION_TIMEOUT_CAP)
609 }
610
611 #[must_use]
631 pub fn code_action_resolve_timeout(&self) -> Duration {
632 self.request_timeout().min(CODE_ACTION_RESOLVE_TIMEOUT_CAP)
633 }
634
635 async fn register_and_send_request(
640 &self,
641 request: JsonRpcRequest,
642 responder: oneshot::Sender<Result<Value>>,
643 ) -> Result<InFlightRequest<'_>> {
644 let mut in_flight = InFlightRequest::register(self, request.id.clone(), responder);
645 self.command_tx
646 .send(ClientCommand::SendRequest { request })
647 .await
648 .map_err(|_| Error::ServerTerminated)?;
649 in_flight.mark_sent();
650 Ok(in_flight)
651 }
652
653 pub async fn request<P, R>(
692 &self,
693 method: &str,
694 params: P,
695 timeout_duration: Duration,
696 ) -> Result<R>
697 where
698 P: Serialize,
699 R: DeserializeOwned,
700 {
701 Box::pin(self.request_inner(method, params, timeout_duration))
702 .await
703 .map_err(UnclassifiedError::surface)
704 }
705
706 async fn request_inner<P, R>(
707 &self,
708 method: &str,
709 params: P,
710 timeout_duration: Duration,
711 ) -> std::result::Result<R, UnclassifiedError>
712 where
713 P: Serialize,
714 R: DeserializeOwned,
715 {
716 let params_value = Self::omit_null_params(
717 serde_json::to_value(params).map_err(|e| UnclassifiedError::logged(e.into()))?,
718 );
719 let mut delay_ms = SERVER_CANCELLED_INITIAL_DELAY_MS;
720
721 for attempt in 0..=SERVER_CANCELLED_MAX_RETRIES {
722 if attempt > 0 {
723 debug!(
724 "Retrying {} (attempt {}/{}), backoff={}ms",
725 method, attempt, SERVER_CANCELLED_MAX_RETRIES, delay_ms
726 );
727 tokio::time::sleep(Duration::from_millis(delay_ms)).await;
728 delay_ms = delay_ms.saturating_mul(2);
729 }
730
731 let id = RequestId::Number(self.request_counter.fetch_add(1, Ordering::SeqCst));
732 let (response_tx, response_rx) = oneshot::channel();
733 let request = JsonRpcRequest {
734 jsonrpc: JsonRpcVersion,
735 id: id.clone(),
736 method: method.to_string(),
737 params: params_value.clone(),
738 };
739
740 debug!("Sending request: {} (id={:?})", method, id);
741
742 let mut in_flight = self
743 .register_and_send_request(request, response_tx)
744 .await
745 .map_err(UnclassifiedError::logged)?;
746
747 let outcome = match timeout(timeout_duration, response_rx).await {
748 Ok(received) => {
749 in_flight.mark_settled();
750 received.map_err(|_| UnclassifiedError::logged(Error::ServerTerminated))?
751 }
752 Err(_elapsed) => {
753 in_flight.expire();
757 return Err(UnclassifiedError::logged(Error::Timeout(timeout_duration)));
758 }
759 };
760
761 match outcome {
762 Ok(result_value) => {
763 return serde_json::from_value(result_value).map_err(|e| {
764 UnclassifiedError::logged(
765 self.redactions.protocol_error(format_args!(
766 "Failed to deserialize response: {e}"
767 )),
768 )
769 });
770 }
771 Err(Error::LspServerError(response)) if Self::retries(&response, method) => {
772 if attempt == SERVER_CANCELLED_MAX_RETRIES {
773 error!(
778 "LSP error response: {} (code {}) on '{}' (id={:?}), retries exhausted",
779 Self::truncate_error_message_for_log(&response.message),
780 response.code,
781 method,
782 id
783 );
784 return Err(UnclassifiedError::logged(Error::LspServerError(response)));
785 }
786 warn!(
787 "LSP error response: {} (code {}) on '{}' (id={:?}), will retry",
788 Self::truncate_error_message_for_log(&response.message),
789 response.code,
790 method,
791 id
792 );
793 }
795 Err(Error::LspServerError(response)) => {
796 return Err(UnclassifiedError::server_response(response, method, id));
797 }
798 Err(e) => return Err(UnclassifiedError::logged(e)),
799 }
800 }
801
802 Err(UnclassifiedError::logged(Error::ServerTerminated))
803 }
804
805 fn retries(response: &JsonRpcError, method: &str) -> bool {
810 let retryable = match LspErrorCodes::from(response.code) {
811 LspErrorCodes::ServerCancelled => true,
812 LspErrorCodes::ContentModified => {
813 CONTENT_MODIFIED_RETRY_METHODS.contains(&LspRequestMethod::from(method))
814 }
815 _ => false,
816 };
817 retryable && Self::should_retrigger(response.data.as_ref())
818 }
819
820 pub(crate) async fn request_typed_classified<R>(
828 &self,
829 params: R::Params,
830 timeout_duration: Duration,
831 ) -> std::result::Result<R::Result, UnclassifiedError>
832 where
833 R: lsp_types::Request,
834 {
835 Box::pin(self.request_inner(R::METHOD.as_str(), params, timeout_duration)).await
836 }
837
838 pub async fn request_typed<R>(
848 &self,
849 params: R::Params,
850 timeout_duration: Duration,
851 ) -> Result<R::Result>
852 where
853 R: lsp_types::Request,
854 {
855 self.request(R::METHOD.as_str(), params, timeout_duration)
856 .await
857 }
858
859 fn should_retrigger(data: Option<&Value>) -> bool {
873 data.is_none_or(|v| {
874 v.get("retriggerRequest")
875 .and_then(Value::as_bool)
876 .unwrap_or(true)
877 })
878 }
879
880 pub(crate) fn is_message_loop_finished(&self) -> bool {
886 self.receiver_task
887 .as_ref()
888 .is_some_and(JoinHandle::is_finished)
889 }
890
891 pub(crate) fn fail_pending_requests(&self) {
899 Self::drain_and_fail_pending(&self.pending_requests, &self.pending_failure);
900 }
901
902 pub(crate) fn mark_restarted(&self, server_id: ServerId) {
906 *self
907 .pending_failure
908 .lock()
909 .unwrap_or_else(PoisonError::into_inner) = PendingFailure::Restarted(server_id);
910 }
911
912 fn drain_and_fail_pending(pending: &PendingRequests, failure: &StdMutex<PendingFailure>) {
916 for sender in pending.drain_waiting() {
917 let error = failure
918 .lock()
919 .unwrap_or_else(PoisonError::into_inner)
920 .error();
921 let _ = sender.send(Err(error));
922 }
923 }
924
925 pub async fn notify<P>(&self, method: &str, params: P) -> Result<()>
931 where
932 P: Serialize,
933 {
934 let notification = OutboundNotification::new(method, params)?;
935
936 debug!("Sending notification: {}", method);
937
938 self.send_notification(notification).await
939 }
940
941 pub async fn notify_typed<N>(&self, params: N::Params) -> Result<()>
949 where
950 N: lsp_types::Notification,
951 {
952 self.notify(N::METHOD.as_str(), params).await
953 }
954
955 #[allow(
968 clippy::arithmetic_side_effects,
969 reason = "SHUTDOWN_TIMEOUT is a small constant"
970 )]
971 pub async fn shutdown(mut self) -> Result<()> {
972 self.shutdown_until(Instant::now() + SHUTDOWN_TIMEOUT).await
973 }
974
975 pub(crate) async fn shutdown_until(&mut self, deadline: Instant) -> Result<()> {
977 debug!("Shutting down LSP client");
978
979 self.suppress_cancels();
980 let mut task = self.receiver_task.take();
981 let command_tx = &self.command_tx;
982 let outcome = timeout_at(deadline, async {
983 let _ = command_tx.send(ClientCommand::Shutdown).await;
984 match task.as_mut() {
985 Some(task) => match task.await {
986 Ok(result) => result,
987 Err(source) => Err(Error::TaskFailed {
988 task: BackgroundTask::LspReceiver,
989 source,
990 }),
991 },
992 None => Ok(()),
993 }
994 })
995 .await;
996
997 let result = outcome.unwrap_or(Err(Error::ShutdownTimeout));
998 if matches!(result, Err(Error::ShutdownTimeout)) {
999 if let Some(task) = task {
1000 task.abort();
1001 let _ = timeout(SHUTDOWN_ABORT_SETTLE, task).await;
1004 }
1005 Self::drain_and_fail_pending(&self.pending_requests, &self.pending_failure);
1006 }
1007
1008 *self.state.lock().await = super::ServerState::Shutdown;
1009
1010 result
1011 }
1012
1013 #[allow(
1020 clippy::too_many_arguments,
1021 reason = "one-to-one with the spawned loop's channels and shared state"
1022 )]
1023 async fn message_loop(
1024 transport: (LspTransport, LspTransportReader),
1025 mut command_rx: mpsc::Receiver<ClientCommand>,
1026 pending_requests: PendingRequests,
1027 pending_failure: Arc<StdMutex<PendingFailure>>,
1028 lanes: Option<Lanes>,
1029 redactions: Arc<Redactions>,
1030 settings: Option<Arc<LspSettings>>,
1031 ) -> Result<()> {
1032 debug!("Message loop started");
1033 let (mut transport, reader) = transport;
1034 let (reader_handle, mut msg_rx) = spawn_reader_task(reader);
1035 let result = {
1036 let _abort_reader_on_drop = crate::util::AbortOnDrop(&reader_handle);
1038 Self::message_loop_inner(
1039 &mut transport,
1040 &mut msg_rx,
1041 &mut command_rx,
1042 &pending_requests,
1043 lanes.as_ref(),
1044 &redactions,
1045 settings.as_deref(),
1046 )
1047 .await
1048 };
1049 let _ = timeout(READER_TASK_ABORT_GRACE, reader_handle).await;
1050 drop(command_rx);
1057 Self::drain_and_fail_pending(&pending_requests, &pending_failure);
1061 if let Err(ref e) = result {
1062 error!("Message loop exiting with error: {}", e);
1063 } else {
1064 debug!("Message loop exiting normally");
1065 }
1066 result
1067 }
1068
1069 fn omit_null_params(params: Value) -> Option<Value> {
1073 if params.is_null() { None } else { Some(params) }
1074 }
1075
1076 fn truncate_error_message_for_log(message: &str) -> String {
1084 crate::util::truncate_str(message, crate::util::MAX_LOG_STRING_BYTES)
1085 }
1086
1087 fn route(notification: LspNotification, lanes: Option<&Lanes>) -> Route<'_> {
1107 let Some(lanes) = lanes else {
1108 return Route::Floor;
1109 };
1110 match notification {
1111 LspNotification::PublishDiagnostics(params) => {
1112 Route::Mailbox(&lanes.notifications, params)
1113 }
1114 LspNotification::LogMessage(params) => {
1115 Route::Messages(&lanes.notifications, ServerMessage::Log(params))
1116 }
1117 LspNotification::ShowMessage(params) => {
1118 Route::Messages(&lanes.notifications, ServerMessage::Show(params))
1119 }
1120 LspNotification::Progress(params)
1121 if crate::lsp::types::ProgressKind::from_value(¶ms.value).is_some() =>
1122 {
1123 Route::Lifecycle(&lanes.lifecycle, LspNotification::Progress(params))
1124 }
1125 LspNotification::Other { method, params }
1126 if method.as_ref() != lsp_types::ProgressNotification::METHOD.as_str() =>
1127 {
1128 Route::Lifecycle(&lanes.lifecycle, LspNotification::Other { method, params })
1129 }
1130 LspNotification::Progress(_) | LspNotification::Other { .. } => Route::Floor,
1131 }
1132 }
1133
1134 fn deliver(route: Route<'_>) -> Delivery {
1138 let delivery = |lane, delivered: bool| {
1139 if delivered {
1140 Delivery::Delivered
1141 } else {
1142 Delivery::Dropped(lane)
1143 }
1144 };
1145 match route {
1146 Route::Mailbox(sink, params) => {
1147 debug!(
1148 "Forwarding diagnostics for {}: {} items",
1149 params.uri.as_ref(),
1150 params.diagnostics.len()
1151 );
1152 sink.publishes().publish(params);
1153 Delivery::Delivered
1154 }
1155 Route::Messages(sink, message) => delivery(
1156 Lane::Notification,
1157 sink.messages().try_send(message).is_ok(),
1158 ),
1159 Route::Lifecycle(tx, notification) => {
1160 delivery(Lane::Lifecycle, tx.try_send(notification).is_ok())
1161 }
1162 Route::Floor => Delivery::Delivered,
1163 }
1164 }
1165
1166 #[allow(
1167 clippy::too_many_lines,
1168 clippy::too_many_arguments,
1169 reason = "one select loop over the transport, channels and shared state"
1170 )]
1171 async fn message_loop_inner(
1172 transport: &mut LspTransport,
1173 msg_rx: &mut mpsc::Receiver<Result<InboundMessage>>,
1174 command_rx: &mut mpsc::Receiver<ClientCommand>,
1175 pending_requests: &PendingRequests,
1176 lanes: Option<&Lanes>,
1177 redactions: &Redactions,
1178 settings: Option<&LspSettings>,
1179 ) -> Result<()> {
1180 loop {
1181 tokio::select! {
1182 Some(command) = command_rx.recv() => {
1183 match command {
1184 ClientCommand::SendRequest { request } => {
1185 let value = serde_json::to_value(&request)?;
1186 transport.send(&value).await?;
1187 }
1188 ClientCommand::SendNotification { method, params } => {
1189 let notification = serde_json::to_value(JsonRpcNotification {
1190 jsonrpc: JsonRpcVersion,
1191 method,
1192 params,
1193 })?;
1194 transport.send(¬ification).await?;
1195 }
1196 ClientCommand::Shutdown => {
1197 debug!("Client shutdown requested");
1198 while let Ok(message) = msg_rx.try_recv() {
1202 match message {
1203 Ok(m) => {
1204 Self::handle_inbound_message(
1205 transport,
1206 m,
1207 pending_requests,
1208 lanes,
1209 redactions,
1210 settings,
1211 )
1212 .await?;
1213 }
1214 Err(e) => {
1215 error!("Transport receive error while draining on shutdown: {}", e);
1216 break;
1217 }
1218 }
1219 }
1220 break;
1221 }
1222 }
1223 }
1224
1225 message = msg_rx.recv() => {
1227 let message = match message {
1228 Some(Ok(m)) => m,
1229 Some(Err(e)) => {
1230 error!("Transport receive error: {}", e);
1231 return Err(e);
1232 }
1233 None => {
1234 error!("Transport receive error: reader task ended unexpectedly");
1236 return Err(Error::ServerTerminated);
1237 }
1238 };
1239 Self::handle_inbound_message(
1240 transport,
1241 message,
1242 pending_requests,
1243 lanes,
1244 redactions,
1245 settings,
1246 )
1247 .await?;
1248 }
1249 }
1250 }
1251
1252 Ok(())
1253 }
1254
1255 fn complete_pending(
1257 pending_requests: &PendingRequests,
1258 response: JsonRpcResponse,
1259 redactions: &Redactions,
1260 ) {
1261 trace!("Received response: id={:?}", response.id);
1262
1263 match pending_requests.resolve(&response.id) {
1264 Resolution::Waiting(sender) => match response.outcome {
1265 JsonRpcOutcome::Error(error) => {
1266 let message = redactions.apply(&error.message);
1277 trace!(
1278 "LSP error response: {} (code {})",
1279 Self::truncate_error_message_for_log(&message),
1280 error.code
1281 );
1282 let caller_message =
1288 crate::util::truncate_str(&message, MAX_ERROR_MESSAGE_CALLER_BYTES);
1289 let _ = sender.send(Err(Error::LspServerError(JsonRpcError {
1290 code: error.code,
1291 message: caller_message,
1292 data: error.data,
1293 })));
1294 }
1295 JsonRpcOutcome::Result(result) => {
1296 let _ = sender.send(Ok(result));
1297 }
1298 },
1299 Resolution::Cancelled(cause) => {
1300 trace!(
1301 "Dropped the reply to a request mcpls cancelled ({cause:?}): id={:?}",
1302 response.id
1303 );
1304 }
1305 Resolution::Unknown => {
1306 warn!(
1307 "Received response for unknown request ID: {:?}",
1308 response.id
1309 );
1310 }
1311 }
1312 }
1313
1314 async fn handle_inbound_message(
1320 transport: &mut LspTransport,
1321 message: InboundMessage,
1322 pending_requests: &PendingRequests,
1323 lanes: Option<&Lanes>,
1324 redactions: &Redactions,
1325 settings: Option<&LspSettings>,
1326 ) -> Result<()> {
1327 match message {
1328 InboundMessage::Response(response) => {
1329 Self::complete_pending(pending_requests, response, redactions);
1330 }
1331 InboundMessage::UndecodableResponse { id } => {
1332 warn!("Failed the request answered by an undecodable response: id={id:?}");
1333 if let Resolution::Waiting(sender) = pending_requests.resolve(&id) {
1334 let _ = sender.send(Err(Error::LspProtocolError(RedactedText::fixed(
1335 "undecodable response",
1336 ))));
1337 }
1338 }
1339 InboundMessage::UndecodableRequest { id } => {
1340 debug!("Answering an undecodable server request with an error: id={id:?}");
1341 let reply = JsonRpcReply::new(
1342 id,
1343 JsonRpcOutcome::Error(JsonRpcError::new(
1344 ErrorCodes::InvalidRequest,
1345 "Invalid Request: the request could not be decoded",
1346 )),
1347 );
1348 transport.send(&serde_json::to_value(&reply)?).await?;
1349 }
1350 InboundMessage::UndecodableNotification | InboundMessage::UndecodableFrame => {}
1351 InboundMessage::Request(request) => {
1352 debug!(
1353 "Received server request: {} (id={:?})",
1354 request.method, request.id
1355 );
1356 let reply = Self::server_request_reply(request, settings);
1357 let value = serde_json::to_value(&reply)?;
1358 transport.send(&value).await?;
1359 }
1360 InboundMessage::Notification(notification) => {
1361 debug!("Received notification: {}", notification.method);
1362
1363 let mut typed = LspNotification::parse(¬ification.method, notification.params);
1365 Self::redact_notification(&mut typed, redactions);
1366
1367 let route = Self::route(typed, lanes);
1368 if tracing::enabled!(tracing::Level::TRACE) {
1369 let forwarded: Option<&dyn std::fmt::Debug> = match &route {
1370 Route::Messages(_, message) => Some(message),
1371 Route::Lifecycle(_, notification) => Some(notification),
1372 Route::Mailbox(..) | Route::Floor => None,
1373 };
1374 if let Some(payload) = forwarded {
1375 trace!(
1376 "Forwarding notification: {}",
1377 redactions.apply(&format!("{payload:?}"))
1378 );
1379 }
1380 }
1381
1382 if let Delivery::Dropped(lane) = Self::deliver(route) {
1383 DropLog::of(lane).record(¬ification.method);
1384 }
1385 }
1386 }
1387
1388 Ok(())
1389 }
1390
1391 pub(crate) fn redact_notification(notification: &mut LspNotification, redactions: &Redactions) {
1395 if redactions.is_empty() {
1396 return;
1397 }
1398 match notification {
1399 LspNotification::LogMessage(params) => {
1400 redactions.redact_in_place(&mut params.message);
1401 }
1402 LspNotification::ShowMessage(params) => {
1403 redactions.redact_in_place(&mut params.message);
1404 }
1405 LspNotification::PublishDiagnostics(params) => {
1406 for diagnostic in &mut params.diagnostics {
1407 redactions.redact_diagnostic(diagnostic);
1408 }
1409 }
1410 LspNotification::Progress(params) => redactions.redact_progress(&mut params.value),
1411 LspNotification::Other { .. } => {}
1412 }
1413 }
1414
1415 fn server_request_reply(
1416 request: JsonRpcRequest,
1417 settings: Option<&LspSettings>,
1418 ) -> JsonRpcReply {
1419 let outcome =
1420 Self::server_request_result(&request.method, request.params.as_ref(), settings);
1421 JsonRpcReply::new(request.id, outcome.into())
1422 }
1423
1424 fn server_request_result(
1425 method: &str,
1426 params: Option<&Value>,
1427 settings: Option<&LspSettings>,
1428 ) -> std::result::Result<Value, JsonRpcError> {
1429 match LspRequestMethod::from(method) {
1430 LspRequestMethod::ClientRegisterCapability
1431 | LspRequestMethod::ClientUnregisterCapability
1432 | LspRequestMethod::WorkspaceWorkspaceFolders
1433 | LspRequestMethod::WorkspaceDiagnosticRefresh
1434 | LspRequestMethod::WorkspaceSemanticTokensRefresh
1435 | LspRequestMethod::WorkspaceInlayHintRefresh
1436 | LspRequestMethod::WorkspaceCodeLensRefresh
1437 | LspRequestMethod::WindowShowMessageRequest
1438 | LspRequestMethod::WindowWorkDoneProgressCreate => Ok(Value::Null),
1439 LspRequestMethod::WorkspaceConfiguration => {
1440 Self::workspace_configuration_result(params, settings)
1441 }
1442 LspRequestMethod::WorkspaceApplyEdit => Ok(serde_json::json!({ "applied": false })),
1443 _ => Err(JsonRpcError::new(
1444 ErrorCodes::MethodNotFound,
1445 format!("Unhandled server request: {method}"),
1446 )),
1447 }
1448 }
1449
1450 fn workspace_configuration_result(
1459 params: Option<&Value>,
1460 settings: Option<&LspSettings>,
1461 ) -> std::result::Result<Value, JsonRpcError> {
1462 let items = params
1463 .and_then(|params| params.get("items"))
1464 .and_then(Value::as_array);
1465 let Some(settings) = settings else {
1466 return Ok(Value::Array(vec![Value::Null; items.map_or(0, Vec::len)]));
1467 };
1468 let items = items.ok_or_else(|| {
1469 JsonRpcError::new(
1470 ErrorCodes::InvalidParams,
1471 "Invalid params for workspace/configuration",
1472 )
1473 })?;
1474 let sections = items
1475 .iter()
1476 .map(|item| match ConfigurationItem::parse(item) {
1477 ConfigurationItem::Whole => settings.section(None),
1478 ConfigurationItem::Section(section) => settings.section(Some(section)),
1479 ConfigurationItem::Uninterpretable => Value::Null,
1480 })
1481 .collect();
1482 Ok(Value::Array(sections))
1483 }
1484}
1485
1486fn log_surfaced_error(method: &str, id: &RequestId, code: i32, message: &str) {
1489 error!(
1490 "LSP error response: {} (code {}) on '{}' (id={:?})",
1491 LspClient::truncate_error_message_for_log(message),
1492 code,
1493 method,
1494 id
1495 );
1496}
1497
1498#[derive(Debug)]
1505#[must_use = "call `surface()` or `handled()` to log and unwrap the error"]
1506pub struct UnclassifiedError(Box<Unclassified>);
1507
1508#[derive(Debug)]
1511enum Unclassified {
1512 Logged(Error),
1513 ServerResponse {
1514 error: JsonRpcError,
1515 method: String,
1516 id: RequestId,
1517 },
1518}
1519
1520impl Unclassified {
1521 fn finish(self, report: impl FnOnce(&str, &RequestId, i32, &str)) -> Error {
1524 match self {
1525 Self::Logged(error) => error,
1526 Self::ServerResponse { error, method, id } => {
1527 report(&method, &id, error.code, &error.message);
1528 Error::LspServerError(error)
1529 }
1530 }
1531 }
1532}
1533
1534fn log_handled_error(method: &str, id: &RequestId, code: i32, message: &str) {
1535 debug!(
1536 "LSP error response handled by the caller: {} (code {}) on '{}' (id={:?})",
1537 LspClient::truncate_error_message_for_log(message),
1538 code,
1539 method,
1540 id
1541 );
1542}
1543
1544impl UnclassifiedError {
1545 fn logged(error: Error) -> Self {
1546 Self(Box::new(Unclassified::Logged(error)))
1547 }
1548
1549 fn server_response(response: JsonRpcError, method: &str, id: RequestId) -> Self {
1550 Self(Box::new(Unclassified::ServerResponse {
1551 error: response,
1552 method: method.to_owned(),
1553 id,
1554 }))
1555 }
1556
1557 fn take(mut self) -> Unclassified {
1560 std::mem::replace(&mut *self.0, Unclassified::Logged(Error::ServerTerminated))
1561 }
1562
1563 pub(crate) fn server_error(&self) -> Option<&JsonRpcError> {
1565 match &*self.0 {
1566 Unclassified::Logged(Error::LspServerError(response))
1567 | Unclassified::ServerResponse {
1568 error: response, ..
1569 } => Some(response),
1570 Unclassified::Logged(_) => None,
1571 }
1572 }
1573
1574 pub(crate) fn timed_out(&self) -> bool {
1576 matches!(&*self.0, Unclassified::Logged(Error::Timeout(_)))
1577 }
1578
1579 pub(crate) fn surface(self) -> Error {
1582 self.take().finish(log_surfaced_error)
1583 }
1584
1585 pub(crate) fn handled(self) -> Error {
1588 self.take().finish(log_handled_error)
1589 }
1590}
1591
1592impl Drop for UnclassifiedError {
1595 fn drop(&mut self) {
1596 let inner = std::mem::replace(&mut *self.0, Unclassified::Logged(Error::ServerTerminated));
1597 drop(inner.finish(log_surfaced_error));
1598 }
1599}
1600
1601enum ConfigurationItem<'a> {
1603 Whole,
1604 Section(&'a str),
1605 Uninterpretable,
1606}
1607
1608impl<'a> ConfigurationItem<'a> {
1609 fn parse(item: &'a Value) -> Self {
1610 let Value::Object(object) = item else {
1611 return Self::Uninterpretable;
1612 };
1613 match object.get("section") {
1614 None | Some(Value::Null) => Self::Whole,
1615 Some(Value::String(section)) => Self::Section(section),
1616 Some(_) => Self::Uninterpretable,
1617 }
1618 }
1619}
1620
1621#[cfg(test)]
1622mod tests {
1623 use std::assert_matches;
1624
1625 use super::*;
1626 use crate::config::TimeoutSecs;
1627 use crate::lsp::types::JsonRpcResponse;
1628
1629 #[test]
1630 fn test_unclassified_server_response_always_reports_code_and_message() {
1631 let unclassified = UnclassifiedError::server_response(
1632 JsonRpcError {
1633 code: -32001,
1634 message: "boom".to_owned(),
1635 data: None,
1636 },
1637 "textDocument/hover",
1638 RequestId::Number(3),
1639 );
1640 let mut reported = None;
1641
1642 let error = unclassified.take().finish(|method, id, code, message| {
1643 reported = Some((method.to_owned(), id.clone(), code, message.to_owned()));
1644 });
1645
1646 assert_eq!(
1647 reported,
1648 Some((
1649 "textDocument/hover".to_owned(),
1650 RequestId::Number(3),
1651 -32001,
1652 "boom".to_owned()
1653 ))
1654 );
1655 assert_matches!(
1656 error,
1657 Error::LspServerError(JsonRpcError { code: -32001, .. })
1658 );
1659 }
1660
1661 #[tokio::test]
1664 async fn test_forwarded_notification_trace_is_redacted() {
1665 use tracing_subscriber::prelude::*;
1666
1667 use crate::test_lsp::CapturedLogs;
1668
1669 let redactions =
1670 Redactions::new([("API_TOKEN".to_owned(), "SuperSecretValue123".to_owned())]);
1671 let logs = CapturedLogs::default();
1672 let _guard = tracing::subscriber::set_default(
1673 tracing_subscriber::registry()
1674 .with(tracing_subscriber::filter::LevelFilter::TRACE)
1675 .with(logs.clone()),
1676 );
1677 let (mut transport, _reader) = LspTransport::new(tokio::io::sink(), tokio::io::empty());
1678 let (lanes, mut lifecycle_rx) = open_lanes();
1679 let pending = PendingRequests::default();
1680 let notification = InboundMessage::Notification(JsonRpcNotification {
1681 jsonrpc: JsonRpcVersion,
1682 method: "custom/echo".to_string(),
1683 params: Some(serde_json::json!({"env": "SuperSecretValue123"})),
1684 });
1685
1686 LspClient::handle_inbound_message(
1687 &mut transport,
1688 notification,
1689 &pending,
1690 Some(&lanes),
1691 &redactions,
1692 None,
1693 )
1694 .await
1695 .unwrap();
1696
1697 assert!(lifecycle_rx.recv().await.is_some());
1698 let output = logs.messages().join("\n");
1699 assert!(output.contains("Forwarding notification"), "{output}");
1700 assert!(!output.contains("SuperSecretValue123"), "{output}");
1701 }
1702
1703 fn secret_diagnostics_params() -> Value {
1704 let range = serde_json::json!({
1705 "start": {"line": 0, "character": 0},
1706 "end": {"line": 0, "character": 1}
1707 });
1708 serde_json::json!({
1709 "uri": "file:///w/main.rs",
1710 "diagnostics": [{
1711 "range": range,
1712 "message": "leak SuperSecretValue123",
1713 "data": {"hint": ["SuperSecretValue123"]}
1714 }]
1715 })
1716 }
1717
1718 #[tokio::test]
1721 async fn test_reader_task_forwards_pushed_diagnostics_redacted() {
1722 use crate::test_lsp::{fake_lsp_client_with_redactions, write_notification};
1723
1724 let redactions =
1725 Redactions::new([("API_TOKEN".to_owned(), "SuperSecretValue123".to_owned())]);
1726 let (_client, mut server, mut lanes) = fake_lsp_client_with_redactions(redactions);
1727
1728 write_notification(
1729 &mut server.read_half_stdin,
1730 "textDocument/publishDiagnostics",
1731 secret_diagnostics_params(),
1732 )
1733 .await;
1734
1735 let forwarded = timeout(Duration::from_secs(2), lanes.publishes.recv())
1736 .await
1737 .unwrap()
1738 .unwrap();
1739 let shown = format!("{forwarded:?}");
1740 assert!(shown.contains("[redacted:API_TOKEN]"), "{shown}");
1741 assert!(!shown.contains("SuperSecretValue123"), "{shown}");
1742 }
1743
1744 #[test]
1746 fn test_redact_notification_leaves_text_alone_without_secrets() {
1747 let mut notification = LspNotification::parse(
1748 "textDocument/publishDiagnostics",
1749 Some(secret_diagnostics_params()),
1750 );
1751 let before = format!("{notification:?}");
1752
1753 LspClient::redact_notification(&mut notification, &Redactions::default());
1754 assert_eq!(format!("{notification:?}"), before);
1755
1756 let unrelated = Redactions::new([("API_TOKEN".to_owned(), "NotInTheText99".to_owned())]);
1757 LspClient::redact_notification(&mut notification, &unrelated);
1758 assert_eq!(format!("{notification:?}"), before);
1759
1760 let matching =
1761 Redactions::new([("API_TOKEN".to_owned(), "SuperSecretValue123".to_owned())]);
1762 LspClient::redact_notification(&mut notification, &matching);
1763 assert!(!format!("{notification:?}").contains("SuperSecretValue123"));
1764 }
1765
1766 #[test]
1767 fn test_request_id_generation() {
1768 let counter = AtomicI64::new(1);
1769
1770 let id1 = counter.fetch_add(1, Ordering::SeqCst);
1771 let id2 = counter.fetch_add(1, Ordering::SeqCst);
1772 let id3 = counter.fetch_add(1, Ordering::SeqCst);
1773
1774 assert_eq!(id1, 1);
1775 assert_eq!(id2, 2);
1776 assert_eq!(id3, 3);
1777 }
1778
1779 #[test]
1780 fn test_client_creation() {
1781 let config = LspServerConfig::rust_analyzer();
1782
1783 let client = LspClient::new(config);
1784 assert_eq!(client.language_id(), "rust");
1785 }
1786
1787 #[test]
1788 fn test_client_clone() {
1789 let config = LspServerConfig::rust_analyzer();
1790 let client = LspClient::new(config);
1791
1792 #[allow(clippy::redundant_clone, reason = "the test exercises `Clone` itself")]
1793 let cloned = client.clone();
1794 assert_eq!(cloned.language_id(), "rust");
1795
1796 assert!(
1797 cloned.receiver_task.is_none(),
1798 "Cloned client should not own receiver task"
1799 );
1800 }
1801
1802 #[test]
1803 fn test_request_timeout_and_completion_timeout_at_default() {
1804 let config = LspServerConfig::rust_analyzer();
1805 let client = LspClient::new(config);
1806
1807 assert_eq!(client.request_timeout(), Duration::from_secs(30));
1808 assert_eq!(client.completion_timeout(), Duration::from_secs(10));
1809 }
1810
1811 #[test]
1812 fn test_completion_timeout_clamps_to_ten_seconds() {
1813 for secs in [1, 2, 3, 30, 300] {
1814 let mut config = LspServerConfig::rust_analyzer();
1815 config.request_timeout_seconds = TimeoutSecs::new(secs).unwrap();
1816 let client = LspClient::new(config);
1817
1818 assert_eq!(
1819 client.completion_timeout(),
1820 Duration::from_secs(secs.min(10)),
1821 "request_timeout_seconds={secs}"
1822 );
1823 assert!(client.completion_timeout() <= client.request_timeout());
1824 }
1825 }
1826
1827 #[test]
1828 fn test_code_action_resolve_timeout_clamps_to_ten_seconds() {
1829 for secs in [1, 2, 3, 30, 300] {
1830 let mut config = LspServerConfig::rust_analyzer();
1831 config.request_timeout_seconds = TimeoutSecs::new(secs).unwrap();
1832 let client = LspClient::new(config);
1833
1834 assert_eq!(
1835 client.code_action_resolve_timeout(),
1836 Duration::from_secs(secs.min(10)),
1837 "request_timeout_seconds={secs}"
1838 );
1839 assert!(client.code_action_resolve_timeout() <= client.request_timeout());
1840 }
1841 }
1842
1843 #[test]
1844 fn test_request_timeout_independent_per_server() {
1845 let mut config_a = LspServerConfig::rust_analyzer();
1846 config_a.request_timeout_seconds = TimeoutSecs::new(5).unwrap();
1847 let mut config_b = LspServerConfig::pyright();
1848 config_b.request_timeout_seconds = TimeoutSecs::new(15).unwrap();
1849
1850 let client_a = LspClient::new(config_a);
1851 let client_b = LspClient::new(config_b);
1852
1853 assert_eq!(client_a.request_timeout(), Duration::from_secs(5));
1854 assert_eq!(client_b.request_timeout(), Duration::from_secs(15));
1855 }
1856
1857 #[test]
1858 fn test_register_capability_request_is_acknowledged() {
1859 let request = JsonRpcRequest {
1860 jsonrpc: JsonRpcVersion,
1861 id: RequestId::String("ts1".to_string()),
1862 method: "client/registerCapability".to_string(),
1863 params: Some(serde_json::json!({ "registrations": [] })),
1864 };
1865
1866 let reply = reply_json(request);
1867
1868 assert_eq!(reply["id"], serde_json::json!("ts1"));
1869 assert_eq!(reply["jsonrpc"], serde_json::json!("2.0"));
1870 assert_eq!(reply["result"], Value::Null);
1871 assert!(reply.get("error").is_none());
1872 }
1873
1874 #[test]
1875 fn test_workspace_configuration_request_returns_null_per_item_without_settings() {
1876 let result = LspClient::workspace_configuration_result(
1877 Some(&serde_json::json!({
1878 "items": [{ "section": "typescript" }, { "section": "editor" }]
1879 })),
1880 None,
1881 );
1882
1883 assert_eq!(result.unwrap(), serde_json::json!([null, null]));
1884 }
1885
1886 fn sample_settings() -> LspSettings {
1887 serde_json::from_value(serde_json::json!({
1888 "python.analysis.typeCheckingMode": "strict",
1889 "gopls": { "ui.semanticTokens": true }
1890 }))
1891 .unwrap()
1892 }
1893
1894 #[test]
1895 fn test_workspace_configuration_serves_settings_sections() {
1896 let settings = sample_settings();
1897 let result = LspClient::workspace_configuration_result(
1898 Some(&serde_json::json!({
1899 "items": [
1900 { "section": "python.analysis" },
1901 { "section": "gopls", "scopeUri": "file:///ignored" },
1902 { "section": "missing" },
1903 {}
1904 ]
1905 })),
1906 Some(&settings),
1907 )
1908 .unwrap();
1909
1910 assert_eq!(
1911 result,
1912 serde_json::json!([
1913 { "typeCheckingMode": "strict" },
1914 { "ui.semanticTokens": true },
1915 null,
1916 settings.to_value()
1917 ])
1918 );
1919 }
1920
1921 #[test]
1922 fn test_workspace_configuration_non_object_item_gets_null() {
1923 let settings = sample_settings();
1924 let result = LspClient::workspace_configuration_result(
1925 Some(&serde_json::json!({
1926 "items": [
1927 { "section": "python.analysis" },
1928 { "section": "missing.x" },
1929 {},
1930 { "section": 5 },
1931 "x",
1932 null,
1933 7
1934 ]
1935 })),
1936 Some(&settings),
1937 )
1938 .unwrap();
1939
1940 assert_eq!(
1941 result,
1942 serde_json::json!([
1943 { "typeCheckingMode": "strict" },
1944 null,
1945 settings.to_value(),
1946 null,
1947 null,
1948 null,
1949 null
1950 ])
1951 );
1952 }
1953
1954 #[tokio::test]
1955 async fn test_message_loop_answers_workspace_configuration_from_settings() {
1956 use tokio::io::BufReader;
1957
1958 use crate::test_lsp::{fake_lsp_client_with_config, read_framed_message, write_request};
1959
1960 let mut config = LspServerConfig::rust_analyzer();
1961 config.settings = Some(sample_settings());
1962 let (_client, mut server) = fake_lsp_client_with_config(config);
1963
1964 write_request(
1965 &mut server.read_half_stdin,
1966 &serde_json::json!(7),
1967 "workspace/configuration",
1968 serde_json::json!({ "items": [{ "section": "python.analysis", "scopeUri": 1 }] }),
1969 )
1970 .await;
1971 let mut reader = BufReader::new(&mut server.write_stdout);
1972 let response = read_framed_message(&mut reader).await;
1973
1974 assert_eq!(response["id"], 7);
1975 assert_eq!(
1976 response["result"],
1977 serde_json::json!([{ "typeCheckingMode": "strict" }])
1978 );
1979 }
1980
1981 #[test]
1982 fn test_workspace_configuration_is_lenient_about_item_shape() {
1983 let settings = sample_settings();
1984 let params = serde_json::json!({
1985 "items": [
1986 { "section": 5 },
1987 { "section": null },
1988 3,
1989 { "section": "gopls", "scopeUri": 42 }
1990 ]
1991 });
1992
1993 let with = LspClient::workspace_configuration_result(Some(¶ms), Some(&settings));
1994 let without = LspClient::workspace_configuration_result(Some(¶ms), None);
1995
1996 assert_eq!(
1997 with.unwrap(),
1998 serde_json::json!([
1999 null,
2000 settings.to_value(),
2001 null,
2002 { "ui.semanticTokens": true }
2003 ])
2004 );
2005 assert_eq!(
2006 without.unwrap(),
2007 serde_json::json!([null, null, null, null])
2008 );
2009 }
2010
2011 #[test]
2012 fn test_workspace_configuration_without_settings_matches_the_old_reply_for_bad_params() {
2013 for params in [
2014 None,
2015 Some(serde_json::json!({})),
2016 Some(serde_json::json!({ "items": "x" })),
2017 ] {
2018 let result = LspClient::workspace_configuration_result(params.as_ref(), None);
2019 assert_eq!(result.unwrap(), serde_json::json!([]));
2020 }
2021 }
2022
2023 #[test]
2024 fn test_workspace_configuration_with_settings_rejects_missing_items() {
2025 for params in [None, Some(serde_json::json!({ "items": "nope" }))] {
2026 let error = LspClient::workspace_configuration_result(
2027 params.as_ref(),
2028 Some(&sample_settings()),
2029 )
2030 .unwrap_err();
2031 assert_eq!(error.code, -32602);
2032 }
2033 }
2034
2035 #[test]
2040 fn test_work_done_progress_create_request_is_acknowledged() {
2041 let request = JsonRpcRequest {
2042 jsonrpc: JsonRpcVersion,
2043 id: RequestId::String("wdp1".to_string()),
2044 method: "window/workDoneProgress/create".to_string(),
2045 params: Some(serde_json::json!({ "token": "indexing" })),
2046 };
2047
2048 let reply = reply_json(request);
2049
2050 assert_eq!(reply["result"], Value::Null);
2051 assert!(reply.get("error").is_none());
2052 }
2053
2054 fn progress(value: serde_json::Value) -> LspNotification {
2055 LspNotification::Progress(lsp_types::ProgressParams {
2056 token: lsp_types::ProgressToken::Int(1),
2057 value,
2058 })
2059 }
2060
2061 fn other(method: &'static str) -> LspNotification {
2062 LspNotification::Other {
2063 method: std::borrow::Cow::Borrowed(method),
2064 params: None,
2065 }
2066 }
2067
2068 fn open_lanes() -> (Lanes, mpsc::Receiver<LspNotification>) {
2069 let (messages_tx, _messages_rx) = mpsc::channel(1);
2070 let (notifications, _reader) = NotificationSink::new(messages_tx);
2071 let (lifecycle, lifecycle_rx) = mpsc::channel(4);
2072 (
2073 Lanes {
2074 notifications,
2075 lifecycle,
2076 },
2077 lifecycle_rx,
2078 )
2079 }
2080
2081 #[test]
2086 fn test_report_progress_frame_goes_to_the_floor() {
2087 let report = progress(serde_json::json!({ "kind": "report", "percentage": 50 }));
2088
2089 let (lanes, _rx) = open_lanes();
2090
2091 assert_matches!(LspClient::route(report, Some(&lanes)), Route::Floor);
2092 }
2093
2094 #[test]
2101 fn test_malformed_progress_other_goes_to_the_floor() {
2102 let (lanes, _rx) = open_lanes();
2103
2104 assert_matches!(
2105 LspClient::route(other("$/progress"), Some(&lanes)),
2106 Route::Floor
2107 );
2108 }
2109
2110 #[test]
2115 fn test_begin_and_other_notifications_reach_lifecycle_lane() {
2116 let begin = progress(serde_json::json!({ "kind": "begin", "title": "Indexing" }));
2117 let end = progress(serde_json::json!({ "kind": "end" }));
2118
2119 let (lanes, _rx) = open_lanes();
2120
2121 for notification in [begin, end, other("experimental/serverStatus")] {
2122 assert_matches!(
2123 LspClient::route(notification, Some(&lanes)),
2124 Route::Lifecycle(..)
2125 );
2126 }
2127 }
2128
2129 #[test]
2130 fn test_route_covers_every_notification_shape() {
2131 let publish = || {
2132 LspNotification::PublishDiagnostics(PublishDiagnosticsParams {
2133 uri: lsp_types::Uri::from("file:///a.rs".to_owned()),
2134 diagnostics: Vec::new(),
2135 version: None,
2136 })
2137 };
2138 let log = || {
2139 LspNotification::LogMessage(lsp_types::LogMessageParams {
2140 kind: lsp_types::MessageType::Info,
2141 message: "m".to_owned(),
2142 })
2143 };
2144 let show = || {
2145 LspNotification::ShowMessage(lsp_types::ShowMessageParams {
2146 kind: lsp_types::MessageType::Info,
2147 message: "m".to_owned(),
2148 })
2149 };
2150
2151 let (lanes, _rx) = open_lanes();
2152
2153 assert_matches!(
2154 LspClient::route(publish(), Some(&lanes)),
2155 Route::Mailbox(..)
2156 );
2157 assert_matches!(
2158 LspClient::route(log(), Some(&lanes)),
2159 Route::Messages(_, ServerMessage::Log(_))
2160 );
2161 assert_matches!(
2162 LspClient::route(show(), Some(&lanes)),
2163 Route::Messages(_, ServerMessage::Show(_))
2164 );
2165 for notification in [
2166 progress(serde_json::json!({ "kind": "begin" })),
2167 other("experimental/serverStatus"),
2168 ] {
2169 assert_matches!(
2170 LspClient::route(notification, Some(&lanes)),
2171 Route::Lifecycle(..)
2172 );
2173 }
2174 for notification in [publish(), log(), show(), other("custom/x")] {
2175 assert_matches!(LspClient::route(notification, None), Route::Floor);
2176 }
2177 }
2178
2179 #[test]
2180 fn test_deliver_reports_the_lane_that_dropped_a_frame() {
2181 let (lifecycle_tx, lifecycle_rx) = mpsc::channel(1);
2182 drop(lifecycle_rx);
2183
2184 let delivery = LspClient::deliver(Route::Lifecycle(
2185 &lifecycle_tx,
2186 other("experimental/serverStatus"),
2187 ));
2188
2189 assert_eq!(delivery, Delivery::Dropped(Lane::Lifecycle));
2190 assert_eq!(LspClient::deliver(Route::Floor), Delivery::Delivered);
2191 }
2192
2193 #[test]
2194 fn test_unknown_server_request_returns_method_not_found() {
2195 let request = JsonRpcRequest {
2196 jsonrpc: JsonRpcVersion,
2197 id: RequestId::String("unknown-1".to_string()),
2198 method: "custom/request".to_string(),
2199 params: None,
2200 };
2201
2202 let reply = reply_json(request);
2203
2204 assert!(reply.get("result").is_none());
2205 assert_eq!(reply["error"]["code"], serde_json::json!(-32601));
2206 assert_eq!(
2207 reply["error"]["message"],
2208 serde_json::json!("Unhandled server request: custom/request")
2209 );
2210 }
2211
2212 fn reply_json(request: JsonRpcRequest) -> Value {
2213 serde_json::to_value(LspClient::server_request_reply(request, None)).unwrap()
2214 }
2215
2216 #[tokio::test]
2217 async fn test_null_response_handling() {
2218 use crate::lsp::types::{JsonRpcResponse, RequestId};
2219
2220 let pending_requests = PendingRequests::default();
2221
2222 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
2223
2224 pending_requests.register(RequestId::Number(1), response_tx);
2225
2226 let null_response = JsonRpcResponse {
2227 jsonrpc: JsonRpcVersion,
2228 id: RequestId::Number(1),
2229 outcome: JsonRpcOutcome::Result(Value::Null),
2230 };
2231
2232 let Resolution::Waiting(sender) = pending_requests.resolve(&null_response.id) else {
2233 panic!("the request must be waiting");
2234 };
2235 let _ = sender.send(Ok(Value::Null));
2236
2237 let timeout_result =
2238 tokio::time::timeout(tokio::time::Duration::from_millis(100), response_rx).await;
2239
2240 assert!(timeout_result.is_ok(), "Should not timeout");
2241
2242 let channel_result = timeout_result.unwrap();
2243 assert!(
2244 channel_result.is_ok(),
2245 "Channel should not be closed: {:?}",
2246 channel_result.err()
2247 );
2248
2249 let response = channel_result.unwrap();
2250 assert!(
2251 response.is_ok(),
2252 "Should receive Ok(Value::Null), not Err: {:?}",
2253 response.err()
2254 );
2255
2256 let value = response.unwrap();
2257 assert_eq!(value, Value::Null, "Should receive Value::Null");
2258 }
2259
2260 #[tokio::test]
2261 async fn test_error_response_handling() {
2262 use crate::lsp::types::{JsonRpcError, JsonRpcResponse, RequestId};
2263
2264 let pending_requests = PendingRequests::default();
2265 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
2266
2267 pending_requests.register(RequestId::Number(1), response_tx);
2268
2269 let error_response = JsonRpcResponse {
2270 jsonrpc: JsonRpcVersion,
2271 id: RequestId::Number(1),
2272 outcome: JsonRpcOutcome::Error(JsonRpcError {
2273 code: -32601,
2274 message: "Method not found".to_string(),
2275 data: None,
2276 }),
2277 };
2278
2279 if let Resolution::Waiting(sender) = pending_requests.resolve(&error_response.id)
2280 && let JsonRpcOutcome::Error(error) = error_response.outcome
2281 {
2282 let _ = sender.send(Err(Error::LspServerError(error)));
2283 }
2284
2285 let result = response_rx.await.unwrap();
2286 assert!(result.is_err(), "Should receive error");
2287
2288 if let Err(Error::LspServerError(JsonRpcError { code, message, .. })) = result {
2289 assert_eq!(code, -32601);
2290 assert_eq!(message, "Method not found");
2291 } else {
2292 panic!("Expected LspServerError");
2293 }
2294 }
2295
2296 #[tokio::test]
2297 async fn test_unknown_request_id() {
2298 use crate::lsp::types::{JsonRpcResponse, RequestId};
2299
2300 let pending_requests = PendingRequests::default();
2301
2302 let response = JsonRpcResponse {
2303 jsonrpc: JsonRpcVersion,
2304 id: RequestId::Number(999),
2305 outcome: JsonRpcOutcome::Result(Value::Null),
2306 };
2307
2308 assert_matches!(
2309 pending_requests.resolve(&response.id),
2310 Resolution::Unknown,
2311 "Should not find sender for unknown ID"
2312 );
2313 }
2314
2315 #[test]
2316 fn test_truncate_error_message_for_log_handles_multibyte_boundary() {
2317 let message = format!("{}€{}", "x".repeat(199), "y".repeat(50));
2319
2320 let truncated = LspClient::truncate_error_message_for_log(&message);
2321
2322 assert_eq!(truncated, format!("{}... (truncated)", "x".repeat(199)));
2325 }
2326
2327 #[test]
2328 fn test_truncate_error_message_for_log_no_truncation_at_or_below_limit() {
2329 let exact = "x".repeat(200);
2330 assert_eq!(LspClient::truncate_error_message_for_log(&exact), exact);
2331 assert_eq!(LspClient::truncate_error_message_for_log(""), "");
2332 }
2333
2334 #[test]
2335 fn test_truncate_error_message_for_log_truncates_just_above_limit() {
2336 let message = "x".repeat(201);
2337 assert_eq!(
2338 LspClient::truncate_error_message_for_log(&message),
2339 format!("{}... (truncated)", "x".repeat(200))
2340 );
2341 }
2342
2343 #[test]
2344 fn test_truncate_error_message_for_log_handles_wide_char_at_limit() {
2345 let message = format!("{}{}", "x".repeat(197), "🦀".repeat(10));
2347
2348 let truncated = LspClient::truncate_error_message_for_log(&message);
2349
2350 assert_eq!(truncated, format!("{}... (truncated)", "x".repeat(197)));
2351 }
2352
2353 #[tokio::test]
2354 async fn test_concurrent_request_ids() {
2355 let counter = Arc::new(AtomicI64::new(1));
2356
2357 let counter1 = Arc::clone(&counter);
2358 let counter2 = Arc::clone(&counter);
2359 let counter3 = Arc::clone(&counter);
2360
2361 let handles = vec![
2362 tokio::spawn(async move { counter1.fetch_add(1, Ordering::SeqCst) }),
2363 tokio::spawn(async move { counter2.fetch_add(1, Ordering::SeqCst) }),
2364 tokio::spawn(async move { counter3.fetch_add(1, Ordering::SeqCst) }),
2365 ];
2366
2367 let mut ids = Vec::new();
2368 for handle in handles {
2369 ids.push(handle.await.unwrap());
2370 }
2371
2372 ids.sort_unstable();
2373 assert_eq!(ids, vec![1, 2, 3], "IDs should be unique and sequential");
2374 }
2375
2376 #[test]
2377 fn test_jsonrpc_version_constant() {
2378 assert_eq!(
2379 serde_json::to_value(JsonRpcVersion).unwrap(),
2380 serde_json::json!("2.0")
2381 );
2382 }
2383
2384 #[cfg(unix)]
2393 #[tokio::test]
2394 async fn test_request_timeout_removes_pending_entry() {
2395 let mut child = tokio::process::Command::new("sleep")
2396 .arg("2")
2397 .stdin(std::process::Stdio::piped())
2398 .stdout(std::process::Stdio::piped())
2399 .kill_on_drop(true)
2400 .spawn()
2401 .unwrap();
2402 let stdin = child.stdin.take().unwrap();
2403 let stdout = child.stdout.take().unwrap();
2404
2405 let transport = LspTransport::new(stdin, stdout);
2406 let client = LspClient::from_transport(LspServerConfig::rust_analyzer(), transport);
2407
2408 let result: Result<Value> = client
2409 .request(
2410 "textDocument/hover",
2411 serde_json::json!({}),
2412 Duration::from_millis(50),
2413 )
2414 .await;
2415
2416 assert_matches!(result, Err(Error::Timeout(_)), "got {result:?}");
2417 assert!(
2418 client.pending_requests.no_waiting(),
2419 "timed-out request must not remain in pending_requests"
2420 );
2421 }
2422
2423 #[tokio::test]
2427 async fn test_fail_pending_requests_resolves_all_as_server_terminated() {
2428 let pending_requests = PendingRequests::default();
2429 let (command_tx, _command_rx) = mpsc::channel(1);
2430
2431 let client = LspClient {
2432 config: LspServerConfig::rust_analyzer(),
2433 state: Arc::new(Mutex::new(super::super::ServerState::Ready)),
2434 request_counter: Arc::new(AtomicI64::new(1)),
2435 command_tx,
2436 pending_requests: pending_requests.clone(),
2437 closing: Arc::default(),
2438 pending_failure: Arc::default(),
2439 receiver_task: None,
2440 redactions: Arc::default(),
2441 connection_id: ConnectionId::next(),
2442 };
2443
2444 let (tx1, rx1) = oneshot::channel::<Result<Value>>();
2445 let (tx2, rx2) = oneshot::channel::<Result<Value>>();
2446 pending_requests.register(RequestId::Number(1), tx1);
2447 pending_requests.register(RequestId::Number(2), tx2);
2448
2449 client.fail_pending_requests();
2450
2451 assert!(pending_requests.no_waiting());
2452 assert_matches!(rx1.await.unwrap(), Err(Error::ServerTerminated));
2453 assert_matches!(rx2.await.unwrap(), Err(Error::ServerTerminated));
2454 }
2455
2456 mod timeout_cancellation {
2461 use tokio::io::BufReader;
2462 use tokio::task::JoinHandle;
2463
2464 use super::*;
2465 use crate::test_lsp::{fake_lsp_client, read_framed_message, write_response};
2466
2467 const TIMEOUT: Duration = Duration::from_secs(5);
2468
2469 fn spawn_hover(client: &LspClient) -> JoinHandle<Result<Value>> {
2470 let client = client.clone();
2471 tokio::spawn(async move {
2472 client
2473 .request("textDocument/hover", serde_json::json!({}), TIMEOUT)
2474 .await
2475 })
2476 }
2477
2478 #[tokio::test(start_paused = true)]
2479 async fn test_timeout_while_awaiting_reply_clears_pending_and_discards_late_reply() {
2480 let (client, mut server) = fake_lsp_client();
2481 let mut reader = BufReader::new(&mut server.write_stdout);
2482
2483 let first = spawn_hover(&client);
2484 let first_request = read_framed_message(&mut reader).await;
2485
2486 tokio::time::advance(TIMEOUT + Duration::from_secs(1)).await;
2487 assert_matches!(
2488 first.await.unwrap(),
2489 Err(Error::Timeout(limit)) if limit == TIMEOUT,
2490 "request awaiting its reply must time out"
2491 );
2492 assert!(
2493 client.pending_requests.no_waiting(),
2494 "timed-out request must not remain in pending_requests"
2495 );
2496 let cancel = read_framed_message(&mut reader).await;
2497 assert_eq!(cancel["method"], "$/cancelRequest");
2498 assert_eq!(cancel["params"]["id"], first_request["id"]);
2499
2500 write_response(
2501 &mut server.read_half_stdin,
2502 &first_request["id"],
2503 serde_json::json!("stale"),
2504 )
2505 .await;
2506
2507 let second = spawn_hover(&client);
2508 let second_request = read_framed_message(&mut reader).await;
2509 assert_ne!(second_request["id"], first_request["id"]);
2510 write_response(
2511 &mut server.read_half_stdin,
2512 &second_request["id"],
2513 serde_json::json!("fresh"),
2514 )
2515 .await;
2516
2517 assert_eq!(second.await.unwrap().unwrap(), serde_json::json!("fresh"));
2518 assert!(client.pending_requests.no_waiting());
2519 }
2520
2521 #[tokio::test]
2522 async fn test_undecodable_response_fails_only_its_request() {
2523 use crate::test_lsp::write_raw_frame;
2524
2525 let (client, mut server) = fake_lsp_client();
2526 let mut reader = BufReader::new(&mut server.write_stdout);
2527
2528 let first = spawn_hover(&client);
2529 let first_request = read_framed_message(&mut reader).await;
2530 let deep = format!("{}1{}", "{\"parent\":".repeat(200), "}".repeat(200));
2531 write_raw_frame(
2532 &mut server.read_half_stdin,
2533 &format!(
2534 r#"{{"jsonrpc":"2.0","id":{},"result":{deep}}}"#,
2535 first_request["id"]
2536 ),
2537 )
2538 .await;
2539
2540 assert_matches!(
2541 first.await.unwrap(),
2542 Err(Error::LspProtocolError(message)) if message.as_str() == "undecodable response"
2543 );
2544
2545 let second = spawn_hover(&client);
2546 let second_request = read_framed_message(&mut reader).await;
2547 write_response(
2548 &mut server.read_half_stdin,
2549 &second_request["id"],
2550 serde_json::json!("alive"),
2551 )
2552 .await;
2553 assert_eq!(second.await.unwrap().unwrap(), serde_json::json!("alive"));
2554 assert!(client.pending_requests.no_waiting());
2555 }
2556
2557 fn deeply_nested() -> String {
2558 format!("{}1{}", "{\"parent\":".repeat(200), "}".repeat(200))
2559 }
2560
2561 #[tokio::test]
2563 async fn test_invalid_utf8_response_fails_only_its_request() {
2564 use tokio::io::AsyncWriteExt as _;
2565
2566 let (client, mut server) = fake_lsp_client();
2567 let mut reader = BufReader::new(&mut server.write_stdout);
2568
2569 let first = spawn_hover(&client);
2570 let first_request = read_framed_message(&mut reader).await;
2571 let mut body = format!(
2572 r#"{{"jsonrpc":"2.0","id":{},"result":"x"#,
2573 first_request["id"]
2574 )
2575 .into_bytes();
2576 body.extend_from_slice(&[0xff, 0xfe]);
2577 body.extend_from_slice(br#"y"}"#);
2578 let stdin = &mut server.read_half_stdin;
2579 stdin
2580 .write_all(format!("Content-Length: {}\r\n\r\n", body.len()).as_bytes())
2581 .await
2582 .unwrap();
2583 stdin.write_all(&body).await.unwrap();
2584 stdin.flush().await.unwrap();
2585
2586 assert_matches!(
2587 first.await.unwrap(),
2588 Err(Error::LspProtocolError(message)) if message.as_str() == "undecodable response"
2589 );
2590
2591 let second = spawn_hover(&client);
2592 let second_request = read_framed_message(&mut reader).await;
2593 write_response(
2594 &mut server.read_half_stdin,
2595 &second_request["id"],
2596 serde_json::json!("alive"),
2597 )
2598 .await;
2599 assert_eq!(second.await.unwrap().unwrap(), serde_json::json!("alive"));
2600 }
2601
2602 #[test]
2605 fn test_undecodable_warning_is_time_limited_and_counts_the_suppressed() {
2606 use tracing_subscriber::layer::SubscriberExt as _;
2607
2608 let captured = crate::test_lsp::CapturedLogs::default();
2609 let _guard = tracing::subscriber::set_default(
2610 tracing_subscriber::registry().with(captured.clone()),
2611 );
2612 let mut run = UndecodableRun::default();
2613 let start = std::time::Instant::now();
2614 let at = |secs| start + Duration::from_secs(secs);
2615 for secs in [0, 1, 2, 30] {
2616 run.observe_at(InboundMessage::UndecodableFrame, at(secs))
2617 .unwrap();
2618 }
2619 run.observe_at(InboundMessage::UndecodableFrame, at(61))
2620 .unwrap();
2621
2622 let warnings: Vec<_> = captured
2623 .entries()
2624 .into_iter()
2625 .filter(|(level, _)| *level == tracing::Level::WARN)
2626 .map(|(_, message)| message)
2627 .collect();
2628 assert_eq!(warnings.len(), 2, "{warnings:?}");
2629 assert!(warnings[0].contains("(0 others"), "{warnings:?}");
2630 assert!(warnings[1].contains("(3 others"), "{warnings:?}");
2631 }
2632
2633 #[tokio::test]
2634 async fn test_undecodable_server_request_is_answered_with_an_error() {
2635 use crate::test_lsp::write_raw_frame;
2636
2637 let (client, mut server) = fake_lsp_client();
2638 let mut reader = BufReader::new(&mut server.write_stdout);
2639
2640 write_raw_frame(
2641 &mut server.read_half_stdin,
2642 &format!(
2643 r#"{{"jsonrpc":"2.0","id":"srv-1","method":"client/registerCapability","params":{}}}"#,
2644 deeply_nested()
2645 ),
2646 )
2647 .await;
2648 let reply = read_framed_message(&mut reader).await;
2649
2650 assert_eq!(reply["id"], serde_json::json!("srv-1"));
2651 assert_eq!(reply["error"]["code"], serde_json::json!(-32600));
2652
2653 let hover = spawn_hover(&client);
2654 let request = read_framed_message(&mut reader).await;
2655 write_response(
2656 &mut server.read_half_stdin,
2657 &request["id"],
2658 serde_json::json!("alive"),
2659 )
2660 .await;
2661 assert_eq!(hover.await.unwrap().unwrap(), serde_json::json!("alive"));
2662 }
2663
2664 #[tokio::test]
2665 async fn test_undecodable_notification_is_dropped_and_the_connection_stays_up() {
2666 use crate::test_lsp::write_raw_frame;
2667
2668 let (client, mut server) = fake_lsp_client();
2669 let mut reader = BufReader::new(&mut server.write_stdout);
2670 let hover = spawn_hover(&client);
2671 let request = read_framed_message(&mut reader).await;
2672
2673 write_raw_frame(
2674 &mut server.read_half_stdin,
2675 &format!(
2676 r#"{{"jsonrpc":"2.0","method":"window/logMessage","params":{}}}"#,
2677 deeply_nested()
2678 ),
2679 )
2680 .await;
2681 write_response(
2682 &mut server.read_half_stdin,
2683 &request["id"],
2684 serde_json::json!("alive"),
2685 )
2686 .await;
2687
2688 assert_eq!(hover.await.unwrap().unwrap(), serde_json::json!("alive"));
2689 }
2690
2691 #[tokio::test]
2692 async fn test_a_run_of_undecodable_frames_ends_the_connection() {
2693 use crate::test_lsp::write_raw_frame;
2694
2695 let (client, mut server) = fake_lsp_client();
2696 let mut reader = BufReader::new(&mut server.write_stdout);
2697 let hover = spawn_hover(&client);
2698 let _request = read_framed_message(&mut reader).await;
2699
2700 for _ in 0..MAX_CONSECUTIVE_UNDECODABLE_FRAMES {
2701 write_raw_frame(&mut server.read_half_stdin, "{not json").await;
2702 }
2703
2704 assert_matches!(hover.await.unwrap(), Err(Error::ServerTerminated));
2705 }
2706
2707 #[tokio::test]
2708 async fn test_a_decodable_message_ends_a_run_of_undecodable_frames() {
2709 use crate::test_lsp::write_raw_frame;
2710
2711 let (client, mut server) = fake_lsp_client();
2712 let mut reader = BufReader::new(&mut server.write_stdout);
2713 let hover = spawn_hover(&client);
2714 let request = read_framed_message(&mut reader).await;
2715
2716 let almost = MAX_CONSECUTIVE_UNDECODABLE_FRAMES - 1;
2717 for _ in 0..2 {
2718 for _ in 0..almost {
2719 write_raw_frame(&mut server.read_half_stdin, "{not json").await;
2720 }
2721 write_raw_frame(
2722 &mut server.read_half_stdin,
2723 r#"{"jsonrpc":"2.0","method":"$/progress"}"#,
2724 )
2725 .await;
2726 }
2727 write_response(
2728 &mut server.read_half_stdin,
2729 &request["id"],
2730 serde_json::json!("alive"),
2731 )
2732 .await;
2733
2734 assert_eq!(hover.await.unwrap().unwrap(), serde_json::json!("alive"));
2735 }
2736
2737 #[tokio::test(start_paused = true)]
2738 async fn test_dropped_caller_after_send_is_cleared_by_late_reply() {
2739 let (client, mut server) = fake_lsp_client();
2740 let mut reader = BufReader::new(&mut server.write_stdout);
2741
2742 let first = spawn_hover(&client);
2743 let first_request = read_framed_message(&mut reader).await;
2744 first.abort();
2745 assert!(first.await.unwrap_err().is_cancelled());
2746 let cancel = read_framed_message(&mut reader).await;
2747 assert_eq!(cancel["method"], "$/cancelRequest");
2748 assert_eq!(cancel["params"]["id"], first_request["id"]);
2749
2750 write_response(
2751 &mut server.read_half_stdin,
2752 &first_request["id"],
2753 serde_json::json!("orphaned"),
2754 )
2755 .await;
2756
2757 let second = spawn_hover(&client);
2758 let second_request = read_framed_message(&mut reader).await;
2759 write_response(
2760 &mut server.read_half_stdin,
2761 &second_request["id"],
2762 serde_json::json!("fresh"),
2763 )
2764 .await;
2765
2766 assert_eq!(second.await.unwrap().unwrap(), serde_json::json!("fresh"));
2767 assert!(
2768 client.pending_requests.no_waiting(),
2769 "the orphaned reply must clear the abandoned entry"
2770 );
2771 }
2772 }
2773
2774 #[test]
2775 fn test_should_retrigger_defaults_to_true_when_data_absent() {
2776 assert!(LspClient::should_retrigger(None));
2777 }
2778
2779 #[test]
2780 fn test_should_retrigger_false_when_flag_false() {
2781 assert!(!LspClient::should_retrigger(Some(&serde_json::json!({
2782 "retriggerRequest": false
2783 }))));
2784 }
2785
2786 #[test]
2787 fn test_should_retrigger_true_when_flag_true() {
2788 assert!(LspClient::should_retrigger(Some(&serde_json::json!({
2789 "retriggerRequest": true
2790 }))));
2791 }
2792
2793 mod void_params_wire {
2798 use tokio::io::BufReader;
2799
2800 use super::*;
2801 use crate::test_lsp::{fake_lsp_client, read_framed_message, write_response};
2802
2803 #[tokio::test]
2804 async fn test_request_with_null_params_omits_params_key() {
2805 let (client, mut server) = fake_lsp_client();
2806
2807 let request_task = tokio::spawn(async move {
2808 client
2809 .request::<_, Value>("shutdown", Value::Null, Duration::from_secs(5))
2810 .await
2811 });
2812
2813 let mut reader = BufReader::new(&mut server.write_stdout);
2814 let request = read_framed_message(&mut reader).await;
2815
2816 assert_eq!(request["method"], "shutdown");
2817 assert!(
2818 request.get("params").is_none(),
2819 "null params must be omitted, got: {request}"
2820 );
2821
2822 write_response(&mut server.read_half_stdin, &request["id"], Value::Null).await;
2823 request_task.await.unwrap().unwrap();
2824 }
2825
2826 #[tokio::test]
2827 async fn test_notify_with_null_params_omits_params_key() {
2828 let (client, mut server) = fake_lsp_client();
2829
2830 client.notify("exit", Value::Null).await.unwrap();
2831
2832 let mut reader = BufReader::new(&mut server.write_stdout);
2833 let notification = read_framed_message(&mut reader).await;
2834
2835 assert_eq!(notification["method"], "exit");
2836 assert!(
2837 notification.get("params").is_none(),
2838 "null params must be omitted, got: {notification}"
2839 );
2840 }
2841
2842 #[tokio::test]
2843 async fn test_notify_with_empty_object_params_keeps_params_key() {
2844 let (client, mut server) = fake_lsp_client();
2845
2846 client
2847 .notify("initialized", lsp_types::InitializedParams {})
2848 .await
2849 .unwrap();
2850
2851 let mut reader = BufReader::new(&mut server.write_stdout);
2852 let notification = read_framed_message(&mut reader).await;
2853
2854 assert_eq!(notification["method"], "initialized");
2855 assert_eq!(
2856 notification["params"],
2857 serde_json::json!({}),
2858 "non-null params must still be sent"
2859 );
2860 }
2861 }
2862
2863 mod retry_behavior {
2864 use tokio::io::{AsyncWriteExt, BufReader, DuplexStream};
2865
2866 use super::*;
2867 use crate::test_lsp::{
2868 CapturedLogs, fake_lsp_client, read_framed_message, write_error_response,
2869 write_response as write_success_response,
2870 };
2871
2872 async fn write_retryable_error_response(
2880 stdin: &mut DuplexStream,
2881 id: &Value,
2882 code: i32,
2883 message: &str,
2884 retrigger: bool,
2885 ) {
2886 let response = serde_json::json!({
2887 "jsonrpc": "2.0",
2888 "id": id,
2889 "error": {
2890 "code": code,
2891 "message": message,
2892 "data": { "retriggerRequest": retrigger },
2893 },
2894 });
2895 let content = serde_json::to_string(&response).unwrap();
2896 let header = format!("Content-Length: {}\r\n\r\n", content.len());
2897 stdin.write_all(header.as_bytes()).await.unwrap();
2898 stdin.write_all(content.as_bytes()).await.unwrap();
2899 stdin.flush().await.unwrap();
2900 }
2901
2902 #[tokio::test]
2912 async fn test_retry_exhaustion_returns_original_server_cancelled_error() {
2913 use tracing_subscriber::layer::SubscriberExt as _;
2914
2915 let (client, mut server) = fake_lsp_client();
2916 let captured = CapturedLogs::default();
2917 let subscriber = tracing_subscriber::registry().with(captured.clone());
2918 let guard = tracing::subscriber::set_default(subscriber);
2919
2920 let request_task = tokio::spawn(async move {
2921 client
2922 .request::<_, Value>(
2923 "textDocument/hover",
2924 serde_json::json!({}),
2925 Duration::from_secs(30),
2926 )
2927 .await
2928 });
2929
2930 let mut reader = BufReader::new(&mut server.write_stdout);
2931 for _ in 0..=SERVER_CANCELLED_MAX_RETRIES {
2935 let request = read_framed_message(&mut reader).await;
2936 let id = request["id"].clone();
2937 write_retryable_error_response(
2938 &mut server.read_half_stdin,
2939 &id,
2940 i32::from(LspErrorCodes::ServerCancelled),
2941 "server cancelled the request",
2942 true,
2943 )
2944 .await;
2945 }
2946
2947 let result = request_task.await.unwrap();
2948
2949 match result {
2950 Err(Error::LspServerError(JsonRpcError {
2951 code,
2952 message,
2953 data,
2954 })) => {
2955 assert_eq!(code, i32::from(LspErrorCodes::ServerCancelled));
2959 assert_eq!(message, "server cancelled the request");
2960 assert_eq!(data, Some(serde_json::json!({ "retriggerRequest": true })));
2961 }
2962 other => panic!("expected exhausted ServerCancelled error, got {other:?}"),
2963 }
2964
2965 drop(guard);
2966 let logs = captured.entries();
2967 assert_eq!(
2968 logs.iter()
2969 .filter(|(level, _)| *level == tracing::Level::ERROR)
2970 .count(),
2971 1,
2972 "exactly the final exhausted attempt must log at ERROR, got: {logs:?}"
2973 );
2974 assert!(
2975 logs.iter()
2976 .any(|(level, msg)| *level == tracing::Level::ERROR
2977 && msg.contains("LSP error response")
2978 && msg.contains("retries exhausted")),
2979 "expected an ERROR log sharing the 'LSP error response' prefix and naming \
2980 retry exhaustion, got: {logs:?}"
2981 );
2982 assert_eq!(
2983 logs.iter()
2984 .filter(
2985 |(level, msg)| *level == tracing::Level::WARN && msg.contains("will retry")
2986 )
2987 .count(),
2988 usize::try_from(SERVER_CANCELLED_MAX_RETRIES).unwrap(),
2989 "every attempt before the last must log a WARN 'will retry' line, got: {logs:?}"
2990 );
2991 }
2992
2993 #[tokio::test]
2994 async fn test_retrigger_false_returns_immediately_without_retry() {
2995 let (client, mut server) = fake_lsp_client();
2996
2997 let request_task = tokio::spawn(async move {
2998 client
2999 .request::<_, Value>(
3000 "textDocument/hover",
3001 serde_json::json!({}),
3002 Duration::from_secs(30),
3003 )
3004 .await
3005 });
3006
3007 let mut reader = BufReader::new(&mut server.write_stdout);
3008 let request = read_framed_message(&mut reader).await;
3009 let id = request["id"].clone();
3010 write_retryable_error_response(
3011 &mut server.read_half_stdin,
3012 &id,
3013 i32::from(LspErrorCodes::ServerCancelled),
3014 "server cancelled the request",
3015 false,
3016 )
3017 .await;
3018
3019 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
3026 .await
3027 .unwrap()
3028 .unwrap();
3029
3030 match result {
3031 Err(Error::LspServerError(JsonRpcError { code, .. })) => {
3032 assert_eq!(code, i32::from(LspErrorCodes::ServerCancelled));
3033 }
3034 other => panic!("expected immediate ServerCancelled error, got {other:?}"),
3035 }
3036
3037 let second_request =
3038 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
3039 .await;
3040 assert!(
3041 second_request.is_err(),
3042 "no retry should have been sent after retriggerRequest: false"
3043 );
3044 }
3045
3046 #[tokio::test]
3047 async fn test_retry_succeeds_after_one_server_cancelled_response() {
3048 let (client, mut server) = fake_lsp_client();
3049
3050 let request_task = tokio::spawn(async move {
3051 client
3052 .request::<_, Value>(
3053 "textDocument/hover",
3054 serde_json::json!({}),
3055 Duration::from_secs(30),
3056 )
3057 .await
3058 });
3059
3060 let mut reader = BufReader::new(&mut server.write_stdout);
3061
3062 let first = read_framed_message(&mut reader).await;
3064 write_retryable_error_response(
3065 &mut server.read_half_stdin,
3066 &first["id"].clone(),
3067 i32::from(LspErrorCodes::ServerCancelled),
3068 "server cancelled the request",
3069 true,
3070 )
3071 .await;
3072
3073 let second = read_framed_message(&mut reader).await;
3076 assert_ne!(
3077 first["id"], second["id"],
3078 "retry must use a fresh request id"
3079 );
3080 let expected_result = serde_json::json!({ "contents": "resolved on retry" });
3081 write_success_response(
3082 &mut server.read_half_stdin,
3083 &second["id"].clone(),
3084 expected_result.clone(),
3085 )
3086 .await;
3087
3088 let result = request_task.await.unwrap();
3089 assert_eq!(result.unwrap(), expected_result);
3090 }
3091
3092 #[tokio::test]
3093 async fn test_retry_exhaustion_returns_original_content_modified_error() {
3094 let (client, mut server) = fake_lsp_client();
3095
3096 let request_task = tokio::spawn(async move {
3097 client
3098 .request::<_, Value>(
3099 "textDocument/hover",
3100 serde_json::json!({}),
3101 Duration::from_secs(30),
3102 )
3103 .await
3104 });
3105
3106 let mut reader = BufReader::new(&mut server.write_stdout);
3107 for _ in 0..=SERVER_CANCELLED_MAX_RETRIES {
3112 let request = read_framed_message(&mut reader).await;
3113 let id = request["id"].clone();
3114 write_retryable_error_response(
3115 &mut server.read_half_stdin,
3116 &id,
3117 i32::from(LspErrorCodes::ContentModified),
3118 "content modified",
3119 true,
3120 )
3121 .await;
3122 }
3123
3124 let result = request_task.await.unwrap();
3125
3126 match result {
3127 Err(Error::LspServerError(JsonRpcError {
3128 code,
3129 message,
3130 data,
3131 })) => {
3132 assert_eq!(code, i32::from(LspErrorCodes::ContentModified));
3136 assert_eq!(message, "content modified");
3137 assert_eq!(data, Some(serde_json::json!({ "retriggerRequest": true })));
3138 }
3139 other => panic!("expected exhausted ContentModified error, got {other:?}"),
3140 }
3141 }
3142
3143 #[tokio::test]
3144 async fn test_retrigger_false_returns_immediately_without_retry_for_content_modified() {
3145 let (client, mut server) = fake_lsp_client();
3146
3147 let request_task = tokio::spawn(async move {
3148 client
3149 .request::<_, Value>(
3150 "textDocument/hover",
3151 serde_json::json!({}),
3152 Duration::from_secs(30),
3153 )
3154 .await
3155 });
3156
3157 let mut reader = BufReader::new(&mut server.write_stdout);
3158 let request = read_framed_message(&mut reader).await;
3159 let id = request["id"].clone();
3160 write_retryable_error_response(
3161 &mut server.read_half_stdin,
3162 &id,
3163 i32::from(LspErrorCodes::ContentModified),
3164 "content modified",
3165 false,
3166 )
3167 .await;
3168
3169 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
3174 .await
3175 .unwrap()
3176 .unwrap();
3177
3178 match result {
3179 Err(Error::LspServerError(JsonRpcError { code, .. })) => {
3180 assert_eq!(code, i32::from(LspErrorCodes::ContentModified));
3181 }
3182 other => panic!("expected immediate ContentModified error, got {other:?}"),
3183 }
3184
3185 let second_request =
3186 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
3187 .await;
3188 assert!(
3189 second_request.is_err(),
3190 "no retry should have been sent after retriggerRequest: false"
3191 );
3192 }
3193
3194 #[tokio::test]
3195 async fn test_retry_succeeds_after_one_content_modified_response() {
3196 let (client, mut server) = fake_lsp_client();
3197
3198 let request_task = tokio::spawn(async move {
3199 client
3200 .request::<_, Value>(
3201 "textDocument/hover",
3202 serde_json::json!({}),
3203 Duration::from_secs(30),
3204 )
3205 .await
3206 });
3207
3208 let mut reader = BufReader::new(&mut server.write_stdout);
3209
3210 let first = read_framed_message(&mut reader).await;
3212 write_retryable_error_response(
3213 &mut server.read_half_stdin,
3214 &first["id"].clone(),
3215 i32::from(LspErrorCodes::ContentModified),
3216 "content modified",
3217 true,
3218 )
3219 .await;
3220
3221 let second = read_framed_message(&mut reader).await;
3224 assert_ne!(
3225 first["id"], second["id"],
3226 "retry must use a fresh request id"
3227 );
3228 let expected_result = serde_json::json!({ "contents": "resolved on retry" });
3229 write_success_response(
3230 &mut server.read_half_stdin,
3231 &second["id"].clone(),
3232 expected_result.clone(),
3233 )
3234 .await;
3235
3236 let result = request_task.await.unwrap();
3237 assert_eq!(result.unwrap(), expected_result);
3238 }
3239
3240 #[tokio::test]
3243 async fn test_retry_follows_the_error_of_a_frame_with_both_result_and_error() {
3244 let (client, mut server) = fake_lsp_client();
3245
3246 let request_task = tokio::spawn(async move {
3247 client
3248 .request::<_, Value>(
3249 "textDocument/hover",
3250 serde_json::json!({}),
3251 Duration::from_secs(30),
3252 )
3253 .await
3254 });
3255
3256 let mut reader = BufReader::new(&mut server.write_stdout);
3257 let first = read_framed_message(&mut reader).await;
3258 let both = serde_json::json!({
3259 "jsonrpc": "2.0",
3260 "id": first["id"],
3261 "result": null,
3262 "error": {
3263 "code": i32::from(LspErrorCodes::ContentModified),
3264 "message": "content modified",
3265 "data": { "retriggerRequest": true },
3266 },
3267 })
3268 .to_string();
3269 let frame = format!("Content-Length: {}\r\n\r\n{both}", both.len());
3270 server
3271 .read_half_stdin
3272 .write_all(frame.as_bytes())
3273 .await
3274 .unwrap();
3275 server.read_half_stdin.flush().await.unwrap();
3276
3277 let second = read_framed_message(&mut reader).await;
3278 assert_ne!(first["id"], second["id"]);
3279 let expected = serde_json::json!({ "contents": "resolved on retry" });
3280 write_success_response(
3281 &mut server.read_half_stdin,
3282 &second["id"].clone(),
3283 expected.clone(),
3284 )
3285 .await;
3286
3287 assert_eq!(request_task.await.unwrap().unwrap(), expected);
3288 }
3289
3290 #[tokio::test]
3291 async fn test_content_modified_on_non_allowlisted_method_does_not_retry() {
3292 let (client, mut server) = fake_lsp_client();
3293
3294 let request_task = tokio::spawn(async move {
3301 client
3302 .request::<_, Value>(
3303 "textDocument/rename",
3304 serde_json::json!({}),
3305 Duration::from_secs(30),
3306 )
3307 .await
3308 });
3309
3310 let mut reader = BufReader::new(&mut server.write_stdout);
3311 let request = read_framed_message(&mut reader).await;
3312 let id = request["id"].clone();
3313 write_retryable_error_response(
3314 &mut server.read_half_stdin,
3315 &id,
3316 i32::from(LspErrorCodes::ContentModified),
3317 "content modified",
3318 true,
3319 )
3320 .await;
3321
3322 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
3323 .await
3324 .unwrap()
3325 .unwrap();
3326
3327 match result {
3328 Err(Error::LspServerError(JsonRpcError { code, .. })) => {
3329 assert_eq!(code, i32::from(LspErrorCodes::ContentModified));
3330 }
3331 other => panic!("expected immediate ContentModified error, got {other:?}"),
3332 }
3333
3334 let second_request =
3335 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
3336 .await;
3337 assert!(
3338 second_request.is_err(),
3339 "no retry should have been sent for a non-allowlisted method"
3340 );
3341 }
3342
3343 #[tokio::test]
3350 async fn test_oversized_error_message_truncated_for_caller() {
3351 let (client, mut server) = fake_lsp_client();
3352
3353 let request_task = tokio::spawn(async move {
3354 client
3355 .request::<_, Value>(
3356 "textDocument/hover",
3357 serde_json::json!({}),
3358 Duration::from_secs(30),
3359 )
3360 .await
3361 });
3362
3363 let mut reader = BufReader::new(&mut server.write_stdout);
3364 let request = read_framed_message(&mut reader).await;
3365 let id = request["id"].clone();
3366 let oversized_message = "x".repeat(MAX_ERROR_MESSAGE_CALLER_BYTES + 500);
3367 write_error_response(&mut server.read_half_stdin, &id, -32603, &oversized_message)
3368 .await;
3369
3370 let result = request_task.await.unwrap();
3371
3372 match result {
3373 Err(Error::LspServerError(JsonRpcError { code, message, .. })) => {
3374 assert_eq!(code, -32603);
3375 assert!(
3376 message.len() < oversized_message.len(),
3377 "caller-facing message must be truncated, got {} bytes",
3378 message.len()
3379 );
3380 assert!(message.ends_with("... (truncated)"));
3381 }
3382 other => panic!("expected truncated LspServerError, got {other:?}"),
3383 }
3384 }
3385
3386 #[tokio::test]
3392 async fn test_error_message_between_log_and_caller_caps_reaches_caller_intact() {
3393 let (client, mut server) = fake_lsp_client();
3394
3395 let request_task = tokio::spawn(async move {
3396 client
3397 .request::<_, Value>(
3398 "textDocument/hover",
3399 serde_json::json!({}),
3400 Duration::from_secs(30),
3401 )
3402 .await
3403 });
3404
3405 let mut reader = BufReader::new(&mut server.write_stdout);
3406 let request = read_framed_message(&mut reader).await;
3407 let id = request["id"].clone();
3408 let message = "x".repeat(crate::util::MAX_LOG_STRING_BYTES + 50);
3409 write_error_response(&mut server.read_half_stdin, &id, -32603, &message).await;
3410
3411 let result = request_task.await.unwrap();
3412
3413 match result {
3414 Err(Error::LspServerError(JsonRpcError {
3415 message: returned, ..
3416 })) => {
3417 assert_eq!(
3418 returned, message,
3419 "message under the caller cap must not be truncated"
3420 );
3421 }
3422 other => panic!("expected untruncated LspServerError, got {other:?}"),
3423 }
3424 }
3425
3426 #[tokio::test]
3431 async fn test_retried_error_that_recovers_does_not_log_error_level() {
3432 use tracing_subscriber::layer::SubscriberExt as _;
3433
3434 let (client, mut server) = fake_lsp_client();
3435 let captured = CapturedLogs::default();
3436 let subscriber = tracing_subscriber::registry().with(captured.clone());
3437 let guard = tracing::subscriber::set_default(subscriber);
3438
3439 let request_task = tokio::spawn(async move {
3440 client
3441 .request::<_, Value>(
3442 "textDocument/hover",
3443 serde_json::json!({}),
3444 Duration::from_secs(30),
3445 )
3446 .await
3447 });
3448
3449 let mut reader = BufReader::new(&mut server.write_stdout);
3450
3451 let first = read_framed_message(&mut reader).await;
3452 write_retryable_error_response(
3453 &mut server.read_half_stdin,
3454 &first["id"].clone(),
3455 i32::from(LspErrorCodes::ServerCancelled),
3456 "server cancelled the request",
3457 true,
3458 )
3459 .await;
3460
3461 let second = read_framed_message(&mut reader).await;
3462 write_success_response(
3463 &mut server.read_half_stdin,
3464 &second["id"].clone(),
3465 serde_json::json!({ "contents": "resolved on retry" }),
3466 )
3467 .await;
3468
3469 let result = request_task.await.unwrap();
3470 assert!(result.is_ok(), "expected retry to recover, got {result:?}");
3471
3472 drop(guard);
3473 let logs = captured.entries();
3474 assert!(
3475 !logs
3476 .iter()
3477 .any(|(level, _)| *level == tracing::Level::ERROR),
3478 "a retried-and-recovered error must not log at ERROR, got: {logs:?}"
3479 );
3480 assert!(
3481 logs.iter().any(
3482 |(level, msg)| *level == tracing::Level::WARN && msg.contains("will retry")
3483 ),
3484 "expected a WARN 'will retry' log line, got: {logs:?}"
3485 );
3486 }
3487
3488 #[tokio::test]
3492 async fn test_non_retryable_error_logs_error_level() {
3493 use tracing_subscriber::layer::SubscriberExt as _;
3494
3495 let (client, mut server) = fake_lsp_client();
3496 let captured = CapturedLogs::default();
3497 let subscriber = tracing_subscriber::registry().with(captured.clone());
3498 let guard = tracing::subscriber::set_default(subscriber);
3499
3500 let request_task = tokio::spawn(async move {
3501 client
3502 .request::<_, Value>(
3503 "textDocument/rename",
3504 serde_json::json!({}),
3505 Duration::from_secs(30),
3506 )
3507 .await
3508 });
3509
3510 let mut reader = BufReader::new(&mut server.write_stdout);
3511 let request = read_framed_message(&mut reader).await;
3512 let id = request["id"].clone();
3513 write_retryable_error_response(
3514 &mut server.read_half_stdin,
3515 &id,
3516 i32::from(LspErrorCodes::ContentModified),
3517 "content modified",
3518 true,
3519 )
3520 .await;
3521
3522 let result = request_task.await.unwrap();
3523 assert!(result.is_err(), "expected a non-retryable error");
3524
3525 drop(guard);
3526 let logs = captured.entries();
3527 assert!(
3528 logs.iter()
3529 .any(|(level, msg)| *level == tracing::Level::ERROR
3530 && msg.contains("LSP error response")
3531 && msg.contains("content modified")),
3532 "a non-retryable error must still surface an ERROR log sharing the \
3533 'LSP error response' prefix, got: {logs:?}"
3534 );
3535 }
3536 }
3537
3538 mod reader_task_regression {
3542 use tokio::io::BufReader;
3543
3544 use super::*;
3545 use crate::test_lsp::{
3546 fake_lsp_client, inert_transport, read_framed_message, write_response,
3547 };
3548
3549 #[tokio::test]
3556 async fn test_message_loop_inner_treats_reader_channel_close_as_server_terminated() {
3557 let (mut transport, _reader) = inert_transport();
3558 let (_command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
3559 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
3560 let pending_requests = PendingRequests::default();
3561
3562 drop(msg_tx);
3563
3564 let result = LspClient::message_loop_inner(
3565 &mut transport,
3566 &mut msg_rx,
3567 &mut command_rx,
3568 &pending_requests,
3569 None,
3570 &Redactions::default(),
3571 None,
3572 )
3573 .await;
3574
3575 assert_matches!(result, Err(Error::ServerTerminated), "got {result:?}");
3576 }
3577
3578 #[tokio::test]
3590 async fn test_message_loop_inner_reader_gone_then_drain_fails_pending_request() {
3591 let (mut transport, _reader) = inert_transport();
3592 let (_command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
3593 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
3594 let pending_requests = PendingRequests::default();
3595
3596 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
3597 pending_requests.register(RequestId::Number(1), response_tx);
3598
3599 drop(msg_tx);
3600
3601 let result = LspClient::message_loop_inner(
3602 &mut transport,
3603 &mut msg_rx,
3604 &mut command_rx,
3605 &pending_requests,
3606 None,
3607 &Redactions::default(),
3608 None,
3609 )
3610 .await;
3611 assert_matches!(result, Err(Error::ServerTerminated), "got {result:?}");
3612
3613 LspClient::drain_and_fail_pending(&pending_requests, &StdMutex::default());
3614
3615 let received = response_rx.await.unwrap();
3616 assert_matches!(received, Err(Error::ServerTerminated), "got {received:?}");
3617 assert!(pending_requests.no_waiting());
3618 }
3619
3620 #[tokio::test]
3628 async fn test_shutdown_drains_buffered_responses_instead_of_dropping_them() {
3629 let (mut transport, _reader) = inert_transport();
3630 let (command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
3631 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
3632 let pending_requests = PendingRequests::default();
3633
3634 let id = RequestId::Number(1);
3635 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
3636 pending_requests.register(id.clone(), response_tx);
3637
3638 msg_tx
3639 .send(Ok(InboundMessage::Response(JsonRpcResponse {
3640 jsonrpc: JsonRpcVersion,
3641 id: id.clone(),
3642 outcome: JsonRpcOutcome::Result(serde_json::json!({ "ok": true })),
3643 })))
3644 .await
3645 .unwrap();
3646 command_tx.send(ClientCommand::Shutdown).await.unwrap();
3647 drop(command_tx);
3648
3649 let result = LspClient::message_loop_inner(
3650 &mut transport,
3651 &mut msg_rx,
3652 &mut command_rx,
3653 &pending_requests,
3654 None,
3655 &Redactions::default(),
3656 None,
3657 )
3658 .await;
3659
3660 assert!(result.is_ok(), "got {result:?}");
3661 let received = response_rx.await.unwrap();
3664 assert_eq!(received.unwrap(), serde_json::json!({ "ok": true }));
3665 assert!(
3666 pending_requests.no_waiting(),
3667 "the drained response must resolve its pending request entry"
3668 );
3669 }
3670
3671 #[tokio::test]
3679 async fn test_message_loop_fails_pending_requests_on_transport_error_exit() {
3680 let (transport, reader) = inert_transport();
3681 let (_command_tx, command_rx) = mpsc::channel::<ClientCommand>(1);
3682 let pending_requests = PendingRequests::default();
3683
3684 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
3685 pending_requests.register(RequestId::Number(1), response_tx);
3686
3687 let result = LspClient::message_loop(
3688 (transport, reader),
3689 command_rx,
3690 pending_requests.clone(),
3691 Arc::default(),
3692 None,
3693 Arc::default(),
3694 None,
3695 )
3696 .await;
3697
3698 assert!(result.is_err(), "got {result:?}");
3699 let received = response_rx.await.unwrap();
3700 assert_matches!(received, Err(Error::ServerTerminated), "got {received:?}");
3701 assert!(pending_requests.no_waiting());
3702 }
3703
3704 #[tokio::test]
3714 async fn test_message_loop_shutdown_resolves_answered_and_fails_unanswered_pending() {
3715 let (mut transport, _reader) = inert_transport();
3716 let (command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
3717 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
3718 let pending_requests = PendingRequests::default();
3719
3720 let answered_id = RequestId::Number(1);
3721 let (answered_tx, answered_rx) = oneshot::channel::<Result<Value>>();
3722 let unanswered_id = RequestId::Number(2);
3723 let (unanswered_tx, unanswered_rx) = oneshot::channel::<Result<Value>>();
3724 pending_requests.register(answered_id.clone(), answered_tx);
3725 pending_requests.register(unanswered_id.clone(), unanswered_tx);
3726
3727 msg_tx
3728 .send(Ok(InboundMessage::Response(JsonRpcResponse {
3729 jsonrpc: JsonRpcVersion,
3730 id: answered_id,
3731 outcome: JsonRpcOutcome::Result(serde_json::json!({ "ok": true })),
3732 })))
3733 .await
3734 .unwrap();
3735 command_tx.send(ClientCommand::Shutdown).await.unwrap();
3736 drop(command_tx);
3737
3738 let inner_result = LspClient::message_loop_inner(
3739 &mut transport,
3740 &mut msg_rx,
3741 &mut command_rx,
3742 &pending_requests,
3743 None,
3744 &Redactions::default(),
3745 None,
3746 )
3747 .await;
3748 assert!(inner_result.is_ok(), "got {inner_result:?}");
3749
3750 LspClient::drain_and_fail_pending(&pending_requests, &StdMutex::default());
3753
3754 let answered = answered_rx.await.unwrap();
3755 assert_eq!(answered.unwrap(), serde_json::json!({ "ok": true }));
3756
3757 let unanswered = unanswered_rx.await.unwrap();
3758 assert_matches!(
3759 unanswered,
3760 Err(Error::ServerTerminated),
3761 "got {unanswered:?}"
3762 );
3763 assert!(pending_requests.no_waiting());
3764 }
3765
3766 #[tokio::test]
3776 async fn test_message_loop_end_to_end_resolves_answered_then_fails_unanswered_on_shutdown()
3777 {
3778 let (client, mut server) = fake_lsp_client();
3779 let mut reader = BufReader::new(&mut server.write_stdout);
3780
3781 let answered_client = client.clone();
3782 let answered_task = tokio::spawn(async move {
3783 answered_client
3784 .request::<_, Value>(
3785 "textDocument/hover",
3786 serde_json::json!({}),
3787 Duration::from_secs(30),
3788 )
3789 .await
3790 });
3791
3792 let request = read_framed_message(&mut reader).await;
3793 let id = request["id"].clone();
3794 write_response(
3795 &mut server.read_half_stdin,
3796 &id,
3797 serde_json::json!({ "ok": true }),
3798 )
3799 .await;
3800
3801 let answered = answered_task.await.unwrap().unwrap();
3802 assert_eq!(answered, serde_json::json!({ "ok": true }));
3803
3804 let unanswered_client = client.clone();
3805 let unanswered_task = tokio::spawn(async move {
3806 unanswered_client
3807 .request::<_, Value>(
3808 "textDocument/definition",
3809 serde_json::json!({}),
3810 Duration::from_secs(30),
3811 )
3812 .await
3813 });
3814
3815 let _unanswered_request = read_framed_message(&mut reader).await;
3819
3820 client.shutdown().await.unwrap();
3821
3822 let unanswered = tokio::time::timeout(Duration::from_millis(200), unanswered_task)
3823 .await
3824 .unwrap_or_else(|_| {
3825 panic!(
3826 "unanswered request must fail immediately on shutdown, not hang until its own timeout"
3827 )
3828 })
3829 .unwrap();
3830 assert_matches!(
3831 unanswered,
3832 Err(Error::ServerTerminated),
3833 "got {unanswered:?}"
3834 );
3835 }
3836
3837 #[tokio::test]
3844 async fn test_dense_concurrent_requests_all_resolve_to_matching_responses() {
3845 const REQUEST_COUNT: usize = 20;
3846 let (client, mut server) = fake_lsp_client();
3847
3848 let mut request_tasks = Vec::with_capacity(REQUEST_COUNT);
3855 for i in 0..REQUEST_COUNT {
3856 let client = client.clone();
3857 request_tasks.push(tokio::spawn(async move {
3858 client
3859 .request::<_, Value>(
3860 "textDocument/hover",
3861 serde_json::json!({ "n": i }),
3862 Duration::from_secs(30),
3863 )
3864 .await
3865 }));
3866 }
3867
3868 let server_task = tokio::spawn(async move {
3869 let mut reader = BufReader::new(&mut server.write_stdout);
3870 let mut requests = Vec::with_capacity(REQUEST_COUNT);
3871 for _ in 0..REQUEST_COUNT {
3872 requests.push(read_framed_message(&mut reader).await);
3873 }
3874 for request in requests.into_iter().rev() {
3875 let id = request["id"].clone();
3876 let n = request["params"]["n"].clone();
3877 write_response(
3878 &mut server.read_half_stdin,
3879 &id,
3880 serde_json::json!({ "echo": n }),
3881 )
3882 .await;
3883 }
3884 });
3885
3886 server_task.await.unwrap();
3887
3888 for (i, task) in request_tasks.into_iter().enumerate() {
3889 let value = task
3890 .await
3891 .unwrap()
3892 .unwrap_or_else(|e| panic!("request {i} failed: {e:?}"));
3893 assert_eq!(
3894 value["echo"],
3895 serde_json::json!(i),
3896 "response for request {i} carried the wrong payload -- id/response mismatch"
3897 );
3898 }
3899 }
3900
3901 #[tokio::test]
3904 async fn test_shutdown_on_clone_stops_shared_loop_then_owner_shutdown_succeeds() {
3905 let (client, _server) = fake_lsp_client();
3906
3907 client.clone().shutdown().await.unwrap();
3908
3909 let request = timeout(
3910 Duration::from_secs(2),
3911 client.request::<_, Value>("x/y", serde_json::json!({}), Duration::from_secs(30)),
3912 )
3913 .await
3914 .unwrap_or_else(|_| panic!("request on a stopped loop must fail fast"));
3915 assert_matches!(request, Err(Error::ServerTerminated), "got {request:?}");
3916
3917 client.shutdown().await.unwrap();
3918 }
3919
3920 #[tokio::test]
3923 async fn test_shutdown_until_expired_deadline_reports_timeout() {
3924 let (mut client, _server) = fake_lsp_client();
3925 let probe = client.clone();
3926
3927 let result = client
3928 .shutdown_until(Instant::now() - Duration::from_millis(50))
3929 .await;
3930
3931 assert_matches!(result, Err(Error::ShutdownTimeout), "got {result:?}");
3932 assert_matches!(probe.state().await, crate::lsp::ServerState::Shutdown);
3933 }
3934
3935 #[tokio::test]
3939 async fn test_shutdown_until_aborts_wedged_loop_and_fails_pending() {
3940 let (mut client, mut server) = fake_lsp_client();
3941 let mut reader = BufReader::new(&mut server.write_stdout);
3942
3943 let in_flight_client = client.clone();
3944 let in_flight = tokio::spawn(async move {
3945 in_flight_client
3946 .request::<_, Value>(
3947 "textDocument/hover",
3948 serde_json::json!({}),
3949 Duration::from_secs(30),
3950 )
3951 .await
3952 });
3953 let _ = read_framed_message(&mut reader).await;
3954
3955 client
3957 .notify("x/wedge", serde_json::json!({ "p": "a".repeat(1 << 20) }))
3958 .await
3959 .unwrap();
3960
3961 let deadline = Instant::now() + Duration::from_millis(200);
3962 let result = timeout(Duration::from_secs(5), client.shutdown_until(deadline))
3963 .await
3964 .unwrap_or_else(|_| panic!("shutdown_until must honor its deadline"));
3965 assert_matches!(result, Err(Error::ShutdownTimeout), "got {result:?}");
3966
3967 let in_flight = timeout(Duration::from_secs(1), in_flight)
3968 .await
3969 .unwrap_or_else(|_| panic!("in-flight request must be failed by the drain"))
3970 .unwrap();
3971 assert_matches!(in_flight, Err(Error::ServerTerminated), "got {in_flight:?}");
3972 }
3973
3974 #[tokio::test]
3978 async fn test_marked_restarted_client_fails_pending_with_server_restarted() {
3979 let (mut client, mut server) = fake_lsp_client();
3980 let mut reader = BufReader::new(&mut server.write_stdout);
3981
3982 let in_flight_client = client.clone();
3983 let in_flight = tokio::spawn(async move {
3984 in_flight_client
3985 .request::<_, Value>(
3986 "textDocument/hover",
3987 serde_json::json!({}),
3988 Duration::from_secs(30),
3989 )
3990 .await
3991 });
3992 let _ = read_framed_message(&mut reader).await;
3993
3994 client.mark_restarted(ServerId::from_static("rust"));
3995 client
3996 .shutdown_until(Instant::now() + Duration::from_millis(200))
3997 .await
3998 .ok();
3999
4000 let in_flight = timeout(Duration::from_secs(1), in_flight)
4001 .await
4002 .unwrap_or_else(|_| panic!("in-flight request must be failed by the drain"))
4003 .unwrap();
4004 assert!(
4005 matches!(&in_flight, Err(Error::ServerRestarted { server_id }) if server_id.as_str() == "rust"),
4006 "got {in_flight:?}"
4007 );
4008 }
4009
4010 #[tokio::test]
4017 async fn test_request_after_shutdown_fails_fast_instead_of_hanging() {
4018 let (client, _server) = fake_lsp_client();
4019 let post_shutdown_client = client.clone();
4020
4021 client.shutdown().await.unwrap();
4022
4023 let result = tokio::time::timeout(
4024 Duration::from_millis(200),
4025 post_shutdown_client.request::<_, Value>(
4026 "textDocument/hover",
4027 serde_json::json!({}),
4028 Duration::from_secs(30),
4029 ),
4030 )
4031 .await
4032 .unwrap_or_else(|_| {
4033 panic!(
4034 "request after shutdown must fail immediately, not hang until its own timeout"
4035 )
4036 });
4037
4038 assert_matches!(result, Err(Error::ServerTerminated), "got {result:?}");
4039 }
4040 }
4041}