1use std::collections::HashMap;
4use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
5use std::sync::{Arc, Mutex as StdMutex, PoisonError};
6
7use lsp_types::{ErrorCodes, LspErrorCodes};
8use serde::Serialize;
9use serde::de::DeserializeOwned;
10use serde_json::Value;
11use tokio::sync::{Mutex, mpsc, oneshot};
12use tokio::task::JoinHandle;
13use tokio::time::{Duration, Instant, timeout, timeout_at};
14use tracing::{debug, error, trace, warn};
15
16use crate::config::{LanguageId, LspServerConfig, LspSettings, ServerId};
17use crate::error::{BackgroundTask, Error, Result};
18use crate::lsp::transport::{LspTransport, LspTransportReader};
19use crate::lsp::types::{
20 InboundMessage, JsonRpcError, JsonRpcNotification, JsonRpcOutcome, JsonRpcReply,
21 JsonRpcRequest, JsonRpcResponse, JsonRpcVersion, LspNotification, RequestId,
22};
23use crate::lsp::{DropCounter, DropLog, Lane, NotificationSink, ServerMessage};
24use crate::redaction::{RedactedText, Redactions};
25use crate::util::MAX_ERROR_MESSAGE_CALLER_BYTES;
26
27const SERVER_CANCELLED_MAX_RETRIES: u32 = 3;
29
30const SERVER_CANCELLED_INITIAL_DELAY_MS: u64 = 500;
32
33const READER_CHANNEL_CAPACITY: usize = 100;
43
44const READER_TASK_ABORT_GRACE: Duration = Duration::from_secs(1);
54
55pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(10);
59
60pub const MAX_CONSECUTIVE_UNDECODABLE_FRAMES: u32 = 64;
69
70const _: () = assert!(MAX_CONSECUTIVE_UNDECODABLE_FRAMES > 0);
71
72const SHUTDOWN_ABORT_SETTLE: Duration = Duration::from_millis(50);
75
76pub const CONTENT_MODIFIED_RETRY_METHODS: &[&str] = &[
99 "textDocument/signatureHelp",
100 "textDocument/inlayHint",
101 "textDocument/completion",
102 "textDocument/prepareCallHierarchy",
103 "callHierarchy/incomingCalls",
104 "callHierarchy/outgoingCalls",
105 "textDocument/diagnostic",
106 "textDocument/hover",
107 "textDocument/definition",
108 "textDocument/references",
109 "textDocument/implementation",
110 "textDocument/typeDefinition",
111 "textDocument/documentSymbol",
112 "workspace/symbol",
113];
114
115const COMPLETION_TIMEOUT_CAP: Duration = Duration::from_secs(10);
123
124const CODE_ACTION_RESOLVE_TIMEOUT_CAP: Duration = Duration::from_secs(10);
135
136type PendingRequests = HashMap<RequestId, oneshot::Sender<Result<Value>>>;
138
139fn spawn_reader_task(
159 mut reader: LspTransportReader,
160) -> (JoinHandle<()>, mpsc::Receiver<Result<InboundMessage>>) {
161 let (tx, rx) = mpsc::channel(READER_CHANNEL_CAPACITY);
162 let handle = tokio::spawn(async move {
163 let mut run = UndecodableRun::default();
164 loop {
165 let message = reader
166 .receive()
167 .await
168 .and_then(|message| run.observe(message));
169 let is_err = message.is_err();
170 if tx.send(message).await.is_err() || is_err {
171 break;
172 }
173 }
174 });
175 (handle, rx)
176}
177
178#[derive(Debug, Default)]
180struct UndecodableRun {
181 in_a_row: u32,
182 drops: DropCounter,
183}
184
185impl UndecodableRun {
186 fn observe(&mut self, message: InboundMessage) -> Result<InboundMessage> {
196 self.observe_at(message, std::time::Instant::now())
197 }
198
199 fn observe_at(
200 &mut self,
201 message: InboundMessage,
202 now: std::time::Instant,
203 ) -> Result<InboundMessage> {
204 if !message.is_undecodable() {
205 self.in_a_row = 0;
206 return Ok(message);
207 }
208 self.in_a_row = self.in_a_row.saturating_add(1);
209 if self.in_a_row >= MAX_CONSECUTIVE_UNDECODABLE_FRAMES {
210 return Err(Error::LspProtocolError(RedactedText::fixed(
211 "too many consecutive undecodable messages",
212 )));
213 }
214 if let Some(suppressed) = self.drops.record(now) {
215 warn!(
216 "Dropped an undecodable LSP message ({suppressed} others dropped since the last \
217 warning); further ones are logged at debug for a minute"
218 );
219 } else {
220 debug!(
221 "Dropped an undecodable LSP message ({} in a row)",
222 self.in_a_row
223 );
224 }
225 Ok(message)
226 }
227}
228
229#[derive(Debug, Clone, Default)]
232pub enum PendingFailure {
233 #[default]
235 Terminated,
236 Restarted(ServerId),
238}
239
240impl PendingFailure {
241 fn error(&self) -> Error {
242 match self {
243 Self::Terminated => Error::ServerTerminated,
244 Self::Restarted(server_id) => Error::ServerRestarted {
245 server_id: server_id.clone(),
246 },
247 }
248 }
249}
250
251#[derive(Debug)]
259pub struct LspClient {
260 config: LspServerConfig,
262
263 state: Arc<Mutex<super::ServerState>>,
265
266 request_counter: Arc<AtomicI64>,
268
269 command_tx: mpsc::Sender<ClientCommand>,
271
272 pending_requests: Arc<Mutex<PendingRequests>>,
280
281 pending_failure: Arc<StdMutex<PendingFailure>>,
284
285 receiver_task: Option<JoinHandle<Result<()>>>,
287
288 redactions: Arc<Redactions>,
290
291 connection_id: ConnectionId,
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub struct ConnectionId(u64);
302
303impl ConnectionId {
304 fn next() -> Self {
305 static NEXT: AtomicU64 = AtomicU64::new(1);
306 Self(NEXT.fetch_add(1, Ordering::Relaxed))
307 }
308}
309
310impl Clone for LspClient {
311 fn clone(&self) -> Self {
316 Self {
317 config: self.config.clone(),
318 state: Arc::clone(&self.state),
319 request_counter: Arc::clone(&self.request_counter),
320 command_tx: self.command_tx.clone(),
321 pending_requests: Arc::clone(&self.pending_requests),
322 pending_failure: Arc::clone(&self.pending_failure),
323 receiver_task: None,
324 redactions: Arc::clone(&self.redactions),
325 connection_id: self.connection_id,
326 }
327 }
328}
329
330enum ClientCommand {
332 SendRequest { request: JsonRpcRequest },
334 SendNotification {
336 method: String,
337 params: Option<Value>,
338 },
339 Shutdown,
341}
342
343impl LspClient {
344 #[must_use]
349 pub fn new(config: LspServerConfig) -> Self {
350 let (command_tx, _command_rx) = mpsc::channel(1); Self {
356 config,
357 state: Arc::new(Mutex::new(super::ServerState::Uninitialized)),
358 request_counter: Arc::new(AtomicI64::new(1)),
359 command_tx,
360 pending_requests: Arc::new(Mutex::new(HashMap::new())),
361 pending_failure: Arc::default(),
362 receiver_task: None,
363 redactions: Arc::default(),
364 connection_id: ConnectionId::next(),
365 }
366 }
367
368 #[cfg(test)]
372 pub(crate) fn from_transport(
373 config: LspServerConfig,
374 transport: (LspTransport, LspTransportReader),
375 ) -> Self {
376 let state = Arc::new(Mutex::new(super::ServerState::Initializing));
377 let request_counter = Arc::new(AtomicI64::new(1));
378 let pending_requests = Arc::new(Mutex::new(HashMap::new()));
379 let pending_failure = Arc::<StdMutex<PendingFailure>>::default();
380
381 let (command_tx, command_rx) = mpsc::channel(100);
382
383 let receiver_task = tokio::spawn(Self::message_loop(
384 transport,
385 command_rx,
386 Arc::clone(&pending_requests),
387 Arc::clone(&pending_failure),
388 None,
389 None,
390 Arc::default(),
391 Self::shared_settings(&config),
392 ));
393
394 Self {
395 config,
396 state,
397 request_counter,
398 command_tx,
399 pending_requests,
400 pending_failure,
401 receiver_task: Some(receiver_task),
402 redactions: Arc::default(),
403 connection_id: ConnectionId::next(),
404 }
405 }
406
407 pub(crate) fn from_transport_with_notifications(
416 config: LspServerConfig,
417 transport: (LspTransport, LspTransportReader),
418 notification_tx: NotificationSink,
419 lifecycle_tx: mpsc::Sender<LspNotification>,
420 redactions: Arc<Redactions>,
421 ) -> Self {
422 let state = Arc::new(Mutex::new(super::ServerState::Initializing));
423 let request_counter = Arc::new(AtomicI64::new(1));
424 let pending_requests = Arc::new(Mutex::new(HashMap::new()));
425 let pending_failure = Arc::<StdMutex<PendingFailure>>::default();
426
427 let (command_tx, command_rx) = mpsc::channel(100);
428
429 let receiver_task = tokio::spawn(Self::message_loop(
430 transport,
431 command_rx,
432 Arc::clone(&pending_requests),
433 Arc::clone(&pending_failure),
434 Some(notification_tx),
435 Some(lifecycle_tx),
436 Arc::clone(&redactions),
437 Self::shared_settings(&config),
438 ));
439
440 Self {
441 config,
442 state,
443 request_counter,
444 command_tx,
445 pending_requests,
446 pending_failure,
447 receiver_task: Some(receiver_task),
448 redactions,
449 connection_id: ConnectionId::next(),
450 }
451 }
452
453 fn shared_settings(config: &LspServerConfig) -> Option<Arc<LspSettings>> {
455 config.settings.clone().map(Arc::new)
456 }
457
458 pub(crate) const fn connection_id(&self) -> ConnectionId {
460 self.connection_id
461 }
462
463 pub(crate) const fn redactions(&self) -> &Arc<Redactions> {
465 &self.redactions
466 }
467
468 #[must_use]
470 pub const fn language_id(&self) -> &LanguageId {
471 &self.config.language_id
472 }
473
474 pub async fn state(&self) -> super::ServerState {
476 *self.state.lock().await
477 }
478
479 #[must_use]
519 pub const fn request_timeout(&self) -> Duration {
520 self.config.request_timeout_seconds.as_duration()
521 }
522
523 #[must_use]
547 pub fn completion_timeout(&self) -> Duration {
548 self.request_timeout().min(COMPLETION_TIMEOUT_CAP)
549 }
550
551 #[must_use]
571 pub fn code_action_resolve_timeout(&self) -> Duration {
572 self.request_timeout().min(CODE_ACTION_RESOLVE_TIMEOUT_CAP)
573 }
574
575 async fn register_and_send_request(
578 &self,
579 request: JsonRpcRequest,
580 response_tx: oneshot::Sender<Result<Value>>,
581 ) -> Result<()> {
582 let id = request.id.clone();
583
584 self.pending_requests
585 .lock()
586 .await
587 .insert(id.clone(), response_tx);
588
589 if self
590 .command_tx
591 .send(ClientCommand::SendRequest { request })
592 .await
593 .is_err()
594 {
595 self.pending_requests.lock().await.remove(&id);
596 return Err(Error::ServerTerminated);
597 }
598
599 Ok(())
600 }
601
602 pub async fn request<P, R>(
631 &self,
632 method: &str,
633 params: P,
634 timeout_duration: Duration,
635 ) -> Result<R>
636 where
637 P: Serialize,
638 R: DeserializeOwned,
639 {
640 Box::pin(self.request_inner(method, params, timeout_duration))
641 .await
642 .map_err(UnclassifiedError::surface)
643 }
644
645 async fn request_inner<P, R>(
646 &self,
647 method: &str,
648 params: P,
649 timeout_duration: Duration,
650 ) -> std::result::Result<R, UnclassifiedError>
651 where
652 P: Serialize,
653 R: DeserializeOwned,
654 {
655 let params_value = Self::omit_null_params(
656 serde_json::to_value(params).map_err(|e| UnclassifiedError::logged(e.into()))?,
657 );
658 let mut delay_ms = SERVER_CANCELLED_INITIAL_DELAY_MS;
659
660 for attempt in 0..=SERVER_CANCELLED_MAX_RETRIES {
661 if attempt > 0 {
662 debug!(
663 "Retrying {} (attempt {}/{}), backoff={}ms",
664 method, attempt, SERVER_CANCELLED_MAX_RETRIES, delay_ms
665 );
666 tokio::time::sleep(Duration::from_millis(delay_ms)).await;
667 delay_ms = delay_ms.saturating_mul(2);
668 }
669
670 let id = RequestId::Number(self.request_counter.fetch_add(1, Ordering::SeqCst));
671 let (response_tx, response_rx) = oneshot::channel();
672 let request = JsonRpcRequest {
673 jsonrpc: JsonRpcVersion,
674 id: id.clone(),
675 method: method.to_string(),
676 params: params_value.clone(),
677 };
678
679 debug!("Sending request: {} (id={:?})", method, id);
680
681 self.register_and_send_request(request, response_tx)
682 .await
683 .map_err(UnclassifiedError::logged)?;
684
685 let outcome = match timeout(timeout_duration, response_rx).await {
686 Ok(received) => {
687 received.map_err(|_| UnclassifiedError::logged(Error::ServerTerminated))?
688 }
689 Err(_elapsed) => {
690 self.pending_requests.lock().await.remove(&id);
695 return Err(UnclassifiedError::logged(Error::Timeout(timeout_duration)));
696 }
697 };
698
699 match outcome {
700 Ok(result_value) => {
701 return serde_json::from_value(result_value).map_err(|e| {
702 UnclassifiedError::logged(
703 self.redactions.protocol_error(format_args!(
704 "Failed to deserialize response: {e}"
705 )),
706 )
707 });
708 }
709 Err(Error::LspServerError {
710 code,
711 message,
712 data,
713 }) if (LspErrorCodes::from(code) == LspErrorCodes::ServerCancelled
714 || (LspErrorCodes::from(code) == LspErrorCodes::ContentModified
715 && CONTENT_MODIFIED_RETRY_METHODS.contains(&method)))
716 && Self::should_retrigger(data.as_ref()) =>
717 {
718 if attempt == SERVER_CANCELLED_MAX_RETRIES {
719 error!(
724 "LSP error response: {} (code {}) on '{}' (id={:?}), retries exhausted",
725 Self::truncate_error_message_for_log(&message),
726 code,
727 method,
728 id
729 );
730 return Err(UnclassifiedError::logged(Error::LspServerError {
731 code,
732 message,
733 data,
734 }));
735 }
736 warn!(
737 "LSP error response: {} (code {}) on '{}' (id={:?}), will retry",
738 Self::truncate_error_message_for_log(&message),
739 code,
740 method,
741 id
742 );
743 }
745 Err(Error::LspServerError {
746 code,
747 message,
748 data,
749 }) => {
750 return Err(UnclassifiedError::server_response(
751 ServerErrorResponse::new(code, message, data),
752 method,
753 id,
754 ));
755 }
756 Err(e) => return Err(UnclassifiedError::logged(e)),
757 }
758 }
759
760 Err(UnclassifiedError::logged(Error::ServerTerminated))
761 }
762
763 pub(crate) async fn request_typed_classified<R>(
771 &self,
772 params: R::Params,
773 timeout_duration: Duration,
774 ) -> std::result::Result<R::Result, UnclassifiedError>
775 where
776 R: lsp_types::Request,
777 {
778 Box::pin(self.request_inner(R::METHOD.as_str(), params, timeout_duration)).await
779 }
780
781 pub async fn request_typed<R>(
791 &self,
792 params: R::Params,
793 timeout_duration: Duration,
794 ) -> Result<R::Result>
795 where
796 R: lsp_types::Request,
797 {
798 self.request(R::METHOD.as_str(), params, timeout_duration)
799 .await
800 }
801
802 fn should_retrigger(data: Option<&Value>) -> bool {
816 data.is_none_or(|v| {
817 v.get("retriggerRequest")
818 .and_then(Value::as_bool)
819 .unwrap_or(true)
820 })
821 }
822
823 pub(crate) fn is_message_loop_finished(&self) -> bool {
829 self.receiver_task
830 .as_ref()
831 .is_some_and(JoinHandle::is_finished)
832 }
833
834 pub(crate) async fn fail_pending_requests(&self) {
842 Self::drain_and_fail_pending(&self.pending_requests, &self.pending_failure).await;
843 }
844
845 pub(crate) fn mark_restarted(&self, server_id: ServerId) {
849 *self
850 .pending_failure
851 .lock()
852 .unwrap_or_else(PoisonError::into_inner) = PendingFailure::Restarted(server_id);
853 }
854
855 async fn drain_and_fail_pending(
859 pending: &Arc<Mutex<PendingRequests>>,
860 failure: &StdMutex<PendingFailure>,
861 ) {
862 for (_, sender) in pending.lock().await.drain() {
863 let error = failure
864 .lock()
865 .unwrap_or_else(PoisonError::into_inner)
866 .error();
867 let _ = sender.send(Err(error));
868 }
869 }
870
871 pub async fn notify<P>(&self, method: &str, params: P) -> Result<()>
877 where
878 P: Serialize,
879 {
880 let params_value = Self::omit_null_params(serde_json::to_value(params)?);
881
882 debug!("Sending notification: {}", method);
883
884 self.command_tx
885 .send(ClientCommand::SendNotification {
886 method: method.to_string(),
887 params: params_value,
888 })
889 .await
890 .map_err(|_| Error::ServerTerminated)?;
891
892 Ok(())
893 }
894
895 pub async fn notify_typed<N>(&self, params: N::Params) -> Result<()>
903 where
904 N: lsp_types::Notification,
905 {
906 self.notify(N::METHOD.as_str(), params).await
907 }
908
909 #[allow(
922 clippy::arithmetic_side_effects,
923 reason = "SHUTDOWN_TIMEOUT is a small constant"
924 )]
925 pub async fn shutdown(mut self) -> Result<()> {
926 self.shutdown_until(Instant::now() + SHUTDOWN_TIMEOUT).await
927 }
928
929 pub(crate) async fn shutdown_until(&mut self, deadline: Instant) -> Result<()> {
931 debug!("Shutting down LSP client");
932
933 let mut task = self.receiver_task.take();
934 let command_tx = &self.command_tx;
935 let outcome = timeout_at(deadline, async {
936 let _ = command_tx.send(ClientCommand::Shutdown).await;
937 match task.as_mut() {
938 Some(task) => match task.await {
939 Ok(result) => result,
940 Err(source) => Err(Error::TaskFailed {
941 task: BackgroundTask::LspReceiver,
942 source,
943 }),
944 },
945 None => Ok(()),
946 }
947 })
948 .await;
949
950 let result = outcome.unwrap_or(Err(Error::ShutdownTimeout));
951 if matches!(result, Err(Error::ShutdownTimeout)) {
952 if let Some(task) = task {
953 task.abort();
954 let _ = timeout(SHUTDOWN_ABORT_SETTLE, task).await;
957 }
958 Self::drain_and_fail_pending(&self.pending_requests, &self.pending_failure).await;
959 }
960
961 *self.state.lock().await = super::ServerState::Shutdown;
962
963 result
964 }
965
966 #[allow(
973 clippy::too_many_arguments,
974 reason = "one-to-one with the spawned loop's channels and shared state"
975 )]
976 async fn message_loop(
977 transport: (LspTransport, LspTransportReader),
978 mut command_rx: mpsc::Receiver<ClientCommand>,
979 pending_requests: Arc<Mutex<PendingRequests>>,
980 pending_failure: Arc<StdMutex<PendingFailure>>,
981 notification_tx: Option<NotificationSink>,
982 lifecycle_tx: Option<mpsc::Sender<LspNotification>>,
983 redactions: Arc<Redactions>,
984 settings: Option<Arc<LspSettings>>,
985 ) -> Result<()> {
986 debug!("Message loop started");
987 let (mut transport, reader) = transport;
988 let (reader_handle, mut msg_rx) = spawn_reader_task(reader);
989 let result = {
990 let _abort_reader_on_drop = crate::util::AbortOnDrop(&reader_handle);
992 Self::message_loop_inner(
993 &mut transport,
994 &mut msg_rx,
995 &mut command_rx,
996 &pending_requests,
997 notification_tx.as_ref(),
998 lifecycle_tx.as_ref(),
999 &redactions,
1000 settings.as_deref(),
1001 )
1002 .await
1003 };
1004 let _ = timeout(READER_TASK_ABORT_GRACE, reader_handle).await;
1005 drop(command_rx);
1012 Self::drain_and_fail_pending(&pending_requests, &pending_failure).await;
1016 if let Err(ref e) = result {
1017 error!("Message loop exiting with error: {}", e);
1018 } else {
1019 debug!("Message loop exiting normally");
1020 }
1021 result
1022 }
1023
1024 fn omit_null_params(params: Value) -> Option<Value> {
1028 if params.is_null() { None } else { Some(params) }
1029 }
1030
1031 fn truncate_error_message_for_log(message: &str) -> String {
1039 crate::util::truncate_str(message, crate::util::MAX_LOG_STRING_BYTES)
1040 }
1041
1042 fn reaches_a_lane(
1071 notification: &LspNotification,
1072 has_sink: bool,
1073 lifecycle_tx: Option<&mpsc::Sender<LspNotification>>,
1074 ) -> bool {
1075 match notification {
1076 LspNotification::PublishDiagnostics(_) => false,
1077 LspNotification::LogMessage(_) | LspNotification::ShowMessage(_) => has_sink,
1078 other => Self::lifecycle_lane(other, lifecycle_tx).is_some(),
1079 }
1080 }
1081
1082 fn lifecycle_lane<'a>(
1083 notification: &LspNotification,
1084 lifecycle_tx: Option<&'a mpsc::Sender<LspNotification>>,
1085 ) -> Option<&'a mpsc::Sender<LspNotification>> {
1086 match notification {
1087 LspNotification::PublishDiagnostics(_)
1088 | LspNotification::LogMessage(_)
1089 | LspNotification::ShowMessage(_) => None,
1090 LspNotification::Progress(params) => {
1091 crate::lsp::types::ProgressKind::from_value(¶ms.value).and(lifecycle_tx)
1092 }
1093 LspNotification::Other { method, .. } if method.as_ref() == "$/progress" => None,
1094 LspNotification::Other { .. } => lifecycle_tx,
1095 }
1096 }
1097
1098 #[allow(
1099 clippy::too_many_lines,
1100 clippy::too_many_arguments,
1101 reason = "one select loop over the transport, channels and shared state"
1102 )]
1103 async fn message_loop_inner(
1104 transport: &mut LspTransport,
1105 msg_rx: &mut mpsc::Receiver<Result<InboundMessage>>,
1106 command_rx: &mut mpsc::Receiver<ClientCommand>,
1107 pending_requests: &Arc<Mutex<PendingRequests>>,
1108 notification_tx: Option<&NotificationSink>,
1109 lifecycle_tx: Option<&mpsc::Sender<LspNotification>>,
1110 redactions: &Redactions,
1111 settings: Option<&LspSettings>,
1112 ) -> Result<()> {
1113 loop {
1114 tokio::select! {
1115 Some(command) = command_rx.recv() => {
1116 match command {
1117 ClientCommand::SendRequest { request } => {
1118 let value = serde_json::to_value(&request)?;
1119 transport.send(&value).await?;
1120 }
1121 ClientCommand::SendNotification { method, params } => {
1122 let notification = serde_json::to_value(JsonRpcNotification {
1123 jsonrpc: JsonRpcVersion,
1124 method,
1125 params,
1126 })?;
1127 transport.send(¬ification).await?;
1128 }
1129 ClientCommand::Shutdown => {
1130 debug!("Client shutdown requested");
1131 while let Ok(message) = msg_rx.try_recv() {
1135 match message {
1136 Ok(m) => {
1137 Self::handle_inbound_message(
1138 transport,
1139 m,
1140 pending_requests,
1141 notification_tx,
1142 lifecycle_tx,
1143 redactions,
1144 settings,
1145 )
1146 .await?;
1147 }
1148 Err(e) => {
1149 error!("Transport receive error while draining on shutdown: {}", e);
1150 break;
1151 }
1152 }
1153 }
1154 break;
1155 }
1156 }
1157 }
1158
1159 message = msg_rx.recv() => {
1161 let message = match message {
1162 Some(Ok(m)) => m,
1163 Some(Err(e)) => {
1164 error!("Transport receive error: {}", e);
1165 return Err(e);
1166 }
1167 None => {
1168 error!("Transport receive error: reader task ended unexpectedly");
1170 return Err(Error::ServerTerminated);
1171 }
1172 };
1173 Self::handle_inbound_message(
1174 transport,
1175 message,
1176 pending_requests,
1177 notification_tx,
1178 lifecycle_tx,
1179 redactions,
1180 settings,
1181 )
1182 .await?;
1183 }
1184 }
1185 }
1186
1187 Ok(())
1188 }
1189
1190 async fn complete_pending(
1192 pending_requests: &Arc<Mutex<PendingRequests>>,
1193 response: JsonRpcResponse,
1194 redactions: &Redactions,
1195 ) {
1196 trace!("Received response: id={:?}", response.id);
1197
1198 let sender = pending_requests.lock().await.remove(&response.id);
1199
1200 if let Some(sender) = sender {
1201 match response.outcome {
1202 JsonRpcOutcome::Error(error) => {
1203 let message = redactions.apply(&error.message);
1214 trace!(
1215 "LSP error response: {} (code {})",
1216 Self::truncate_error_message_for_log(&message),
1217 error.code
1218 );
1219 let caller_message =
1225 crate::util::truncate_str(&message, MAX_ERROR_MESSAGE_CALLER_BYTES);
1226 let _ = sender.send(Err(Error::LspServerError {
1227 code: error.code,
1228 message: caller_message,
1229 data: error.data,
1230 }));
1231 }
1232 JsonRpcOutcome::Result(result) => {
1233 let _ = sender.send(Ok(result));
1234 }
1235 }
1236 } else {
1237 warn!(
1238 "Received response for unknown request ID: {:?}",
1239 response.id
1240 );
1241 }
1242 }
1243
1244 async fn handle_inbound_message(
1250 transport: &mut LspTransport,
1251 message: InboundMessage,
1252 pending_requests: &Arc<Mutex<PendingRequests>>,
1253 notification_tx: Option<&NotificationSink>,
1254 lifecycle_tx: Option<&mpsc::Sender<LspNotification>>,
1255 redactions: &Redactions,
1256 settings: Option<&LspSettings>,
1257 ) -> Result<()> {
1258 match message {
1259 InboundMessage::Response(response) => {
1260 Self::complete_pending(pending_requests, response, redactions).await;
1261 }
1262 InboundMessage::UndecodableResponse { id } => {
1263 warn!("Failed the request answered by an undecodable response: id={id:?}");
1264 let sender = pending_requests.lock().await.remove(&id);
1265 if let Some(sender) = sender {
1266 let _ = sender.send(Err(Error::LspProtocolError(RedactedText::fixed(
1267 "undecodable response",
1268 ))));
1269 }
1270 }
1271 InboundMessage::UndecodableRequest { id } => {
1272 debug!("Answering an undecodable server request with an error: id={id:?}");
1273 let reply = JsonRpcReply::new(
1274 id,
1275 JsonRpcOutcome::Error(JsonRpcError::new(
1276 ErrorCodes::InvalidRequest,
1277 "Invalid Request: the request could not be decoded",
1278 )),
1279 );
1280 transport.send(&serde_json::to_value(&reply)?).await?;
1281 }
1282 InboundMessage::UndecodableNotification | InboundMessage::UndecodableFrame => {}
1283 InboundMessage::Request(request) => {
1284 debug!(
1285 "Received server request: {} (id={:?})",
1286 request.method, request.id
1287 );
1288 let reply = Self::server_request_reply(request, settings);
1289 let value = serde_json::to_value(&reply)?;
1290 transport.send(&value).await?;
1291 }
1292 InboundMessage::Notification(notification) => {
1293 debug!("Received notification: {}", notification.method);
1294
1295 let mut typed = LspNotification::parse(¬ification.method, notification.params);
1297 Self::redact_notification(&mut typed, redactions);
1298
1299 if tracing::enabled!(tracing::Level::TRACE)
1300 && Self::reaches_a_lane(&typed, notification_tx.is_some(), lifecycle_tx)
1301 {
1302 trace!(
1303 "Forwarding notification: {}",
1304 redactions.apply(&format!("{typed:?}"))
1305 );
1306 }
1307
1308 let delivered = match typed {
1312 LspNotification::PublishDiagnostics(params) => {
1313 if let Some(sink) = notification_tx {
1314 debug!(
1315 "Forwarding diagnostics for {}: {} items",
1316 params.uri.as_ref(),
1317 params.diagnostics.len()
1318 );
1319 sink.publishes().publish(params);
1320 }
1321 return Ok(());
1322 }
1323 LspNotification::LogMessage(params) => notification_tx.map(|sink| {
1324 (
1325 Lane::Notification,
1326 sink.messages().try_send(ServerMessage::Log(params)).is_ok(),
1327 )
1328 }),
1329 LspNotification::ShowMessage(params) => notification_tx.map(|sink| {
1330 (
1331 Lane::Notification,
1332 sink.messages()
1333 .try_send(ServerMessage::Show(params))
1334 .is_ok(),
1335 )
1336 }),
1337 other => Self::lifecycle_lane(&other, lifecycle_tx)
1338 .map(|tx| (Lane::Lifecycle, tx.try_send(other).is_ok())),
1339 };
1340
1341 if let Some((lane, false)) = delivered {
1342 DropLog::of(lane).record(¬ification.method);
1343 }
1344 }
1345 }
1346
1347 Ok(())
1348 }
1349
1350 pub(crate) fn redact_notification(notification: &mut LspNotification, redactions: &Redactions) {
1354 if redactions.is_empty() {
1355 return;
1356 }
1357 match notification {
1358 LspNotification::LogMessage(params) => {
1359 redactions.redact_in_place(&mut params.message);
1360 }
1361 LspNotification::ShowMessage(params) => {
1362 redactions.redact_in_place(&mut params.message);
1363 }
1364 LspNotification::PublishDiagnostics(params) => {
1365 for diagnostic in &mut params.diagnostics {
1366 redactions.redact_diagnostic(diagnostic);
1367 }
1368 }
1369 LspNotification::Progress(params) => redactions.redact_progress(&mut params.value),
1370 LspNotification::Other { .. } => {}
1371 }
1372 }
1373
1374 fn server_request_reply(
1375 request: JsonRpcRequest,
1376 settings: Option<&LspSettings>,
1377 ) -> JsonRpcReply {
1378 let outcome =
1379 Self::server_request_result(&request.method, request.params.as_ref(), settings);
1380 JsonRpcReply::new(request.id, outcome.into())
1381 }
1382
1383 fn server_request_result(
1384 method: &str,
1385 params: Option<&Value>,
1386 settings: Option<&LspSettings>,
1387 ) -> std::result::Result<Value, JsonRpcError> {
1388 match method {
1389 "client/registerCapability"
1390 | "client/unregisterCapability"
1391 | "workspace/workspaceFolders"
1392 | "workspace/diagnostic/refresh"
1393 | "workspace/semanticTokens/refresh"
1394 | "workspace/inlayHint/refresh"
1395 | "workspace/codeLens/refresh"
1396 | "window/showMessageRequest"
1397 | "window/workDoneProgress/create" => Ok(Value::Null),
1398 "workspace/configuration" => Self::workspace_configuration_result(params, settings),
1399 "workspace/applyEdit" => Ok(serde_json::json!({ "applied": false })),
1400 _ => Err(JsonRpcError::new(
1401 ErrorCodes::MethodNotFound,
1402 format!("Unhandled server request: {method}"),
1403 )),
1404 }
1405 }
1406
1407 fn workspace_configuration_result(
1416 params: Option<&Value>,
1417 settings: Option<&LspSettings>,
1418 ) -> std::result::Result<Value, JsonRpcError> {
1419 let items = params
1420 .and_then(|params| params.get("items"))
1421 .and_then(Value::as_array);
1422 let Some(settings) = settings else {
1423 return Ok(Value::Array(vec![Value::Null; items.map_or(0, Vec::len)]));
1424 };
1425 let items = items.ok_or_else(|| {
1426 JsonRpcError::new(
1427 ErrorCodes::InvalidParams,
1428 "Invalid params for workspace/configuration",
1429 )
1430 })?;
1431 let sections = items
1432 .iter()
1433 .map(|item| match ConfigurationItem::parse(item) {
1434 ConfigurationItem::Whole => settings.section(None),
1435 ConfigurationItem::Section(section) => settings.section(Some(section)),
1436 ConfigurationItem::Uninterpretable => Value::Null,
1437 })
1438 .collect();
1439 Ok(Value::Array(sections))
1440 }
1441}
1442
1443fn log_surfaced_error(method: &str, id: &RequestId, code: i32, message: &str) {
1446 error!(
1447 "LSP error response: {} (code {}) on '{}' (id={:?})",
1448 LspClient::truncate_error_message_for_log(message),
1449 code,
1450 method,
1451 id
1452 );
1453}
1454
1455#[derive(Debug)]
1462#[must_use = "call `surface()` or `handled()` to log and unwrap the error"]
1463pub struct UnclassifiedError(Box<Unclassified>);
1464
1465#[derive(Debug)]
1468struct ServerErrorResponse {
1469 code: i32,
1470 message: String,
1471 data: Option<Value>,
1472 error: std::sync::OnceLock<Error>,
1474}
1475
1476impl ServerErrorResponse {
1477 const fn new(code: i32, message: String, data: Option<Value>) -> Self {
1478 Self {
1479 code,
1480 message,
1481 data,
1482 error: std::sync::OnceLock::new(),
1483 }
1484 }
1485
1486 const fn code_and_message(&self) -> (i32, &str) {
1488 (self.code, self.message.as_str())
1489 }
1490
1491 fn as_error(&self) -> &Error {
1493 self.error.get_or_init(|| Error::LspServerError {
1494 code: self.code,
1495 message: self.message.clone(),
1496 data: self.data.clone(),
1497 })
1498 }
1499
1500 fn into_error(self) -> Error {
1501 self.error.into_inner().unwrap_or(Error::LspServerError {
1502 code: self.code,
1503 message: self.message,
1504 data: self.data,
1505 })
1506 }
1507}
1508
1509#[derive(Debug)]
1512enum Unclassified {
1513 Logged(Error),
1514 ServerResponse {
1515 response: ServerErrorResponse,
1516 method: String,
1517 id: RequestId,
1518 },
1519}
1520
1521impl Unclassified {
1522 fn error(&self) -> &Error {
1523 match self {
1524 Self::Logged(error) => error,
1525 Self::ServerResponse { response, .. } => response.as_error(),
1526 }
1527 }
1528
1529 fn finish(self, report: impl FnOnce(&str, &RequestId, i32, &str)) -> Error {
1532 match self {
1533 Self::Logged(error) => error,
1534 Self::ServerResponse {
1535 response,
1536 method,
1537 id,
1538 } => {
1539 let (code, message) = response.code_and_message();
1540 report(&method, &id, code, message);
1541 response.into_error()
1542 }
1543 }
1544 }
1545}
1546
1547fn log_handled_error(method: &str, id: &RequestId, code: i32, message: &str) {
1548 debug!(
1549 "LSP error response handled by the caller: {} (code {}) on '{}' (id={:?})",
1550 LspClient::truncate_error_message_for_log(message),
1551 code,
1552 method,
1553 id
1554 );
1555}
1556
1557impl UnclassifiedError {
1558 fn logged(error: Error) -> Self {
1559 Self(Box::new(Unclassified::Logged(error)))
1560 }
1561
1562 fn server_response(response: ServerErrorResponse, method: &str, id: RequestId) -> Self {
1563 Self(Box::new(Unclassified::ServerResponse {
1564 response,
1565 method: method.to_owned(),
1566 id,
1567 }))
1568 }
1569
1570 fn take(mut self) -> Unclassified {
1573 std::mem::replace(&mut *self.0, Unclassified::Logged(Error::ServerTerminated))
1574 }
1575
1576 pub(crate) fn error(&self) -> &Error {
1578 self.0.error()
1579 }
1580
1581 pub(crate) fn surface(self) -> Error {
1584 self.take().finish(log_surfaced_error)
1585 }
1586
1587 pub(crate) fn handled(self) -> Error {
1590 self.take().finish(log_handled_error)
1591 }
1592}
1593
1594impl Drop for UnclassifiedError {
1597 fn drop(&mut self) {
1598 let inner = std::mem::replace(&mut *self.0, Unclassified::Logged(Error::ServerTerminated));
1599 drop(inner.finish(log_surfaced_error));
1600 }
1601}
1602
1603enum ConfigurationItem<'a> {
1605 Whole,
1606 Section(&'a str),
1607 Uninterpretable,
1608}
1609
1610impl<'a> ConfigurationItem<'a> {
1611 fn parse(item: &'a Value) -> Self {
1612 let Value::Object(object) = item else {
1613 return Self::Uninterpretable;
1614 };
1615 match object.get("section") {
1616 None | Some(Value::Null) => Self::Whole,
1617 Some(Value::String(section)) => Self::Section(section),
1618 Some(_) => Self::Uninterpretable,
1619 }
1620 }
1621}
1622
1623#[cfg(test)]
1624mod tests {
1625 use std::assert_matches;
1626
1627 use super::*;
1628 use crate::config::TimeoutSecs;
1629 use crate::lsp::types::JsonRpcResponse;
1630
1631 #[test]
1632 fn test_unclassified_server_response_always_reports_code_and_message() {
1633 let unclassified = UnclassifiedError::server_response(
1634 ServerErrorResponse::new(-32001, "boom".to_owned(), None),
1635 "textDocument/hover",
1636 RequestId::Number(3),
1637 );
1638 let mut reported = None;
1639
1640 let error = unclassified.take().finish(|method, id, code, message| {
1641 reported = Some((method.to_owned(), id.clone(), code, message.to_owned()));
1642 });
1643
1644 assert_eq!(
1645 reported,
1646 Some((
1647 "textDocument/hover".to_owned(),
1648 RequestId::Number(3),
1649 -32001,
1650 "boom".to_owned()
1651 ))
1652 );
1653 assert_matches!(error, Error::LspServerError { code: -32001, .. });
1654 }
1655
1656 #[tokio::test]
1659 async fn test_forwarded_notification_trace_is_redacted() {
1660 use tracing_subscriber::prelude::*;
1661
1662 use crate::test_lsp::CapturedLogs;
1663
1664 let redactions =
1665 Redactions::new([("API_TOKEN".to_owned(), "SuperSecretValue123".to_owned())]);
1666 let logs = CapturedLogs::default();
1667 let _guard = tracing::subscriber::set_default(
1668 tracing_subscriber::registry()
1669 .with(tracing_subscriber::filter::LevelFilter::TRACE)
1670 .with(logs.clone()),
1671 );
1672 let (mut transport, _reader) = LspTransport::new(tokio::io::sink(), tokio::io::empty());
1673 let (lifecycle_tx, mut lifecycle_rx) = mpsc::channel(4);
1674 let pending = Arc::new(Mutex::new(HashMap::new()));
1675 let notification = InboundMessage::Notification(JsonRpcNotification {
1676 jsonrpc: JsonRpcVersion,
1677 method: "custom/echo".to_string(),
1678 params: Some(serde_json::json!({"env": "SuperSecretValue123"})),
1679 });
1680
1681 LspClient::handle_inbound_message(
1682 &mut transport,
1683 notification,
1684 &pending,
1685 None,
1686 Some(&lifecycle_tx),
1687 &redactions,
1688 None,
1689 )
1690 .await
1691 .unwrap();
1692
1693 assert!(lifecycle_rx.recv().await.is_some());
1694 let output = logs.messages().join("\n");
1695 assert!(output.contains("Forwarding notification"), "{output}");
1696 assert!(!output.contains("SuperSecretValue123"), "{output}");
1697 }
1698
1699 fn secret_diagnostics_params() -> Value {
1700 let range = serde_json::json!({
1701 "start": {"line": 0, "character": 0},
1702 "end": {"line": 0, "character": 1}
1703 });
1704 serde_json::json!({
1705 "uri": "file:///w/main.rs",
1706 "diagnostics": [{
1707 "range": range,
1708 "message": "leak SuperSecretValue123",
1709 "data": {"hint": ["SuperSecretValue123"]}
1710 }]
1711 })
1712 }
1713
1714 #[tokio::test]
1717 async fn test_reader_task_forwards_pushed_diagnostics_redacted() {
1718 use crate::test_lsp::{fake_lsp_client_with_redactions, write_notification};
1719
1720 let redactions =
1721 Redactions::new([("API_TOKEN".to_owned(), "SuperSecretValue123".to_owned())]);
1722 let (_client, mut server, mut lanes) = fake_lsp_client_with_redactions(redactions);
1723
1724 write_notification(
1725 &mut server.read_half_stdin,
1726 "textDocument/publishDiagnostics",
1727 secret_diagnostics_params(),
1728 )
1729 .await;
1730
1731 let forwarded = timeout(Duration::from_secs(2), lanes.publishes.recv())
1732 .await
1733 .unwrap()
1734 .unwrap();
1735 let shown = format!("{forwarded:?}");
1736 assert!(shown.contains("[redacted:API_TOKEN]"), "{shown}");
1737 assert!(!shown.contains("SuperSecretValue123"), "{shown}");
1738 }
1739
1740 #[test]
1742 fn test_redact_notification_leaves_text_alone_without_secrets() {
1743 let mut notification = LspNotification::parse(
1744 "textDocument/publishDiagnostics",
1745 Some(secret_diagnostics_params()),
1746 );
1747 let before = format!("{notification:?}");
1748
1749 LspClient::redact_notification(&mut notification, &Redactions::default());
1750 assert_eq!(format!("{notification:?}"), before);
1751
1752 let unrelated = Redactions::new([("API_TOKEN".to_owned(), "NotInTheText99".to_owned())]);
1753 LspClient::redact_notification(&mut notification, &unrelated);
1754 assert_eq!(format!("{notification:?}"), before);
1755
1756 let matching =
1757 Redactions::new([("API_TOKEN".to_owned(), "SuperSecretValue123".to_owned())]);
1758 LspClient::redact_notification(&mut notification, &matching);
1759 assert!(!format!("{notification:?}").contains("SuperSecretValue123"));
1760 }
1761
1762 #[test]
1763 fn test_request_id_generation() {
1764 let counter = AtomicI64::new(1);
1765
1766 let id1 = counter.fetch_add(1, Ordering::SeqCst);
1767 let id2 = counter.fetch_add(1, Ordering::SeqCst);
1768 let id3 = counter.fetch_add(1, Ordering::SeqCst);
1769
1770 assert_eq!(id1, 1);
1771 assert_eq!(id2, 2);
1772 assert_eq!(id3, 3);
1773 }
1774
1775 #[test]
1776 fn test_client_creation() {
1777 let config = LspServerConfig::rust_analyzer();
1778
1779 let client = LspClient::new(config);
1780 assert_eq!(client.language_id(), "rust");
1781 }
1782
1783 #[test]
1784 fn test_client_clone() {
1785 let config = LspServerConfig::rust_analyzer();
1786 let client = LspClient::new(config);
1787
1788 #[allow(clippy::redundant_clone, reason = "the test exercises `Clone` itself")]
1789 let cloned = client.clone();
1790 assert_eq!(cloned.language_id(), "rust");
1791
1792 assert!(
1793 cloned.receiver_task.is_none(),
1794 "Cloned client should not own receiver task"
1795 );
1796 }
1797
1798 #[test]
1799 fn test_request_timeout_and_completion_timeout_at_default() {
1800 let config = LspServerConfig::rust_analyzer();
1801 let client = LspClient::new(config);
1802
1803 assert_eq!(client.request_timeout(), Duration::from_secs(30));
1804 assert_eq!(client.completion_timeout(), Duration::from_secs(10));
1805 }
1806
1807 #[test]
1808 fn test_completion_timeout_clamps_to_ten_seconds() {
1809 for secs in [1, 2, 3, 30, 300] {
1810 let mut config = LspServerConfig::rust_analyzer();
1811 config.request_timeout_seconds = TimeoutSecs::new(secs).unwrap();
1812 let client = LspClient::new(config);
1813
1814 assert_eq!(
1815 client.completion_timeout(),
1816 Duration::from_secs(secs.min(10)),
1817 "request_timeout_seconds={secs}"
1818 );
1819 assert!(client.completion_timeout() <= client.request_timeout());
1820 }
1821 }
1822
1823 #[test]
1824 fn test_code_action_resolve_timeout_clamps_to_ten_seconds() {
1825 for secs in [1, 2, 3, 30, 300] {
1826 let mut config = LspServerConfig::rust_analyzer();
1827 config.request_timeout_seconds = TimeoutSecs::new(secs).unwrap();
1828 let client = LspClient::new(config);
1829
1830 assert_eq!(
1831 client.code_action_resolve_timeout(),
1832 Duration::from_secs(secs.min(10)),
1833 "request_timeout_seconds={secs}"
1834 );
1835 assert!(client.code_action_resolve_timeout() <= client.request_timeout());
1836 }
1837 }
1838
1839 #[test]
1840 fn test_request_timeout_independent_per_server() {
1841 let mut config_a = LspServerConfig::rust_analyzer();
1842 config_a.request_timeout_seconds = TimeoutSecs::new(5).unwrap();
1843 let mut config_b = LspServerConfig::pyright();
1844 config_b.request_timeout_seconds = TimeoutSecs::new(15).unwrap();
1845
1846 let client_a = LspClient::new(config_a);
1847 let client_b = LspClient::new(config_b);
1848
1849 assert_eq!(client_a.request_timeout(), Duration::from_secs(5));
1850 assert_eq!(client_b.request_timeout(), Duration::from_secs(15));
1851 }
1852
1853 #[test]
1854 fn test_register_capability_request_is_acknowledged() {
1855 let request = JsonRpcRequest {
1856 jsonrpc: JsonRpcVersion,
1857 id: RequestId::String("ts1".to_string()),
1858 method: "client/registerCapability".to_string(),
1859 params: Some(serde_json::json!({ "registrations": [] })),
1860 };
1861
1862 let reply = reply_json(request);
1863
1864 assert_eq!(reply["id"], serde_json::json!("ts1"));
1865 assert_eq!(reply["jsonrpc"], serde_json::json!("2.0"));
1866 assert_eq!(reply["result"], Value::Null);
1867 assert!(reply.get("error").is_none());
1868 }
1869
1870 #[test]
1871 fn test_workspace_configuration_request_returns_null_per_item_without_settings() {
1872 let result = LspClient::workspace_configuration_result(
1873 Some(&serde_json::json!({
1874 "items": [{ "section": "typescript" }, { "section": "editor" }]
1875 })),
1876 None,
1877 );
1878
1879 assert_eq!(result.unwrap(), serde_json::json!([null, null]));
1880 }
1881
1882 fn sample_settings() -> LspSettings {
1883 serde_json::from_value(serde_json::json!({
1884 "python.analysis.typeCheckingMode": "strict",
1885 "gopls": { "ui.semanticTokens": true }
1886 }))
1887 .unwrap()
1888 }
1889
1890 #[test]
1891 fn test_workspace_configuration_serves_settings_sections() {
1892 let settings = sample_settings();
1893 let result = LspClient::workspace_configuration_result(
1894 Some(&serde_json::json!({
1895 "items": [
1896 { "section": "python.analysis" },
1897 { "section": "gopls", "scopeUri": "file:///ignored" },
1898 { "section": "missing" },
1899 {}
1900 ]
1901 })),
1902 Some(&settings),
1903 )
1904 .unwrap();
1905
1906 assert_eq!(
1907 result,
1908 serde_json::json!([
1909 { "typeCheckingMode": "strict" },
1910 { "ui.semanticTokens": true },
1911 null,
1912 settings.to_value()
1913 ])
1914 );
1915 }
1916
1917 #[test]
1918 fn test_workspace_configuration_non_object_item_gets_null() {
1919 let settings = sample_settings();
1920 let result = LspClient::workspace_configuration_result(
1921 Some(&serde_json::json!({
1922 "items": [
1923 { "section": "python.analysis" },
1924 { "section": "missing.x" },
1925 {},
1926 { "section": 5 },
1927 "x",
1928 null,
1929 7
1930 ]
1931 })),
1932 Some(&settings),
1933 )
1934 .unwrap();
1935
1936 assert_eq!(
1937 result,
1938 serde_json::json!([
1939 { "typeCheckingMode": "strict" },
1940 null,
1941 settings.to_value(),
1942 null,
1943 null,
1944 null,
1945 null
1946 ])
1947 );
1948 }
1949
1950 #[tokio::test]
1951 async fn test_message_loop_answers_workspace_configuration_from_settings() {
1952 use tokio::io::BufReader;
1953
1954 use crate::test_lsp::{fake_lsp_client_with_config, read_framed_message, write_request};
1955
1956 let mut config = LspServerConfig::rust_analyzer();
1957 config.settings = Some(sample_settings());
1958 let (_client, mut server) = fake_lsp_client_with_config(config);
1959
1960 write_request(
1961 &mut server.read_half_stdin,
1962 &serde_json::json!(7),
1963 "workspace/configuration",
1964 serde_json::json!({ "items": [{ "section": "python.analysis", "scopeUri": 1 }] }),
1965 )
1966 .await;
1967 let mut reader = BufReader::new(&mut server.write_stdout);
1968 let response = read_framed_message(&mut reader).await;
1969
1970 assert_eq!(response["id"], 7);
1971 assert_eq!(
1972 response["result"],
1973 serde_json::json!([{ "typeCheckingMode": "strict" }])
1974 );
1975 }
1976
1977 #[test]
1978 fn test_workspace_configuration_is_lenient_about_item_shape() {
1979 let settings = sample_settings();
1980 let params = serde_json::json!({
1981 "items": [
1982 { "section": 5 },
1983 { "section": null },
1984 3,
1985 { "section": "gopls", "scopeUri": 42 }
1986 ]
1987 });
1988
1989 let with = LspClient::workspace_configuration_result(Some(¶ms), Some(&settings));
1990 let without = LspClient::workspace_configuration_result(Some(¶ms), None);
1991
1992 assert_eq!(
1993 with.unwrap(),
1994 serde_json::json!([
1995 null,
1996 settings.to_value(),
1997 null,
1998 { "ui.semanticTokens": true }
1999 ])
2000 );
2001 assert_eq!(
2002 without.unwrap(),
2003 serde_json::json!([null, null, null, null])
2004 );
2005 }
2006
2007 #[test]
2008 fn test_workspace_configuration_without_settings_matches_the_old_reply_for_bad_params() {
2009 for params in [
2010 None,
2011 Some(serde_json::json!({})),
2012 Some(serde_json::json!({ "items": "x" })),
2013 ] {
2014 let result = LspClient::workspace_configuration_result(params.as_ref(), None);
2015 assert_eq!(result.unwrap(), serde_json::json!([]));
2016 }
2017 }
2018
2019 #[test]
2020 fn test_workspace_configuration_with_settings_rejects_missing_items() {
2021 for params in [None, Some(serde_json::json!({ "items": "nope" }))] {
2022 let error = LspClient::workspace_configuration_result(
2023 params.as_ref(),
2024 Some(&sample_settings()),
2025 )
2026 .unwrap_err();
2027 assert_eq!(error.code, -32602);
2028 }
2029 }
2030
2031 #[test]
2036 fn test_work_done_progress_create_request_is_acknowledged() {
2037 let request = JsonRpcRequest {
2038 jsonrpc: JsonRpcVersion,
2039 id: RequestId::String("wdp1".to_string()),
2040 method: "window/workDoneProgress/create".to_string(),
2041 params: Some(serde_json::json!({ "token": "indexing" })),
2042 };
2043
2044 let reply = reply_json(request);
2045
2046 assert_eq!(reply["result"], Value::Null);
2047 assert!(reply.get("error").is_none());
2048 }
2049
2050 #[test]
2055 fn test_report_progress_frame_reaches_neither_lane() {
2056 let (lifecycle_tx, _lifecycle_rx) = mpsc::channel(8);
2057
2058 let report = LspNotification::Progress(lsp_types::ProgressParams {
2059 token: lsp_types::ProgressToken::Int(1),
2060 value: serde_json::json!({ "kind": "report", "percentage": 50 }),
2061 });
2062
2063 let destination = LspClient::lifecycle_lane(&report, Some(&lifecycle_tx));
2064
2065 assert!(
2066 destination.is_none(),
2067 "a report-kind frame must be dropped before reaching either lane"
2068 );
2069 }
2070
2071 #[test]
2078 fn test_malformed_progress_other_reaches_neither_lane() {
2079 let (lifecycle_tx, _lifecycle_rx) = mpsc::channel(8);
2080
2081 let malformed = LspNotification::Other {
2082 method: std::borrow::Cow::Borrowed("$/progress"),
2083 params: None,
2084 };
2085
2086 let destination = LspClient::lifecycle_lane(&malformed, Some(&lifecycle_tx));
2087
2088 assert!(
2089 destination.is_none(),
2090 "a malformed $/progress frame must be dropped, not routed to the lifecycle lane"
2091 );
2092 }
2093
2094 #[test]
2099 fn test_begin_and_other_notifications_reach_lifecycle_lane() {
2100 let (lifecycle_tx, _lifecycle_rx) = mpsc::channel(8);
2101
2102 let begin = LspNotification::Progress(lsp_types::ProgressParams {
2103 token: lsp_types::ProgressToken::Int(1),
2104 value: serde_json::json!({ "kind": "begin", "title": "Indexing" }),
2105 });
2106 assert!(LspClient::lifecycle_lane(&begin, Some(&lifecycle_tx)).is_some());
2107
2108 let server_status = LspNotification::Other {
2109 method: std::borrow::Cow::Borrowed("experimental/serverStatus"),
2110 params: Some(serde_json::json!({ "quiescent": false })),
2111 };
2112 assert!(LspClient::lifecycle_lane(&server_status, Some(&lifecycle_tx)).is_some());
2113 }
2114
2115 #[test]
2116 fn test_unknown_server_request_returns_method_not_found() {
2117 let request = JsonRpcRequest {
2118 jsonrpc: JsonRpcVersion,
2119 id: RequestId::String("unknown-1".to_string()),
2120 method: "custom/request".to_string(),
2121 params: None,
2122 };
2123
2124 let reply = reply_json(request);
2125
2126 assert!(reply.get("result").is_none());
2127 assert_eq!(reply["error"]["code"], serde_json::json!(-32601));
2128 assert_eq!(
2129 reply["error"]["message"],
2130 serde_json::json!("Unhandled server request: custom/request")
2131 );
2132 }
2133
2134 fn reply_json(request: JsonRpcRequest) -> Value {
2135 serde_json::to_value(LspClient::server_request_reply(request, None)).unwrap()
2136 }
2137
2138 #[tokio::test]
2139 async fn test_null_response_handling() {
2140 use crate::lsp::types::{JsonRpcResponse, RequestId};
2141
2142 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
2143
2144 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
2145
2146 pending_requests
2147 .lock()
2148 .await
2149 .insert(RequestId::Number(1), response_tx);
2150
2151 let null_response = JsonRpcResponse {
2152 jsonrpc: JsonRpcVersion,
2153 id: RequestId::Number(1),
2154 outcome: JsonRpcOutcome::Result(Value::Null),
2155 };
2156
2157 let sender = pending_requests.lock().await.remove(&null_response.id);
2158 if let Some(sender) = sender {
2159 let _ = sender.send(Ok(Value::Null));
2160 }
2161
2162 let timeout_result =
2163 tokio::time::timeout(tokio::time::Duration::from_millis(100), response_rx).await;
2164
2165 assert!(timeout_result.is_ok(), "Should not timeout");
2166
2167 let channel_result = timeout_result.unwrap();
2168 assert!(
2169 channel_result.is_ok(),
2170 "Channel should not be closed: {:?}",
2171 channel_result.err()
2172 );
2173
2174 let response = channel_result.unwrap();
2175 assert!(
2176 response.is_ok(),
2177 "Should receive Ok(Value::Null), not Err: {:?}",
2178 response.err()
2179 );
2180
2181 let value = response.unwrap();
2182 assert_eq!(value, Value::Null, "Should receive Value::Null");
2183 }
2184
2185 #[tokio::test]
2186 async fn test_error_response_handling() {
2187 use crate::lsp::types::{JsonRpcError, JsonRpcResponse, RequestId};
2188
2189 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
2190 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
2191
2192 pending_requests
2193 .lock()
2194 .await
2195 .insert(RequestId::Number(1), response_tx);
2196
2197 let error_response = JsonRpcResponse {
2198 jsonrpc: JsonRpcVersion,
2199 id: RequestId::Number(1),
2200 outcome: JsonRpcOutcome::Error(JsonRpcError {
2201 code: -32601,
2202 message: "Method not found".to_string(),
2203 data: None,
2204 }),
2205 };
2206
2207 let sender = pending_requests.lock().await.remove(&error_response.id);
2208 if let Some(sender) = sender
2209 && let JsonRpcOutcome::Error(error) = error_response.outcome
2210 {
2211 let _ = sender.send(Err(Error::LspServerError {
2212 code: error.code,
2213 message: error.message,
2214 data: error.data,
2215 }));
2216 }
2217
2218 let result = response_rx.await.unwrap();
2219 assert!(result.is_err(), "Should receive error");
2220
2221 if let Err(Error::LspServerError { code, message, .. }) = result {
2222 assert_eq!(code, -32601);
2223 assert_eq!(message, "Method not found");
2224 } else {
2225 panic!("Expected LspServerError");
2226 }
2227 }
2228
2229 #[tokio::test]
2230 async fn test_unknown_request_id() {
2231 use crate::lsp::types::{JsonRpcResponse, RequestId};
2232
2233 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
2234
2235 let response = JsonRpcResponse {
2236 jsonrpc: JsonRpcVersion,
2237 id: RequestId::Number(999),
2238 outcome: JsonRpcOutcome::Result(Value::Null),
2239 };
2240
2241 let sender = pending_requests.lock().await.remove(&response.id);
2242 assert!(sender.is_none(), "Should not find sender for unknown ID");
2243 }
2244
2245 #[test]
2246 fn test_truncate_error_message_for_log_handles_multibyte_boundary() {
2247 let message = format!("{}€{}", "x".repeat(199), "y".repeat(50));
2249
2250 let truncated = LspClient::truncate_error_message_for_log(&message);
2251
2252 assert_eq!(truncated, format!("{}... (truncated)", "x".repeat(199)));
2255 }
2256
2257 #[test]
2258 fn test_truncate_error_message_for_log_no_truncation_at_or_below_limit() {
2259 let exact = "x".repeat(200);
2260 assert_eq!(LspClient::truncate_error_message_for_log(&exact), exact);
2261 assert_eq!(LspClient::truncate_error_message_for_log(""), "");
2262 }
2263
2264 #[test]
2265 fn test_truncate_error_message_for_log_truncates_just_above_limit() {
2266 let message = "x".repeat(201);
2267 assert_eq!(
2268 LspClient::truncate_error_message_for_log(&message),
2269 format!("{}... (truncated)", "x".repeat(200))
2270 );
2271 }
2272
2273 #[test]
2274 fn test_truncate_error_message_for_log_handles_wide_char_at_limit() {
2275 let message = format!("{}{}", "x".repeat(197), "🦀".repeat(10));
2277
2278 let truncated = LspClient::truncate_error_message_for_log(&message);
2279
2280 assert_eq!(truncated, format!("{}... (truncated)", "x".repeat(197)));
2281 }
2282
2283 #[tokio::test]
2284 async fn test_concurrent_request_ids() {
2285 let counter = Arc::new(AtomicI64::new(1));
2286
2287 let counter1 = Arc::clone(&counter);
2288 let counter2 = Arc::clone(&counter);
2289 let counter3 = Arc::clone(&counter);
2290
2291 let handles = vec![
2292 tokio::spawn(async move { counter1.fetch_add(1, Ordering::SeqCst) }),
2293 tokio::spawn(async move { counter2.fetch_add(1, Ordering::SeqCst) }),
2294 tokio::spawn(async move { counter3.fetch_add(1, Ordering::SeqCst) }),
2295 ];
2296
2297 let mut ids = Vec::new();
2298 for handle in handles {
2299 ids.push(handle.await.unwrap());
2300 }
2301
2302 ids.sort_unstable();
2303 assert_eq!(ids, vec![1, 2, 3], "IDs should be unique and sequential");
2304 }
2305
2306 #[test]
2307 fn test_jsonrpc_version_constant() {
2308 assert_eq!(
2309 serde_json::to_value(JsonRpcVersion).unwrap(),
2310 serde_json::json!("2.0")
2311 );
2312 }
2313
2314 #[cfg(unix)]
2323 #[tokio::test]
2324 async fn test_request_timeout_removes_pending_entry() {
2325 let mut child = tokio::process::Command::new("sleep")
2326 .arg("2")
2327 .stdin(std::process::Stdio::piped())
2328 .stdout(std::process::Stdio::piped())
2329 .kill_on_drop(true)
2330 .spawn()
2331 .unwrap();
2332 let stdin = child.stdin.take().unwrap();
2333 let stdout = child.stdout.take().unwrap();
2334
2335 let transport = LspTransport::new(stdin, stdout);
2336 let client = LspClient::from_transport(LspServerConfig::rust_analyzer(), transport);
2337
2338 let result: Result<Value> = client
2339 .request(
2340 "textDocument/hover",
2341 serde_json::json!({}),
2342 Duration::from_millis(50),
2343 )
2344 .await;
2345
2346 assert_matches!(result, Err(Error::Timeout(_)), "got {result:?}");
2347 assert!(
2348 client.pending_requests.lock().await.is_empty(),
2349 "timed-out request must not remain in pending_requests"
2350 );
2351 }
2352
2353 #[tokio::test]
2357 async fn test_fail_pending_requests_resolves_all_as_server_terminated() {
2358 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
2359 let (command_tx, _command_rx) = mpsc::channel(1);
2360
2361 let client = LspClient {
2362 config: LspServerConfig::rust_analyzer(),
2363 state: Arc::new(Mutex::new(super::super::ServerState::Ready)),
2364 request_counter: Arc::new(AtomicI64::new(1)),
2365 command_tx,
2366 pending_requests: Arc::clone(&pending_requests),
2367 pending_failure: Arc::default(),
2368 receiver_task: None,
2369 redactions: Arc::default(),
2370 connection_id: ConnectionId::next(),
2371 };
2372
2373 let (tx1, rx1) = oneshot::channel::<Result<Value>>();
2374 let (tx2, rx2) = oneshot::channel::<Result<Value>>();
2375 pending_requests
2376 .lock()
2377 .await
2378 .insert(RequestId::Number(1), tx1);
2379 pending_requests
2380 .lock()
2381 .await
2382 .insert(RequestId::Number(2), tx2);
2383
2384 client.fail_pending_requests().await;
2385
2386 assert!(pending_requests.lock().await.is_empty());
2387 assert_matches!(rx1.await.unwrap(), Err(Error::ServerTerminated));
2388 assert_matches!(rx2.await.unwrap(), Err(Error::ServerTerminated));
2389 }
2390
2391 mod timeout_cancellation {
2396 use tokio::io::BufReader;
2397 use tokio::task::JoinHandle;
2398
2399 use super::*;
2400 use crate::test_lsp::{fake_lsp_client, read_framed_message, write_response};
2401
2402 const TIMEOUT: Duration = Duration::from_secs(5);
2403
2404 fn spawn_hover(client: &LspClient) -> JoinHandle<Result<Value>> {
2405 let client = client.clone();
2406 tokio::spawn(async move {
2407 client
2408 .request("textDocument/hover", serde_json::json!({}), TIMEOUT)
2409 .await
2410 })
2411 }
2412
2413 #[tokio::test(start_paused = true)]
2414 async fn test_timeout_while_awaiting_reply_clears_pending_and_discards_late_reply() {
2415 let (client, mut server) = fake_lsp_client();
2416 let mut reader = BufReader::new(&mut server.write_stdout);
2417
2418 let first = spawn_hover(&client);
2419 let first_request = read_framed_message(&mut reader).await;
2420
2421 tokio::time::advance(TIMEOUT + Duration::from_secs(1)).await;
2422 assert_matches!(
2423 first.await.unwrap(),
2424 Err(Error::Timeout(limit)) if limit == TIMEOUT,
2425 "request awaiting its reply must time out"
2426 );
2427 assert!(
2428 client.pending_requests.lock().await.is_empty(),
2429 "timed-out request must not remain in pending_requests"
2430 );
2431
2432 write_response(
2433 &mut server.read_half_stdin,
2434 &first_request["id"],
2435 serde_json::json!("stale"),
2436 )
2437 .await;
2438
2439 let second = spawn_hover(&client);
2440 let second_request = read_framed_message(&mut reader).await;
2441 assert_ne!(second_request["id"], first_request["id"]);
2442 write_response(
2443 &mut server.read_half_stdin,
2444 &second_request["id"],
2445 serde_json::json!("fresh"),
2446 )
2447 .await;
2448
2449 assert_eq!(second.await.unwrap().unwrap(), serde_json::json!("fresh"));
2450 assert!(client.pending_requests.lock().await.is_empty());
2451 }
2452
2453 #[tokio::test]
2454 async fn test_undecodable_response_fails_only_its_request() {
2455 use crate::test_lsp::write_raw_frame;
2456
2457 let (client, mut server) = fake_lsp_client();
2458 let mut reader = BufReader::new(&mut server.write_stdout);
2459
2460 let first = spawn_hover(&client);
2461 let first_request = read_framed_message(&mut reader).await;
2462 let deep = format!("{}1{}", "{\"parent\":".repeat(200), "}".repeat(200));
2463 write_raw_frame(
2464 &mut server.read_half_stdin,
2465 &format!(
2466 r#"{{"jsonrpc":"2.0","id":{},"result":{deep}}}"#,
2467 first_request["id"]
2468 ),
2469 )
2470 .await;
2471
2472 assert_matches!(
2473 first.await.unwrap(),
2474 Err(Error::LspProtocolError(message)) if message.as_str() == "undecodable response"
2475 );
2476
2477 let second = spawn_hover(&client);
2478 let second_request = read_framed_message(&mut reader).await;
2479 write_response(
2480 &mut server.read_half_stdin,
2481 &second_request["id"],
2482 serde_json::json!("alive"),
2483 )
2484 .await;
2485 assert_eq!(second.await.unwrap().unwrap(), serde_json::json!("alive"));
2486 assert!(client.pending_requests.lock().await.is_empty());
2487 }
2488
2489 fn deeply_nested() -> String {
2490 format!("{}1{}", "{\"parent\":".repeat(200), "}".repeat(200))
2491 }
2492
2493 #[tokio::test]
2495 async fn test_invalid_utf8_response_fails_only_its_request() {
2496 use tokio::io::AsyncWriteExt as _;
2497
2498 let (client, mut server) = fake_lsp_client();
2499 let mut reader = BufReader::new(&mut server.write_stdout);
2500
2501 let first = spawn_hover(&client);
2502 let first_request = read_framed_message(&mut reader).await;
2503 let mut body = format!(
2504 r#"{{"jsonrpc":"2.0","id":{},"result":"x"#,
2505 first_request["id"]
2506 )
2507 .into_bytes();
2508 body.extend_from_slice(&[0xff, 0xfe]);
2509 body.extend_from_slice(br#"y"}"#);
2510 let stdin = &mut server.read_half_stdin;
2511 stdin
2512 .write_all(format!("Content-Length: {}\r\n\r\n", body.len()).as_bytes())
2513 .await
2514 .unwrap();
2515 stdin.write_all(&body).await.unwrap();
2516 stdin.flush().await.unwrap();
2517
2518 assert_matches!(
2519 first.await.unwrap(),
2520 Err(Error::LspProtocolError(message)) if message.as_str() == "undecodable response"
2521 );
2522
2523 let second = spawn_hover(&client);
2524 let second_request = read_framed_message(&mut reader).await;
2525 write_response(
2526 &mut server.read_half_stdin,
2527 &second_request["id"],
2528 serde_json::json!("alive"),
2529 )
2530 .await;
2531 assert_eq!(second.await.unwrap().unwrap(), serde_json::json!("alive"));
2532 }
2533
2534 #[test]
2537 fn test_undecodable_warning_is_time_limited_and_counts_the_suppressed() {
2538 use tracing_subscriber::layer::SubscriberExt as _;
2539
2540 let captured = crate::test_lsp::CapturedLogs::default();
2541 let _guard = tracing::subscriber::set_default(
2542 tracing_subscriber::registry().with(captured.clone()),
2543 );
2544 let mut run = UndecodableRun::default();
2545 let start = std::time::Instant::now();
2546 let at = |secs| start + Duration::from_secs(secs);
2547 for secs in [0, 1, 2, 30] {
2548 run.observe_at(InboundMessage::UndecodableFrame, at(secs))
2549 .unwrap();
2550 }
2551 run.observe_at(InboundMessage::UndecodableFrame, at(61))
2552 .unwrap();
2553
2554 let warnings: Vec<_> = captured
2555 .entries()
2556 .into_iter()
2557 .filter(|(level, _)| *level == tracing::Level::WARN)
2558 .map(|(_, message)| message)
2559 .collect();
2560 assert_eq!(warnings.len(), 2, "{warnings:?}");
2561 assert!(warnings[0].contains("(0 others"), "{warnings:?}");
2562 assert!(warnings[1].contains("(3 others"), "{warnings:?}");
2563 }
2564
2565 #[tokio::test]
2566 async fn test_undecodable_server_request_is_answered_with_an_error() {
2567 use crate::test_lsp::write_raw_frame;
2568
2569 let (client, mut server) = fake_lsp_client();
2570 let mut reader = BufReader::new(&mut server.write_stdout);
2571
2572 write_raw_frame(
2573 &mut server.read_half_stdin,
2574 &format!(
2575 r#"{{"jsonrpc":"2.0","id":"srv-1","method":"client/registerCapability","params":{}}}"#,
2576 deeply_nested()
2577 ),
2578 )
2579 .await;
2580 let reply = read_framed_message(&mut reader).await;
2581
2582 assert_eq!(reply["id"], serde_json::json!("srv-1"));
2583 assert_eq!(reply["error"]["code"], serde_json::json!(-32600));
2584
2585 let hover = spawn_hover(&client);
2586 let request = read_framed_message(&mut reader).await;
2587 write_response(
2588 &mut server.read_half_stdin,
2589 &request["id"],
2590 serde_json::json!("alive"),
2591 )
2592 .await;
2593 assert_eq!(hover.await.unwrap().unwrap(), serde_json::json!("alive"));
2594 }
2595
2596 #[tokio::test]
2597 async fn test_undecodable_notification_is_dropped_and_the_connection_stays_up() {
2598 use crate::test_lsp::write_raw_frame;
2599
2600 let (client, mut server) = fake_lsp_client();
2601 let mut reader = BufReader::new(&mut server.write_stdout);
2602 let hover = spawn_hover(&client);
2603 let request = read_framed_message(&mut reader).await;
2604
2605 write_raw_frame(
2606 &mut server.read_half_stdin,
2607 &format!(
2608 r#"{{"jsonrpc":"2.0","method":"window/logMessage","params":{}}}"#,
2609 deeply_nested()
2610 ),
2611 )
2612 .await;
2613 write_response(
2614 &mut server.read_half_stdin,
2615 &request["id"],
2616 serde_json::json!("alive"),
2617 )
2618 .await;
2619
2620 assert_eq!(hover.await.unwrap().unwrap(), serde_json::json!("alive"));
2621 }
2622
2623 #[tokio::test]
2624 async fn test_a_run_of_undecodable_frames_ends_the_connection() {
2625 use crate::test_lsp::write_raw_frame;
2626
2627 let (client, mut server) = fake_lsp_client();
2628 let mut reader = BufReader::new(&mut server.write_stdout);
2629 let hover = spawn_hover(&client);
2630 let _request = read_framed_message(&mut reader).await;
2631
2632 for _ in 0..MAX_CONSECUTIVE_UNDECODABLE_FRAMES {
2633 write_raw_frame(&mut server.read_half_stdin, "{not json").await;
2634 }
2635
2636 assert_matches!(hover.await.unwrap(), Err(Error::ServerTerminated));
2637 }
2638
2639 #[tokio::test]
2640 async fn test_a_decodable_message_ends_a_run_of_undecodable_frames() {
2641 use crate::test_lsp::write_raw_frame;
2642
2643 let (client, mut server) = fake_lsp_client();
2644 let mut reader = BufReader::new(&mut server.write_stdout);
2645 let hover = spawn_hover(&client);
2646 let request = read_framed_message(&mut reader).await;
2647
2648 let almost = MAX_CONSECUTIVE_UNDECODABLE_FRAMES - 1;
2649 for _ in 0..2 {
2650 for _ in 0..almost {
2651 write_raw_frame(&mut server.read_half_stdin, "{not json").await;
2652 }
2653 write_raw_frame(
2654 &mut server.read_half_stdin,
2655 r#"{"jsonrpc":"2.0","method":"$/progress"}"#,
2656 )
2657 .await;
2658 }
2659 write_response(
2660 &mut server.read_half_stdin,
2661 &request["id"],
2662 serde_json::json!("alive"),
2663 )
2664 .await;
2665
2666 assert_eq!(hover.await.unwrap().unwrap(), serde_json::json!("alive"));
2667 }
2668
2669 #[tokio::test(start_paused = true)]
2670 async fn test_dropped_caller_after_send_is_cleared_by_late_reply() {
2671 let (client, mut server) = fake_lsp_client();
2672 let mut reader = BufReader::new(&mut server.write_stdout);
2673
2674 let first = spawn_hover(&client);
2675 let first_request = read_framed_message(&mut reader).await;
2676 first.abort();
2677 assert!(first.await.unwrap_err().is_cancelled());
2678
2679 write_response(
2680 &mut server.read_half_stdin,
2681 &first_request["id"],
2682 serde_json::json!("orphaned"),
2683 )
2684 .await;
2685
2686 let second = spawn_hover(&client);
2687 let second_request = read_framed_message(&mut reader).await;
2688 write_response(
2689 &mut server.read_half_stdin,
2690 &second_request["id"],
2691 serde_json::json!("fresh"),
2692 )
2693 .await;
2694
2695 assert_eq!(second.await.unwrap().unwrap(), serde_json::json!("fresh"));
2696 assert!(
2697 client.pending_requests.lock().await.is_empty(),
2698 "the orphaned reply must clear the abandoned entry"
2699 );
2700 }
2701 }
2702
2703 #[test]
2704 fn test_should_retrigger_defaults_to_true_when_data_absent() {
2705 assert!(LspClient::should_retrigger(None));
2706 }
2707
2708 #[test]
2709 fn test_should_retrigger_false_when_flag_false() {
2710 assert!(!LspClient::should_retrigger(Some(&serde_json::json!({
2711 "retriggerRequest": false
2712 }))));
2713 }
2714
2715 #[test]
2716 fn test_should_retrigger_true_when_flag_true() {
2717 assert!(LspClient::should_retrigger(Some(&serde_json::json!({
2718 "retriggerRequest": true
2719 }))));
2720 }
2721
2722 mod void_params_wire {
2727 use tokio::io::BufReader;
2728
2729 use super::*;
2730 use crate::test_lsp::{fake_lsp_client, read_framed_message, write_response};
2731
2732 #[tokio::test]
2733 async fn test_request_with_null_params_omits_params_key() {
2734 let (client, mut server) = fake_lsp_client();
2735
2736 let request_task = tokio::spawn(async move {
2737 client
2738 .request::<_, Value>("shutdown", Value::Null, Duration::from_secs(5))
2739 .await
2740 });
2741
2742 let mut reader = BufReader::new(&mut server.write_stdout);
2743 let request = read_framed_message(&mut reader).await;
2744
2745 assert_eq!(request["method"], "shutdown");
2746 assert!(
2747 request.get("params").is_none(),
2748 "null params must be omitted, got: {request}"
2749 );
2750
2751 write_response(&mut server.read_half_stdin, &request["id"], Value::Null).await;
2752 request_task.await.unwrap().unwrap();
2753 }
2754
2755 #[tokio::test]
2756 async fn test_notify_with_null_params_omits_params_key() {
2757 let (client, mut server) = fake_lsp_client();
2758
2759 client.notify("exit", Value::Null).await.unwrap();
2760
2761 let mut reader = BufReader::new(&mut server.write_stdout);
2762 let notification = read_framed_message(&mut reader).await;
2763
2764 assert_eq!(notification["method"], "exit");
2765 assert!(
2766 notification.get("params").is_none(),
2767 "null params must be omitted, got: {notification}"
2768 );
2769 }
2770
2771 #[tokio::test]
2772 async fn test_notify_with_empty_object_params_keeps_params_key() {
2773 let (client, mut server) = fake_lsp_client();
2774
2775 client
2776 .notify("initialized", lsp_types::InitializedParams {})
2777 .await
2778 .unwrap();
2779
2780 let mut reader = BufReader::new(&mut server.write_stdout);
2781 let notification = read_framed_message(&mut reader).await;
2782
2783 assert_eq!(notification["method"], "initialized");
2784 assert_eq!(
2785 notification["params"],
2786 serde_json::json!({}),
2787 "non-null params must still be sent"
2788 );
2789 }
2790 }
2791
2792 mod retry_behavior {
2793 use tokio::io::{AsyncWriteExt, BufReader, DuplexStream};
2794
2795 use super::*;
2796 use crate::test_lsp::{
2797 CapturedLogs, fake_lsp_client, read_framed_message, write_error_response,
2798 write_response as write_success_response,
2799 };
2800
2801 async fn write_retryable_error_response(
2809 stdin: &mut DuplexStream,
2810 id: &Value,
2811 code: i32,
2812 message: &str,
2813 retrigger: bool,
2814 ) {
2815 let response = serde_json::json!({
2816 "jsonrpc": "2.0",
2817 "id": id,
2818 "error": {
2819 "code": code,
2820 "message": message,
2821 "data": { "retriggerRequest": retrigger },
2822 },
2823 });
2824 let content = serde_json::to_string(&response).unwrap();
2825 let header = format!("Content-Length: {}\r\n\r\n", content.len());
2826 stdin.write_all(header.as_bytes()).await.unwrap();
2827 stdin.write_all(content.as_bytes()).await.unwrap();
2828 stdin.flush().await.unwrap();
2829 }
2830
2831 #[tokio::test]
2841 async fn test_retry_exhaustion_returns_original_server_cancelled_error() {
2842 use tracing_subscriber::layer::SubscriberExt as _;
2843
2844 let (client, mut server) = fake_lsp_client();
2845 let captured = CapturedLogs::default();
2846 let subscriber = tracing_subscriber::registry().with(captured.clone());
2847 let guard = tracing::subscriber::set_default(subscriber);
2848
2849 let request_task = tokio::spawn(async move {
2850 client
2851 .request::<_, Value>(
2852 "textDocument/hover",
2853 serde_json::json!({}),
2854 Duration::from_secs(30),
2855 )
2856 .await
2857 });
2858
2859 let mut reader = BufReader::new(&mut server.write_stdout);
2860 for _ in 0..=SERVER_CANCELLED_MAX_RETRIES {
2864 let request = read_framed_message(&mut reader).await;
2865 let id = request["id"].clone();
2866 write_retryable_error_response(
2867 &mut server.read_half_stdin,
2868 &id,
2869 i32::from(LspErrorCodes::ServerCancelled),
2870 "server cancelled the request",
2871 true,
2872 )
2873 .await;
2874 }
2875
2876 let result = request_task.await.unwrap();
2877
2878 match result {
2879 Err(Error::LspServerError {
2880 code,
2881 message,
2882 data,
2883 }) => {
2884 assert_eq!(code, i32::from(LspErrorCodes::ServerCancelled));
2888 assert_eq!(message, "server cancelled the request");
2889 assert_eq!(data, Some(serde_json::json!({ "retriggerRequest": true })));
2890 }
2891 other => panic!("expected exhausted ServerCancelled error, got {other:?}"),
2892 }
2893
2894 drop(guard);
2895 let logs = captured.entries();
2896 assert_eq!(
2897 logs.iter()
2898 .filter(|(level, _)| *level == tracing::Level::ERROR)
2899 .count(),
2900 1,
2901 "exactly the final exhausted attempt must log at ERROR, got: {logs:?}"
2902 );
2903 assert!(
2904 logs.iter()
2905 .any(|(level, msg)| *level == tracing::Level::ERROR
2906 && msg.contains("LSP error response")
2907 && msg.contains("retries exhausted")),
2908 "expected an ERROR log sharing the 'LSP error response' prefix and naming \
2909 retry exhaustion, got: {logs:?}"
2910 );
2911 assert_eq!(
2912 logs.iter()
2913 .filter(
2914 |(level, msg)| *level == tracing::Level::WARN && msg.contains("will retry")
2915 )
2916 .count(),
2917 usize::try_from(SERVER_CANCELLED_MAX_RETRIES).unwrap(),
2918 "every attempt before the last must log a WARN 'will retry' line, got: {logs:?}"
2919 );
2920 }
2921
2922 #[tokio::test]
2923 async fn test_retrigger_false_returns_immediately_without_retry() {
2924 let (client, mut server) = fake_lsp_client();
2925
2926 let request_task = tokio::spawn(async move {
2927 client
2928 .request::<_, Value>(
2929 "textDocument/hover",
2930 serde_json::json!({}),
2931 Duration::from_secs(30),
2932 )
2933 .await
2934 });
2935
2936 let mut reader = BufReader::new(&mut server.write_stdout);
2937 let request = read_framed_message(&mut reader).await;
2938 let id = request["id"].clone();
2939 write_retryable_error_response(
2940 &mut server.read_half_stdin,
2941 &id,
2942 i32::from(LspErrorCodes::ServerCancelled),
2943 "server cancelled the request",
2944 false,
2945 )
2946 .await;
2947
2948 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
2955 .await
2956 .unwrap()
2957 .unwrap();
2958
2959 match result {
2960 Err(Error::LspServerError { code, .. }) => {
2961 assert_eq!(code, i32::from(LspErrorCodes::ServerCancelled));
2962 }
2963 other => panic!("expected immediate ServerCancelled error, got {other:?}"),
2964 }
2965
2966 let second_request =
2967 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
2968 .await;
2969 assert!(
2970 second_request.is_err(),
2971 "no retry should have been sent after retriggerRequest: false"
2972 );
2973 }
2974
2975 #[tokio::test]
2976 async fn test_retry_succeeds_after_one_server_cancelled_response() {
2977 let (client, mut server) = fake_lsp_client();
2978
2979 let request_task = tokio::spawn(async move {
2980 client
2981 .request::<_, Value>(
2982 "textDocument/hover",
2983 serde_json::json!({}),
2984 Duration::from_secs(30),
2985 )
2986 .await
2987 });
2988
2989 let mut reader = BufReader::new(&mut server.write_stdout);
2990
2991 let first = read_framed_message(&mut reader).await;
2993 write_retryable_error_response(
2994 &mut server.read_half_stdin,
2995 &first["id"].clone(),
2996 i32::from(LspErrorCodes::ServerCancelled),
2997 "server cancelled the request",
2998 true,
2999 )
3000 .await;
3001
3002 let second = read_framed_message(&mut reader).await;
3005 assert_ne!(
3006 first["id"], second["id"],
3007 "retry must use a fresh request id"
3008 );
3009 let expected_result = serde_json::json!({ "contents": "resolved on retry" });
3010 write_success_response(
3011 &mut server.read_half_stdin,
3012 &second["id"].clone(),
3013 expected_result.clone(),
3014 )
3015 .await;
3016
3017 let result = request_task.await.unwrap();
3018 assert_eq!(result.unwrap(), expected_result);
3019 }
3020
3021 #[tokio::test]
3022 async fn test_retry_exhaustion_returns_original_content_modified_error() {
3023 let (client, mut server) = fake_lsp_client();
3024
3025 let request_task = tokio::spawn(async move {
3026 client
3027 .request::<_, Value>(
3028 "textDocument/hover",
3029 serde_json::json!({}),
3030 Duration::from_secs(30),
3031 )
3032 .await
3033 });
3034
3035 let mut reader = BufReader::new(&mut server.write_stdout);
3036 for _ in 0..=SERVER_CANCELLED_MAX_RETRIES {
3041 let request = read_framed_message(&mut reader).await;
3042 let id = request["id"].clone();
3043 write_retryable_error_response(
3044 &mut server.read_half_stdin,
3045 &id,
3046 i32::from(LspErrorCodes::ContentModified),
3047 "content modified",
3048 true,
3049 )
3050 .await;
3051 }
3052
3053 let result = request_task.await.unwrap();
3054
3055 match result {
3056 Err(Error::LspServerError {
3057 code,
3058 message,
3059 data,
3060 }) => {
3061 assert_eq!(code, i32::from(LspErrorCodes::ContentModified));
3065 assert_eq!(message, "content modified");
3066 assert_eq!(data, Some(serde_json::json!({ "retriggerRequest": true })));
3067 }
3068 other => panic!("expected exhausted ContentModified error, got {other:?}"),
3069 }
3070 }
3071
3072 #[tokio::test]
3073 async fn test_retrigger_false_returns_immediately_without_retry_for_content_modified() {
3074 let (client, mut server) = fake_lsp_client();
3075
3076 let request_task = tokio::spawn(async move {
3077 client
3078 .request::<_, Value>(
3079 "textDocument/hover",
3080 serde_json::json!({}),
3081 Duration::from_secs(30),
3082 )
3083 .await
3084 });
3085
3086 let mut reader = BufReader::new(&mut server.write_stdout);
3087 let request = read_framed_message(&mut reader).await;
3088 let id = request["id"].clone();
3089 write_retryable_error_response(
3090 &mut server.read_half_stdin,
3091 &id,
3092 i32::from(LspErrorCodes::ContentModified),
3093 "content modified",
3094 false,
3095 )
3096 .await;
3097
3098 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
3103 .await
3104 .unwrap()
3105 .unwrap();
3106
3107 match result {
3108 Err(Error::LspServerError { code, .. }) => {
3109 assert_eq!(code, i32::from(LspErrorCodes::ContentModified));
3110 }
3111 other => panic!("expected immediate ContentModified error, got {other:?}"),
3112 }
3113
3114 let second_request =
3115 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
3116 .await;
3117 assert!(
3118 second_request.is_err(),
3119 "no retry should have been sent after retriggerRequest: false"
3120 );
3121 }
3122
3123 #[tokio::test]
3124 async fn test_retry_succeeds_after_one_content_modified_response() {
3125 let (client, mut server) = fake_lsp_client();
3126
3127 let request_task = tokio::spawn(async move {
3128 client
3129 .request::<_, Value>(
3130 "textDocument/hover",
3131 serde_json::json!({}),
3132 Duration::from_secs(30),
3133 )
3134 .await
3135 });
3136
3137 let mut reader = BufReader::new(&mut server.write_stdout);
3138
3139 let first = read_framed_message(&mut reader).await;
3141 write_retryable_error_response(
3142 &mut server.read_half_stdin,
3143 &first["id"].clone(),
3144 i32::from(LspErrorCodes::ContentModified),
3145 "content modified",
3146 true,
3147 )
3148 .await;
3149
3150 let second = read_framed_message(&mut reader).await;
3153 assert_ne!(
3154 first["id"], second["id"],
3155 "retry must use a fresh request id"
3156 );
3157 let expected_result = serde_json::json!({ "contents": "resolved on retry" });
3158 write_success_response(
3159 &mut server.read_half_stdin,
3160 &second["id"].clone(),
3161 expected_result.clone(),
3162 )
3163 .await;
3164
3165 let result = request_task.await.unwrap();
3166 assert_eq!(result.unwrap(), expected_result);
3167 }
3168
3169 #[tokio::test]
3170 async fn test_content_modified_on_non_allowlisted_method_does_not_retry() {
3171 let (client, mut server) = fake_lsp_client();
3172
3173 let request_task = tokio::spawn(async move {
3180 client
3181 .request::<_, Value>(
3182 "textDocument/rename",
3183 serde_json::json!({}),
3184 Duration::from_secs(30),
3185 )
3186 .await
3187 });
3188
3189 let mut reader = BufReader::new(&mut server.write_stdout);
3190 let request = read_framed_message(&mut reader).await;
3191 let id = request["id"].clone();
3192 write_retryable_error_response(
3193 &mut server.read_half_stdin,
3194 &id,
3195 i32::from(LspErrorCodes::ContentModified),
3196 "content modified",
3197 true,
3198 )
3199 .await;
3200
3201 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
3202 .await
3203 .unwrap()
3204 .unwrap();
3205
3206 match result {
3207 Err(Error::LspServerError { code, .. }) => {
3208 assert_eq!(code, i32::from(LspErrorCodes::ContentModified));
3209 }
3210 other => panic!("expected immediate ContentModified error, got {other:?}"),
3211 }
3212
3213 let second_request =
3214 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
3215 .await;
3216 assert!(
3217 second_request.is_err(),
3218 "no retry should have been sent for a non-allowlisted method"
3219 );
3220 }
3221
3222 #[tokio::test]
3229 async fn test_oversized_error_message_truncated_for_caller() {
3230 let (client, mut server) = fake_lsp_client();
3231
3232 let request_task = tokio::spawn(async move {
3233 client
3234 .request::<_, Value>(
3235 "textDocument/hover",
3236 serde_json::json!({}),
3237 Duration::from_secs(30),
3238 )
3239 .await
3240 });
3241
3242 let mut reader = BufReader::new(&mut server.write_stdout);
3243 let request = read_framed_message(&mut reader).await;
3244 let id = request["id"].clone();
3245 let oversized_message = "x".repeat(MAX_ERROR_MESSAGE_CALLER_BYTES + 500);
3246 write_error_response(&mut server.read_half_stdin, &id, -32603, &oversized_message)
3247 .await;
3248
3249 let result = request_task.await.unwrap();
3250
3251 match result {
3252 Err(Error::LspServerError { code, message, .. }) => {
3253 assert_eq!(code, -32603);
3254 assert!(
3255 message.len() < oversized_message.len(),
3256 "caller-facing message must be truncated, got {} bytes",
3257 message.len()
3258 );
3259 assert!(message.ends_with("... (truncated)"));
3260 }
3261 other => panic!("expected truncated LspServerError, got {other:?}"),
3262 }
3263 }
3264
3265 #[tokio::test]
3271 async fn test_error_message_between_log_and_caller_caps_reaches_caller_intact() {
3272 let (client, mut server) = fake_lsp_client();
3273
3274 let request_task = tokio::spawn(async move {
3275 client
3276 .request::<_, Value>(
3277 "textDocument/hover",
3278 serde_json::json!({}),
3279 Duration::from_secs(30),
3280 )
3281 .await
3282 });
3283
3284 let mut reader = BufReader::new(&mut server.write_stdout);
3285 let request = read_framed_message(&mut reader).await;
3286 let id = request["id"].clone();
3287 let message = "x".repeat(crate::util::MAX_LOG_STRING_BYTES + 50);
3288 write_error_response(&mut server.read_half_stdin, &id, -32603, &message).await;
3289
3290 let result = request_task.await.unwrap();
3291
3292 match result {
3293 Err(Error::LspServerError {
3294 message: returned, ..
3295 }) => {
3296 assert_eq!(
3297 returned, message,
3298 "message under the caller cap must not be truncated"
3299 );
3300 }
3301 other => panic!("expected untruncated LspServerError, got {other:?}"),
3302 }
3303 }
3304
3305 #[tokio::test]
3310 async fn test_retried_error_that_recovers_does_not_log_error_level() {
3311 use tracing_subscriber::layer::SubscriberExt as _;
3312
3313 let (client, mut server) = fake_lsp_client();
3314 let captured = CapturedLogs::default();
3315 let subscriber = tracing_subscriber::registry().with(captured.clone());
3316 let guard = tracing::subscriber::set_default(subscriber);
3317
3318 let request_task = tokio::spawn(async move {
3319 client
3320 .request::<_, Value>(
3321 "textDocument/hover",
3322 serde_json::json!({}),
3323 Duration::from_secs(30),
3324 )
3325 .await
3326 });
3327
3328 let mut reader = BufReader::new(&mut server.write_stdout);
3329
3330 let first = read_framed_message(&mut reader).await;
3331 write_retryable_error_response(
3332 &mut server.read_half_stdin,
3333 &first["id"].clone(),
3334 i32::from(LspErrorCodes::ServerCancelled),
3335 "server cancelled the request",
3336 true,
3337 )
3338 .await;
3339
3340 let second = read_framed_message(&mut reader).await;
3341 write_success_response(
3342 &mut server.read_half_stdin,
3343 &second["id"].clone(),
3344 serde_json::json!({ "contents": "resolved on retry" }),
3345 )
3346 .await;
3347
3348 let result = request_task.await.unwrap();
3349 assert!(result.is_ok(), "expected retry to recover, got {result:?}");
3350
3351 drop(guard);
3352 let logs = captured.entries();
3353 assert!(
3354 !logs
3355 .iter()
3356 .any(|(level, _)| *level == tracing::Level::ERROR),
3357 "a retried-and-recovered error must not log at ERROR, got: {logs:?}"
3358 );
3359 assert!(
3360 logs.iter().any(
3361 |(level, msg)| *level == tracing::Level::WARN && msg.contains("will retry")
3362 ),
3363 "expected a WARN 'will retry' log line, got: {logs:?}"
3364 );
3365 }
3366
3367 #[tokio::test]
3371 async fn test_non_retryable_error_logs_error_level() {
3372 use tracing_subscriber::layer::SubscriberExt as _;
3373
3374 let (client, mut server) = fake_lsp_client();
3375 let captured = CapturedLogs::default();
3376 let subscriber = tracing_subscriber::registry().with(captured.clone());
3377 let guard = tracing::subscriber::set_default(subscriber);
3378
3379 let request_task = tokio::spawn(async move {
3380 client
3381 .request::<_, Value>(
3382 "textDocument/rename",
3383 serde_json::json!({}),
3384 Duration::from_secs(30),
3385 )
3386 .await
3387 });
3388
3389 let mut reader = BufReader::new(&mut server.write_stdout);
3390 let request = read_framed_message(&mut reader).await;
3391 let id = request["id"].clone();
3392 write_retryable_error_response(
3393 &mut server.read_half_stdin,
3394 &id,
3395 i32::from(LspErrorCodes::ContentModified),
3396 "content modified",
3397 true,
3398 )
3399 .await;
3400
3401 let result = request_task.await.unwrap();
3402 assert!(result.is_err(), "expected a non-retryable error");
3403
3404 drop(guard);
3405 let logs = captured.entries();
3406 assert!(
3407 logs.iter()
3408 .any(|(level, msg)| *level == tracing::Level::ERROR
3409 && msg.contains("LSP error response")
3410 && msg.contains("content modified")),
3411 "a non-retryable error must still surface an ERROR log sharing the \
3412 'LSP error response' prefix, got: {logs:?}"
3413 );
3414 }
3415 }
3416
3417 mod reader_task_regression {
3421 use tokio::io::BufReader;
3422
3423 use super::*;
3424 use crate::test_lsp::{
3425 fake_lsp_client, inert_transport, read_framed_message, write_response,
3426 };
3427
3428 #[tokio::test]
3435 async fn test_message_loop_inner_treats_reader_channel_close_as_server_terminated() {
3436 let (mut transport, _reader) = inert_transport();
3437 let (_command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
3438 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
3439 let pending_requests: Arc<Mutex<PendingRequests>> =
3440 Arc::new(Mutex::new(HashMap::new()));
3441
3442 drop(msg_tx);
3443
3444 let result = LspClient::message_loop_inner(
3445 &mut transport,
3446 &mut msg_rx,
3447 &mut command_rx,
3448 &pending_requests,
3449 None,
3450 None,
3451 &Redactions::default(),
3452 None,
3453 )
3454 .await;
3455
3456 assert_matches!(result, Err(Error::ServerTerminated), "got {result:?}");
3457 }
3458
3459 #[tokio::test]
3471 async fn test_message_loop_inner_reader_gone_then_drain_fails_pending_request() {
3472 let (mut transport, _reader) = inert_transport();
3473 let (_command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
3474 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
3475 let pending_requests: Arc<Mutex<PendingRequests>> =
3476 Arc::new(Mutex::new(HashMap::new()));
3477
3478 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
3479 pending_requests
3480 .lock()
3481 .await
3482 .insert(RequestId::Number(1), response_tx);
3483
3484 drop(msg_tx);
3485
3486 let result = LspClient::message_loop_inner(
3487 &mut transport,
3488 &mut msg_rx,
3489 &mut command_rx,
3490 &pending_requests,
3491 None,
3492 None,
3493 &Redactions::default(),
3494 None,
3495 )
3496 .await;
3497 assert_matches!(result, Err(Error::ServerTerminated), "got {result:?}");
3498
3499 LspClient::drain_and_fail_pending(&pending_requests, &StdMutex::default()).await;
3500
3501 let received = response_rx.await.unwrap();
3502 assert_matches!(received, Err(Error::ServerTerminated), "got {received:?}");
3503 assert!(pending_requests.lock().await.is_empty());
3504 }
3505
3506 #[tokio::test]
3514 async fn test_shutdown_drains_buffered_responses_instead_of_dropping_them() {
3515 let (mut transport, _reader) = inert_transport();
3516 let (command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
3517 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
3518 let pending_requests: Arc<Mutex<PendingRequests>> =
3519 Arc::new(Mutex::new(HashMap::new()));
3520
3521 let id = RequestId::Number(1);
3522 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
3523 pending_requests
3524 .lock()
3525 .await
3526 .insert(id.clone(), response_tx);
3527
3528 msg_tx
3529 .send(Ok(InboundMessage::Response(JsonRpcResponse {
3530 jsonrpc: JsonRpcVersion,
3531 id: id.clone(),
3532 outcome: JsonRpcOutcome::Result(serde_json::json!({ "ok": true })),
3533 })))
3534 .await
3535 .unwrap();
3536 command_tx.send(ClientCommand::Shutdown).await.unwrap();
3537 drop(command_tx);
3538
3539 let result = LspClient::message_loop_inner(
3540 &mut transport,
3541 &mut msg_rx,
3542 &mut command_rx,
3543 &pending_requests,
3544 None,
3545 None,
3546 &Redactions::default(),
3547 None,
3548 )
3549 .await;
3550
3551 assert!(result.is_ok(), "got {result:?}");
3552 let received = response_rx.await.unwrap();
3555 assert_eq!(received.unwrap(), serde_json::json!({ "ok": true }));
3556 assert!(
3557 pending_requests.lock().await.is_empty(),
3558 "the drained response must resolve its pending request entry"
3559 );
3560 }
3561
3562 #[tokio::test]
3570 async fn test_message_loop_fails_pending_requests_on_transport_error_exit() {
3571 let (transport, reader) = inert_transport();
3572 let (_command_tx, command_rx) = mpsc::channel::<ClientCommand>(1);
3573 let pending_requests: Arc<Mutex<PendingRequests>> =
3574 Arc::new(Mutex::new(HashMap::new()));
3575
3576 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
3577 pending_requests
3578 .lock()
3579 .await
3580 .insert(RequestId::Number(1), response_tx);
3581
3582 let result = LspClient::message_loop(
3583 (transport, reader),
3584 command_rx,
3585 Arc::clone(&pending_requests),
3586 Arc::default(),
3587 None,
3588 None,
3589 Arc::default(),
3590 None,
3591 )
3592 .await;
3593
3594 assert!(result.is_err(), "got {result:?}");
3595 let received = response_rx.await.unwrap();
3596 assert_matches!(received, Err(Error::ServerTerminated), "got {received:?}");
3597 assert!(pending_requests.lock().await.is_empty());
3598 }
3599
3600 #[tokio::test]
3610 async fn test_message_loop_shutdown_resolves_answered_and_fails_unanswered_pending() {
3611 let (mut transport, _reader) = inert_transport();
3612 let (command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
3613 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
3614 let pending_requests: Arc<Mutex<PendingRequests>> =
3615 Arc::new(Mutex::new(HashMap::new()));
3616
3617 let answered_id = RequestId::Number(1);
3618 let (answered_tx, answered_rx) = oneshot::channel::<Result<Value>>();
3619 let unanswered_id = RequestId::Number(2);
3620 let (unanswered_tx, unanswered_rx) = oneshot::channel::<Result<Value>>();
3621 pending_requests
3622 .lock()
3623 .await
3624 .insert(answered_id.clone(), answered_tx);
3625 pending_requests
3626 .lock()
3627 .await
3628 .insert(unanswered_id.clone(), unanswered_tx);
3629
3630 msg_tx
3631 .send(Ok(InboundMessage::Response(JsonRpcResponse {
3632 jsonrpc: JsonRpcVersion,
3633 id: answered_id,
3634 outcome: JsonRpcOutcome::Result(serde_json::json!({ "ok": true })),
3635 })))
3636 .await
3637 .unwrap();
3638 command_tx.send(ClientCommand::Shutdown).await.unwrap();
3639 drop(command_tx);
3640
3641 let inner_result = LspClient::message_loop_inner(
3642 &mut transport,
3643 &mut msg_rx,
3644 &mut command_rx,
3645 &pending_requests,
3646 None,
3647 None,
3648 &Redactions::default(),
3649 None,
3650 )
3651 .await;
3652 assert!(inner_result.is_ok(), "got {inner_result:?}");
3653
3654 LspClient::drain_and_fail_pending(&pending_requests, &StdMutex::default()).await;
3657
3658 let answered = answered_rx.await.unwrap();
3659 assert_eq!(answered.unwrap(), serde_json::json!({ "ok": true }));
3660
3661 let unanswered = unanswered_rx.await.unwrap();
3662 assert_matches!(
3663 unanswered,
3664 Err(Error::ServerTerminated),
3665 "got {unanswered:?}"
3666 );
3667 assert!(pending_requests.lock().await.is_empty());
3668 }
3669
3670 #[tokio::test]
3680 async fn test_message_loop_end_to_end_resolves_answered_then_fails_unanswered_on_shutdown()
3681 {
3682 let (client, mut server) = fake_lsp_client();
3683 let mut reader = BufReader::new(&mut server.write_stdout);
3684
3685 let answered_client = client.clone();
3686 let answered_task = tokio::spawn(async move {
3687 answered_client
3688 .request::<_, Value>(
3689 "textDocument/hover",
3690 serde_json::json!({}),
3691 Duration::from_secs(30),
3692 )
3693 .await
3694 });
3695
3696 let request = read_framed_message(&mut reader).await;
3697 let id = request["id"].clone();
3698 write_response(
3699 &mut server.read_half_stdin,
3700 &id,
3701 serde_json::json!({ "ok": true }),
3702 )
3703 .await;
3704
3705 let answered = answered_task.await.unwrap().unwrap();
3706 assert_eq!(answered, serde_json::json!({ "ok": true }));
3707
3708 let unanswered_client = client.clone();
3709 let unanswered_task = tokio::spawn(async move {
3710 unanswered_client
3711 .request::<_, Value>(
3712 "textDocument/definition",
3713 serde_json::json!({}),
3714 Duration::from_secs(30),
3715 )
3716 .await
3717 });
3718
3719 let _unanswered_request = read_framed_message(&mut reader).await;
3723
3724 client.shutdown().await.unwrap();
3725
3726 let unanswered = tokio::time::timeout(Duration::from_millis(200), unanswered_task)
3727 .await
3728 .unwrap_or_else(|_| {
3729 panic!(
3730 "unanswered request must fail immediately on shutdown, not hang until its own timeout"
3731 )
3732 })
3733 .unwrap();
3734 assert_matches!(
3735 unanswered,
3736 Err(Error::ServerTerminated),
3737 "got {unanswered:?}"
3738 );
3739 }
3740
3741 #[tokio::test]
3748 async fn test_dense_concurrent_requests_all_resolve_to_matching_responses() {
3749 const REQUEST_COUNT: usize = 20;
3750 let (client, mut server) = fake_lsp_client();
3751
3752 let mut request_tasks = Vec::with_capacity(REQUEST_COUNT);
3759 for i in 0..REQUEST_COUNT {
3760 let client = client.clone();
3761 request_tasks.push(tokio::spawn(async move {
3762 client
3763 .request::<_, Value>(
3764 "textDocument/hover",
3765 serde_json::json!({ "n": i }),
3766 Duration::from_secs(30),
3767 )
3768 .await
3769 }));
3770 }
3771
3772 let server_task = tokio::spawn(async move {
3773 let mut reader = BufReader::new(&mut server.write_stdout);
3774 let mut requests = Vec::with_capacity(REQUEST_COUNT);
3775 for _ in 0..REQUEST_COUNT {
3776 requests.push(read_framed_message(&mut reader).await);
3777 }
3778 for request in requests.into_iter().rev() {
3779 let id = request["id"].clone();
3780 let n = request["params"]["n"].clone();
3781 write_response(
3782 &mut server.read_half_stdin,
3783 &id,
3784 serde_json::json!({ "echo": n }),
3785 )
3786 .await;
3787 }
3788 });
3789
3790 server_task.await.unwrap();
3791
3792 for (i, task) in request_tasks.into_iter().enumerate() {
3793 let value = task
3794 .await
3795 .unwrap()
3796 .unwrap_or_else(|e| panic!("request {i} failed: {e:?}"));
3797 assert_eq!(
3798 value["echo"],
3799 serde_json::json!(i),
3800 "response for request {i} carried the wrong payload -- id/response mismatch"
3801 );
3802 }
3803 }
3804
3805 #[tokio::test]
3808 async fn test_shutdown_on_clone_stops_shared_loop_then_owner_shutdown_succeeds() {
3809 let (client, _server) = fake_lsp_client();
3810
3811 client.clone().shutdown().await.unwrap();
3812
3813 let request = timeout(
3814 Duration::from_secs(2),
3815 client.request::<_, Value>("x/y", serde_json::json!({}), Duration::from_secs(30)),
3816 )
3817 .await
3818 .unwrap_or_else(|_| panic!("request on a stopped loop must fail fast"));
3819 assert_matches!(request, Err(Error::ServerTerminated), "got {request:?}");
3820
3821 client.shutdown().await.unwrap();
3822 }
3823
3824 #[tokio::test]
3827 async fn test_shutdown_until_expired_deadline_reports_timeout() {
3828 let (mut client, _server) = fake_lsp_client();
3829 let probe = client.clone();
3830
3831 let result = client
3832 .shutdown_until(Instant::now() - Duration::from_millis(50))
3833 .await;
3834
3835 assert_matches!(result, Err(Error::ShutdownTimeout), "got {result:?}");
3836 assert_matches!(probe.state().await, crate::lsp::ServerState::Shutdown);
3837 }
3838
3839 #[tokio::test]
3843 async fn test_shutdown_until_aborts_wedged_loop_and_fails_pending() {
3844 let (mut client, mut server) = fake_lsp_client();
3845 let mut reader = BufReader::new(&mut server.write_stdout);
3846
3847 let in_flight_client = client.clone();
3848 let in_flight = tokio::spawn(async move {
3849 in_flight_client
3850 .request::<_, Value>(
3851 "textDocument/hover",
3852 serde_json::json!({}),
3853 Duration::from_secs(30),
3854 )
3855 .await
3856 });
3857 let _ = read_framed_message(&mut reader).await;
3858
3859 client
3861 .notify("x/wedge", serde_json::json!({ "p": "a".repeat(1 << 20) }))
3862 .await
3863 .unwrap();
3864
3865 let deadline = Instant::now() + Duration::from_millis(200);
3866 let result = timeout(Duration::from_secs(5), client.shutdown_until(deadline))
3867 .await
3868 .unwrap_or_else(|_| panic!("shutdown_until must honor its deadline"));
3869 assert_matches!(result, Err(Error::ShutdownTimeout), "got {result:?}");
3870
3871 let in_flight = timeout(Duration::from_secs(1), in_flight)
3872 .await
3873 .unwrap_or_else(|_| panic!("in-flight request must be failed by the drain"))
3874 .unwrap();
3875 assert_matches!(in_flight, Err(Error::ServerTerminated), "got {in_flight:?}");
3876 }
3877
3878 #[tokio::test]
3882 async fn test_marked_restarted_client_fails_pending_with_server_restarted() {
3883 let (mut client, mut server) = fake_lsp_client();
3884 let mut reader = BufReader::new(&mut server.write_stdout);
3885
3886 let in_flight_client = client.clone();
3887 let in_flight = tokio::spawn(async move {
3888 in_flight_client
3889 .request::<_, Value>(
3890 "textDocument/hover",
3891 serde_json::json!({}),
3892 Duration::from_secs(30),
3893 )
3894 .await
3895 });
3896 let _ = read_framed_message(&mut reader).await;
3897
3898 client.mark_restarted(ServerId::from_static("rust"));
3899 client
3900 .shutdown_until(Instant::now() + Duration::from_millis(200))
3901 .await
3902 .ok();
3903
3904 let in_flight = timeout(Duration::from_secs(1), in_flight)
3905 .await
3906 .unwrap_or_else(|_| panic!("in-flight request must be failed by the drain"))
3907 .unwrap();
3908 assert!(
3909 matches!(&in_flight, Err(Error::ServerRestarted { server_id }) if server_id.as_str() == "rust"),
3910 "got {in_flight:?}"
3911 );
3912 }
3913
3914 #[tokio::test]
3921 async fn test_request_after_shutdown_fails_fast_instead_of_hanging() {
3922 let (client, _server) = fake_lsp_client();
3923 let post_shutdown_client = client.clone();
3924
3925 client.shutdown().await.unwrap();
3926
3927 let result = tokio::time::timeout(
3928 Duration::from_millis(200),
3929 post_shutdown_client.request::<_, Value>(
3930 "textDocument/hover",
3931 serde_json::json!({}),
3932 Duration::from_secs(30),
3933 ),
3934 )
3935 .await
3936 .unwrap_or_else(|_| {
3937 panic!(
3938 "request after shutdown must fail immediately, not hang until its own timeout"
3939 )
3940 });
3941
3942 assert_matches!(result, Err(Error::ServerTerminated), "got {result:?}");
3943 }
3944 }
3945}