1use std::collections::HashMap;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicI64, Ordering};
6
7use lsp_types::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, timeout};
14use tracing::{debug, error, trace, warn};
15
16use crate::config::LspServerConfig;
17use crate::error::{Error, Result};
18use crate::lsp::transport::{LspTransport, LspTransportReader};
19use crate::lsp::types::{
20 InboundMessage, JsonRpcError, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse,
21 LspNotification, RequestId,
22};
23
24const JSONRPC_VERSION: &str = "2.0";
26
27const SERVER_CANCELLED_CODE: i32 = -32802;
29
30const SERVER_CANCELLED_MAX_RETRIES: u32 = 3;
32
33const SERVER_CANCELLED_INITIAL_DELAY_MS: u64 = 500;
35
36const READER_CHANNEL_CAPACITY: usize = 100;
46
47const READER_TASK_ABORT_GRACE: Duration = Duration::from_secs(1);
57
58pub const CONTENT_MODIFIED_RETRY_METHODS: &[&str] = &[
81 "textDocument/signatureHelp",
82 "textDocument/inlayHint",
83 "textDocument/completion",
84 "textDocument/prepareCallHierarchy",
85 "callHierarchy/incomingCalls",
86 "callHierarchy/outgoingCalls",
87 "textDocument/diagnostic",
88 "textDocument/hover",
89 "textDocument/definition",
90 "textDocument/references",
91 "textDocument/implementation",
92 "textDocument/typeDefinition",
93 "textDocument/documentSymbol",
94 "workspace/symbol",
95];
96
97const MAX_ERROR_MESSAGE_CALLER_BYTES: usize = 4 * 1024;
111
112const COMPLETION_TIMEOUT_CAP: Duration = Duration::from_secs(10);
120
121const CODE_ACTION_RESOLVE_TIMEOUT_CAP: Duration = Duration::from_secs(10);
132
133type PendingRequests = HashMap<RequestId, oneshot::Sender<Result<Value>>>;
135
136fn spawn_reader_task(
156 mut reader: LspTransportReader,
157) -> (JoinHandle<()>, mpsc::Receiver<Result<InboundMessage>>) {
158 let (tx, rx) = mpsc::channel(READER_CHANNEL_CAPACITY);
159 let handle = tokio::spawn(async move {
160 loop {
161 let message = reader.receive().await;
162 let is_err = message.is_err();
163 if tx.send(message).await.is_err() || is_err {
164 break;
165 }
166 }
167 });
168 (handle, rx)
169}
170
171#[derive(Debug)]
179pub struct LspClient {
180 config: LspServerConfig,
182
183 state: Arc<Mutex<super::ServerState>>,
185
186 request_counter: Arc<AtomicI64>,
188
189 command_tx: mpsc::Sender<ClientCommand>,
191
192 pending_requests: Arc<Mutex<PendingRequests>>,
200
201 receiver_task: Option<JoinHandle<Result<()>>>,
203}
204
205impl Clone for LspClient {
206 fn clone(&self) -> Self {
211 Self {
212 config: self.config.clone(),
213 state: Arc::clone(&self.state),
214 request_counter: Arc::clone(&self.request_counter),
215 command_tx: self.command_tx.clone(),
216 pending_requests: Arc::clone(&self.pending_requests),
217 receiver_task: None,
218 }
219 }
220}
221
222enum ClientCommand {
224 SendRequest { request: JsonRpcRequest },
226 SendNotification {
228 method: String,
229 params: Option<Value>,
230 },
231 Shutdown,
233}
234
235impl LspClient {
236 #[must_use]
241 pub fn new(config: LspServerConfig) -> Self {
242 let (command_tx, _command_rx) = mpsc::channel(1); Self {
248 config,
249 state: Arc::new(Mutex::new(super::ServerState::Uninitialized)),
250 request_counter: Arc::new(AtomicI64::new(1)),
251 command_tx,
252 pending_requests: Arc::new(Mutex::new(HashMap::new())),
253 receiver_task: None,
254 }
255 }
256
257 #[cfg(test)]
261 pub(crate) fn from_transport(
262 config: LspServerConfig,
263 transport: (LspTransport, LspTransportReader),
264 ) -> Self {
265 let state = Arc::new(Mutex::new(super::ServerState::Initializing));
266 let request_counter = Arc::new(AtomicI64::new(1));
267 let pending_requests = Arc::new(Mutex::new(HashMap::new()));
268
269 let (command_tx, command_rx) = mpsc::channel(100);
270
271 let receiver_task = tokio::spawn(Self::message_loop(
272 transport,
273 command_rx,
274 Arc::clone(&pending_requests),
275 None,
276 None,
277 ));
278
279 Self {
280 config,
281 state,
282 request_counter,
283 command_tx,
284 pending_requests,
285 receiver_task: Some(receiver_task),
286 }
287 }
288
289 pub(crate) fn from_transport_with_notifications(
298 config: LspServerConfig,
299 transport: (LspTransport, LspTransportReader),
300 notification_tx: mpsc::Sender<LspNotification>,
301 lifecycle_tx: mpsc::Sender<LspNotification>,
302 ) -> Self {
303 let state = Arc::new(Mutex::new(super::ServerState::Initializing));
304 let request_counter = Arc::new(AtomicI64::new(1));
305 let pending_requests = Arc::new(Mutex::new(HashMap::new()));
306
307 let (command_tx, command_rx) = mpsc::channel(100);
308
309 let receiver_task = tokio::spawn(Self::message_loop(
310 transport,
311 command_rx,
312 Arc::clone(&pending_requests),
313 Some(notification_tx),
314 Some(lifecycle_tx),
315 ));
316
317 Self {
318 config,
319 state,
320 request_counter,
321 command_tx,
322 pending_requests,
323 receiver_task: Some(receiver_task),
324 }
325 }
326
327 #[must_use]
329 pub fn language_id(&self) -> &str {
330 &self.config.language_id
331 }
332
333 pub async fn state(&self) -> super::ServerState {
335 *self.state.lock().await
336 }
337
338 #[must_use]
393 pub fn request_timeout(&self) -> Duration {
394 Duration::from_secs(
395 self.config
396 .request_timeout_seconds
397 .clamp(1, crate::config::MAX_TIMEOUT_SECONDS),
398 )
399 }
400
401 #[must_use]
425 pub fn completion_timeout(&self) -> Duration {
426 self.request_timeout().min(COMPLETION_TIMEOUT_CAP)
427 }
428
429 #[must_use]
449 pub fn code_action_resolve_timeout(&self) -> Duration {
450 self.request_timeout().min(CODE_ACTION_RESOLVE_TIMEOUT_CAP)
451 }
452
453 async fn register_and_send_request(
456 &self,
457 request: JsonRpcRequest,
458 response_tx: oneshot::Sender<Result<Value>>,
459 ) -> Result<()> {
460 let id = request.id.clone();
461
462 self.pending_requests
463 .lock()
464 .await
465 .insert(id.clone(), response_tx);
466
467 if self
468 .command_tx
469 .send(ClientCommand::SendRequest { request })
470 .await
471 .is_err()
472 {
473 self.pending_requests.lock().await.remove(&id);
474 return Err(Error::ServerTerminated);
475 }
476
477 Ok(())
478 }
479
480 pub async fn request<P, R>(
509 &self,
510 method: &str,
511 params: P,
512 timeout_duration: Duration,
513 ) -> Result<R>
514 where
515 P: Serialize,
516 R: DeserializeOwned,
517 {
518 let params_value = Self::omit_null_params(serde_json::to_value(params)?);
519 let mut delay_ms = SERVER_CANCELLED_INITIAL_DELAY_MS;
520
521 for attempt in 0..=SERVER_CANCELLED_MAX_RETRIES {
522 if attempt > 0 {
523 debug!(
524 "Retrying {} (attempt {}/{}), backoff={}ms",
525 method, attempt, SERVER_CANCELLED_MAX_RETRIES, delay_ms
526 );
527 tokio::time::sleep(Duration::from_millis(delay_ms)).await;
528 delay_ms *= 2;
529 }
530
531 let id = RequestId::Number(self.request_counter.fetch_add(1, Ordering::SeqCst));
532 let (response_tx, response_rx) = oneshot::channel();
533 let request = JsonRpcRequest {
534 jsonrpc: JSONRPC_VERSION.to_string(),
535 id: id.clone(),
536 method: method.to_string(),
537 params: params_value.clone(),
538 };
539
540 debug!("Sending request: {} (id={:?})", method, id);
541
542 self.register_and_send_request(request, response_tx).await?;
543
544 let outcome = match timeout(timeout_duration, response_rx).await {
545 Ok(received) => received.map_err(|_| Error::ServerTerminated)?,
546 Err(_elapsed) => {
547 self.pending_requests.lock().await.remove(&id);
552 return Err(Error::Timeout(timeout_duration.as_secs()));
553 }
554 };
555
556 match outcome {
557 Ok(result_value) => {
558 return serde_json::from_value(result_value).map_err(|e| {
559 Error::LspProtocolError(format!("Failed to deserialize response: {e}"))
560 });
561 }
562 Err(Error::LspServerError {
563 code,
564 message,
565 data,
566 }) if (code == SERVER_CANCELLED_CODE
567 || (LspErrorCodes::from(code) == LspErrorCodes::ContentModified
568 && CONTENT_MODIFIED_RETRY_METHODS.contains(&method)))
569 && Self::should_retrigger(data.as_ref()) =>
570 {
571 if attempt == SERVER_CANCELLED_MAX_RETRIES {
572 error!(
577 "LSP error response: {} (code {}) on '{}' (id={:?}), retries exhausted",
578 Self::truncate_error_message_for_log(&message),
579 code,
580 method,
581 id
582 );
583 return Err(Error::LspServerError {
584 code,
585 message,
586 data,
587 });
588 }
589 warn!(
590 "LSP error response: {} (code {}) on '{}' (id={:?}), will retry",
591 Self::truncate_error_message_for_log(&message),
592 code,
593 method,
594 id
595 );
596 }
598 Err(Error::LspServerError {
599 code,
600 message,
601 data,
602 }) => {
603 error!(
604 "LSP error response: {} (code {}) on '{}' (id={:?})",
605 Self::truncate_error_message_for_log(&message),
606 code,
607 method,
608 id
609 );
610 return Err(Error::LspServerError {
611 code,
612 message,
613 data,
614 });
615 }
616 Err(e) => return Err(e),
617 }
618 }
619
620 Err(Error::ServerTerminated)
621 }
622
623 pub async fn request_typed<R>(
633 &self,
634 params: R::Params,
635 timeout_duration: Duration,
636 ) -> Result<R::Result>
637 where
638 R: lsp_types::Request,
639 {
640 self.request(R::METHOD.as_str(), params, timeout_duration)
641 .await
642 }
643
644 fn should_retrigger(data: Option<&Value>) -> bool {
658 data.is_none_or(|v| {
659 v.get("retriggerRequest")
660 .and_then(Value::as_bool)
661 .unwrap_or(true)
662 })
663 }
664
665 pub(crate) async fn fail_pending_requests(&self) {
673 Self::drain_and_fail_pending(&self.pending_requests).await;
674 }
675
676 async fn drain_and_fail_pending(pending: &Arc<Mutex<PendingRequests>>) {
680 for (_, sender) in pending.lock().await.drain() {
681 let _ = sender.send(Err(Error::ServerTerminated));
682 }
683 }
684
685 pub async fn notify<P>(&self, method: &str, params: P) -> Result<()>
691 where
692 P: Serialize,
693 {
694 let params_value = Self::omit_null_params(serde_json::to_value(params)?);
695
696 debug!("Sending notification: {}", method);
697
698 self.command_tx
699 .send(ClientCommand::SendNotification {
700 method: method.to_string(),
701 params: params_value,
702 })
703 .await
704 .map_err(|_| Error::ServerTerminated)?;
705
706 Ok(())
707 }
708
709 pub async fn notify_typed<N>(&self, params: N::Params) -> Result<()>
717 where
718 N: lsp_types::Notification,
719 {
720 self.notify(N::METHOD.as_str(), params).await
721 }
722
723 pub async fn shutdown(mut self) -> Result<()> {
731 debug!("Shutting down LSP client");
732
733 let _ = self.command_tx.send(ClientCommand::Shutdown).await;
734
735 if let Some(task) = self.receiver_task.take() {
736 task.await
737 .map_err(|e| Error::Transport(format!("Receiver task failed: {e}")))??;
738 }
739
740 *self.state.lock().await = super::ServerState::Shutdown;
741
742 Ok(())
743 }
744
745 async fn message_loop(
752 transport: (LspTransport, LspTransportReader),
753 mut command_rx: mpsc::Receiver<ClientCommand>,
754 pending_requests: Arc<Mutex<PendingRequests>>,
755 notification_tx: Option<mpsc::Sender<LspNotification>>,
756 lifecycle_tx: Option<mpsc::Sender<LspNotification>>,
757 ) -> Result<()> {
758 debug!("Message loop started");
759 let (mut transport, reader) = transport;
760 let (reader_handle, mut msg_rx) = spawn_reader_task(reader);
761 let result = {
762 let _abort_reader_on_drop = crate::AbortOnDrop(&reader_handle);
764 Self::message_loop_inner(
765 &mut transport,
766 &mut msg_rx,
767 &mut command_rx,
768 &pending_requests,
769 notification_tx.as_ref(),
770 lifecycle_tx.as_ref(),
771 )
772 .await
773 };
774 let _ = timeout(READER_TASK_ABORT_GRACE, reader_handle).await;
775 drop(command_rx);
782 Self::drain_and_fail_pending(&pending_requests).await;
786 if let Err(ref e) = result {
787 error!("Message loop exiting with error: {}", e);
788 } else {
789 debug!("Message loop exiting normally");
790 }
791 result
792 }
793
794 fn omit_null_params(params: Value) -> Option<Value> {
798 if params.is_null() { None } else { Some(params) }
799 }
800
801 fn truncate_error_message_for_log(message: &str) -> String {
809 crate::util::truncate_str(message, crate::util::MAX_LOG_STRING_BYTES)
810 }
811
812 fn notification_lane<'a>(
839 notification: &LspNotification,
840 notification_tx: Option<&'a mpsc::Sender<LspNotification>>,
841 lifecycle_tx: Option<&'a mpsc::Sender<LspNotification>>,
842 ) -> Option<(&'static str, &'a mpsc::Sender<LspNotification>)> {
843 match notification {
844 LspNotification::PublishDiagnostics(_)
845 | LspNotification::LogMessage(_)
846 | LspNotification::ShowMessage(_) => notification_tx.map(|tx| ("notification", tx)),
847 LspNotification::Progress(params) => {
848 crate::lsp::types::ProgressKind::from_value(¶ms.value)
849 .and(lifecycle_tx)
850 .map(|tx| ("lifecycle", tx))
851 }
852 LspNotification::Other { method, .. } if method.as_ref() == "$/progress" => None,
853 LspNotification::Other { .. } => lifecycle_tx.map(|tx| ("lifecycle", tx)),
854 }
855 }
856
857 #[allow(clippy::too_many_lines)]
858 async fn message_loop_inner(
859 transport: &mut LspTransport,
860 msg_rx: &mut mpsc::Receiver<Result<InboundMessage>>,
861 command_rx: &mut mpsc::Receiver<ClientCommand>,
862 pending_requests: &Arc<Mutex<PendingRequests>>,
863 notification_tx: Option<&mpsc::Sender<LspNotification>>,
864 lifecycle_tx: Option<&mpsc::Sender<LspNotification>>,
865 ) -> Result<()> {
866 loop {
867 tokio::select! {
868 Some(command) = command_rx.recv() => {
869 match command {
870 ClientCommand::SendRequest { request } => {
871 let value = serde_json::to_value(&request)?;
872 transport.send(&value).await?;
873 }
874 ClientCommand::SendNotification { method, params } => {
875 let notification = serde_json::to_value(JsonRpcNotification {
876 jsonrpc: JSONRPC_VERSION.to_string(),
877 method,
878 params,
879 })?;
880 transport.send(¬ification).await?;
881 }
882 ClientCommand::Shutdown => {
883 debug!("Client shutdown requested");
884 while let Ok(message) = msg_rx.try_recv() {
888 match message {
889 Ok(m) => {
890 Self::handle_inbound_message(
891 transport,
892 m,
893 pending_requests,
894 notification_tx,
895 lifecycle_tx,
896 )
897 .await?;
898 }
899 Err(e) => {
900 error!("Transport receive error while draining on shutdown: {}", e);
901 break;
902 }
903 }
904 }
905 break;
906 }
907 }
908 }
909
910 message = msg_rx.recv() => {
912 let message = match message {
913 Some(Ok(m)) => m,
914 Some(Err(e)) => {
915 error!("Transport receive error: {}", e);
916 return Err(e);
917 }
918 None => {
919 error!("Transport receive error: reader task ended unexpectedly");
921 return Err(Error::ServerTerminated);
922 }
923 };
924 Self::handle_inbound_message(
925 transport,
926 message,
927 pending_requests,
928 notification_tx,
929 lifecycle_tx,
930 )
931 .await?;
932 }
933 }
934 }
935
936 Ok(())
937 }
938
939 async fn handle_inbound_message(
945 transport: &mut LspTransport,
946 message: InboundMessage,
947 pending_requests: &Arc<Mutex<PendingRequests>>,
948 notification_tx: Option<&mpsc::Sender<LspNotification>>,
949 lifecycle_tx: Option<&mpsc::Sender<LspNotification>>,
950 ) -> Result<()> {
951 match message {
952 InboundMessage::Response(response) => {
953 trace!("Received response: id={:?}", response.id);
954
955 let sender = pending_requests.lock().await.remove(&response.id);
956
957 if let Some(sender) = sender {
958 if let Some(error) = response.error {
959 trace!(
970 "LSP error response: {} (code {})",
971 Self::truncate_error_message_for_log(&error.message),
972 error.code
973 );
974 let caller_message = crate::util::truncate_str(
980 &error.message,
981 MAX_ERROR_MESSAGE_CALLER_BYTES,
982 );
983 let _ = sender.send(Err(Error::LspServerError {
984 code: error.code,
985 message: caller_message,
986 data: error.data,
987 }));
988 } else if let Some(result) = response.result {
989 let _ = sender.send(Ok(result));
990 } else {
991 trace!("Response with null result: {:?}", response.id);
994 let _ = sender.send(Ok(Value::Null));
995 }
996 } else {
997 warn!(
998 "Received response for unknown request ID: {:?}",
999 response.id
1000 );
1001 }
1002 }
1003 InboundMessage::Request(request) => {
1004 debug!(
1005 "Received server request: {} (id={:?})",
1006 request.method, request.id
1007 );
1008 let response = Self::server_request_response(request);
1009 let value = serde_json::to_value(&response)?;
1010 transport.send(&value).await?;
1011 }
1012 InboundMessage::Notification(notification) => {
1013 debug!("Received notification: {}", notification.method);
1014
1015 let typed = LspNotification::parse(¬ification.method, notification.params);
1017
1018 let destination = Self::notification_lane(&typed, notification_tx, lifecycle_tx);
1019
1020 if let Some((lane, tx)) = destination {
1021 if let LspNotification::PublishDiagnostics(ref params) = typed {
1023 debug!(
1024 "Forwarding diagnostics for {}: {} items",
1025 params.uri.as_ref(),
1026 params.diagnostics.len()
1027 );
1028 } else {
1029 trace!("Forwarding notification: {:?}", typed);
1030 }
1031
1032 if tx.try_send(typed).is_err() {
1034 warn!(
1035 "Dropping notification: lane={lane}, method={} \
1036 (channel full or closed)",
1037 notification.method
1038 );
1039 }
1040 }
1041 }
1042 }
1043
1044 Ok(())
1045 }
1046
1047 fn server_request_response(request: JsonRpcRequest) -> JsonRpcResponse {
1048 match Self::server_request_result(&request.method, request.params.as_ref()) {
1049 Ok(result) => JsonRpcResponse {
1050 jsonrpc: JSONRPC_VERSION.to_string(),
1051 id: request.id,
1052 result: Some(result),
1053 error: None,
1054 },
1055 Err(error) => JsonRpcResponse {
1056 jsonrpc: JSONRPC_VERSION.to_string(),
1057 id: request.id,
1058 result: None,
1059 error: Some(error),
1060 },
1061 }
1062 }
1063
1064 fn server_request_result(
1065 method: &str,
1066 params: Option<&Value>,
1067 ) -> std::result::Result<Value, JsonRpcError> {
1068 match method {
1069 "client/registerCapability"
1070 | "client/unregisterCapability"
1071 | "workspace/workspaceFolders"
1072 | "workspace/diagnostic/refresh"
1073 | "workspace/semanticTokens/refresh"
1074 | "workspace/inlayHint/refresh"
1075 | "workspace/codeLens/refresh"
1076 | "window/showMessageRequest"
1077 | "window/workDoneProgress/create" => Ok(Value::Null),
1078 "workspace/configuration" => Ok(Self::workspace_configuration_result(params)),
1079 "workspace/applyEdit" => Ok(serde_json::json!({ "applied": false })),
1080 _ => Err(JsonRpcError {
1081 code: -32601,
1082 message: format!("Unhandled server request: {method}"),
1083 data: None,
1084 }),
1085 }
1086 }
1087
1088 fn workspace_configuration_result(params: Option<&Value>) -> Value {
1089 let item_count = params
1090 .and_then(|value| value.get("items"))
1091 .and_then(Value::as_array)
1092 .map_or(0, Vec::len);
1093
1094 Value::Array(vec![Value::Null; item_count])
1095 }
1096}
1097
1098#[cfg(test)]
1099#[allow(clippy::unwrap_used)]
1100mod tests {
1101 use super::*;
1102
1103 #[test]
1104 fn test_request_id_generation() {
1105 let counter = AtomicI64::new(1);
1106
1107 let id1 = counter.fetch_add(1, Ordering::SeqCst);
1108 let id2 = counter.fetch_add(1, Ordering::SeqCst);
1109 let id3 = counter.fetch_add(1, Ordering::SeqCst);
1110
1111 assert_eq!(id1, 1);
1112 assert_eq!(id2, 2);
1113 assert_eq!(id3, 3);
1114 }
1115
1116 #[test]
1117 fn test_client_creation() {
1118 let config = LspServerConfig::rust_analyzer();
1119
1120 let client = LspClient::new(config);
1121 assert_eq!(client.language_id(), "rust");
1122 }
1123
1124 #[test]
1125 fn test_client_clone() {
1126 let config = LspServerConfig::rust_analyzer();
1127 let client = LspClient::new(config);
1128
1129 #[allow(clippy::redundant_clone)]
1130 let cloned = client.clone();
1131 assert_eq!(cloned.language_id(), "rust");
1132
1133 assert!(
1134 cloned.receiver_task.is_none(),
1135 "Cloned client should not own receiver task"
1136 );
1137 }
1138
1139 #[test]
1140 fn test_request_timeout_and_completion_timeout_at_default() {
1141 let config = LspServerConfig::rust_analyzer();
1142 let client = LspClient::new(config);
1143
1144 assert_eq!(client.request_timeout(), Duration::from_secs(30));
1145 assert_eq!(client.completion_timeout(), Duration::from_secs(10));
1146 }
1147
1148 #[test]
1149 fn test_completion_timeout_clamps_to_ten_seconds() {
1150 for secs in [1, 2, 3, 30, 300] {
1151 let mut config = LspServerConfig::rust_analyzer();
1152 config.request_timeout_seconds = secs;
1153 let client = LspClient::new(config);
1154
1155 assert_eq!(
1156 client.completion_timeout(),
1157 Duration::from_secs(secs.min(10)),
1158 "request_timeout_seconds={secs}"
1159 );
1160 assert!(client.completion_timeout() <= client.request_timeout());
1161 }
1162 }
1163
1164 #[test]
1165 fn test_code_action_resolve_timeout_clamps_to_ten_seconds() {
1166 for secs in [1, 2, 3, 30, 300] {
1167 let mut config = LspServerConfig::rust_analyzer();
1168 config.request_timeout_seconds = secs;
1169 let client = LspClient::new(config);
1170
1171 assert_eq!(
1172 client.code_action_resolve_timeout(),
1173 Duration::from_secs(secs.min(10)),
1174 "request_timeout_seconds={secs}"
1175 );
1176 assert!(client.code_action_resolve_timeout() <= client.request_timeout());
1177 }
1178 }
1179
1180 #[test]
1181 fn test_request_timeout_clamps_zero_to_one_second() {
1182 let mut config = LspServerConfig::rust_analyzer();
1183 config.request_timeout_seconds = 0;
1184 let client = LspClient::new(config);
1185
1186 assert_eq!(client.request_timeout(), Duration::from_secs(1));
1187 assert_eq!(client.completion_timeout(), Duration::from_secs(1));
1188 }
1189
1190 #[test]
1191 fn test_request_timeout_clamps_above_max_to_max() {
1192 let mut config = LspServerConfig::rust_analyzer();
1193 config.request_timeout_seconds = u64::MAX;
1194 let client = LspClient::new(config);
1195
1196 assert_eq!(
1197 client.request_timeout(),
1198 Duration::from_secs(crate::config::MAX_TIMEOUT_SECONDS)
1199 );
1200 }
1201
1202 #[test]
1203 fn test_request_timeout_independent_per_server() {
1204 let mut config_a = LspServerConfig::rust_analyzer();
1205 config_a.request_timeout_seconds = 5;
1206 let mut config_b = LspServerConfig::pyright();
1207 config_b.request_timeout_seconds = 15;
1208
1209 let client_a = LspClient::new(config_a);
1210 let client_b = LspClient::new(config_b);
1211
1212 assert_eq!(client_a.request_timeout(), Duration::from_secs(5));
1213 assert_eq!(client_b.request_timeout(), Duration::from_secs(15));
1214 }
1215
1216 #[test]
1217 fn test_register_capability_request_is_acknowledged() {
1218 let request = JsonRpcRequest {
1219 jsonrpc: JSONRPC_VERSION.to_string(),
1220 id: RequestId::String("ts1".to_string()),
1221 method: "client/registerCapability".to_string(),
1222 params: Some(serde_json::json!({ "registrations": [] })),
1223 };
1224
1225 let response = LspClient::server_request_response(request);
1226
1227 assert_eq!(response.id, RequestId::String("ts1".to_string()));
1228 assert_eq!(response.result, Some(Value::Null));
1229 assert!(response.error.is_none());
1230 }
1231
1232 #[test]
1233 fn test_workspace_configuration_request_returns_null_per_item() {
1234 let result = LspClient::workspace_configuration_result(Some(&serde_json::json!({
1235 "items": [{ "section": "typescript" }, { "section": "editor" }]
1236 })));
1237
1238 assert_eq!(result, serde_json::json!([null, null]));
1239 }
1240
1241 #[test]
1246 fn test_work_done_progress_create_request_is_acknowledged() {
1247 let request = JsonRpcRequest {
1248 jsonrpc: JSONRPC_VERSION.to_string(),
1249 id: RequestId::String("wdp1".to_string()),
1250 method: "window/workDoneProgress/create".to_string(),
1251 params: Some(serde_json::json!({ "token": "indexing" })),
1252 };
1253
1254 let response = LspClient::server_request_response(request);
1255
1256 assert_eq!(response.result, Some(Value::Null));
1257 assert!(response.error.is_none());
1258 }
1259
1260 #[test]
1265 fn test_report_progress_frame_reaches_neither_lane() {
1266 let (notification_tx, _notification_rx) = mpsc::channel(8);
1267 let (lifecycle_tx, _lifecycle_rx) = mpsc::channel(8);
1268
1269 let report = LspNotification::Progress(lsp_types::ProgressParams {
1270 token: lsp_types::ProgressToken::Int(1),
1271 value: serde_json::json!({ "kind": "report", "percentage": 50 }),
1272 });
1273
1274 let destination =
1275 LspClient::notification_lane(&report, Some(¬ification_tx), Some(&lifecycle_tx));
1276
1277 assert!(
1278 destination.is_none(),
1279 "a report-kind frame must be dropped before reaching either lane"
1280 );
1281 }
1282
1283 #[test]
1290 fn test_malformed_progress_other_reaches_neither_lane() {
1291 let (notification_tx, _notification_rx) = mpsc::channel(8);
1292 let (lifecycle_tx, _lifecycle_rx) = mpsc::channel(8);
1293
1294 let malformed = LspNotification::Other {
1295 method: std::borrow::Cow::Borrowed("$/progress"),
1296 params: None,
1297 };
1298
1299 let destination =
1300 LspClient::notification_lane(&malformed, Some(¬ification_tx), Some(&lifecycle_tx));
1301
1302 assert!(
1303 destination.is_none(),
1304 "a malformed $/progress frame must be dropped, not routed to the lifecycle lane"
1305 );
1306 }
1307
1308 #[test]
1313 fn test_begin_and_other_notifications_reach_lifecycle_lane() {
1314 let (notification_tx, _notification_rx) = mpsc::channel(8);
1315 let (lifecycle_tx, _lifecycle_rx) = mpsc::channel(8);
1316
1317 let begin = LspNotification::Progress(lsp_types::ProgressParams {
1318 token: lsp_types::ProgressToken::Int(1),
1319 value: serde_json::json!({ "kind": "begin", "title": "Indexing" }),
1320 });
1321 assert_eq!(
1322 LspClient::notification_lane(&begin, Some(¬ification_tx), Some(&lifecycle_tx))
1323 .map(|(lane, _)| lane),
1324 Some("lifecycle")
1325 );
1326
1327 let server_status = LspNotification::Other {
1328 method: std::borrow::Cow::Borrowed("experimental/serverStatus"),
1329 params: Some(serde_json::json!({ "quiescent": false })),
1330 };
1331 assert_eq!(
1332 LspClient::notification_lane(
1333 &server_status,
1334 Some(¬ification_tx),
1335 Some(&lifecycle_tx)
1336 )
1337 .map(|(lane, _)| lane),
1338 Some("lifecycle")
1339 );
1340 }
1341
1342 #[test]
1343 fn test_unknown_server_request_returns_method_not_found() {
1344 let request = JsonRpcRequest {
1345 jsonrpc: JSONRPC_VERSION.to_string(),
1346 id: RequestId::String("unknown-1".to_string()),
1347 method: "custom/request".to_string(),
1348 params: None,
1349 };
1350
1351 let response = LspClient::server_request_response(request);
1352
1353 assert!(response.result.is_none());
1354 match response.error {
1355 Some(error) => {
1356 assert_eq!(error.code, -32601);
1357 assert_eq!(error.message, "Unhandled server request: custom/request");
1358 }
1359 None => panic!("unknown request should return error"),
1360 }
1361 }
1362
1363 #[tokio::test]
1364 async fn test_null_response_handling() {
1365 use crate::lsp::types::{JsonRpcResponse, RequestId};
1366
1367 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
1368
1369 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
1370
1371 pending_requests
1372 .lock()
1373 .await
1374 .insert(RequestId::Number(1), response_tx);
1375
1376 let null_response = JsonRpcResponse {
1377 jsonrpc: "2.0".to_string(),
1378 id: RequestId::Number(1),
1379 result: None,
1380 error: None,
1381 };
1382
1383 let sender = pending_requests.lock().await.remove(&null_response.id);
1384 if let Some(sender) = sender {
1385 let _ = sender.send(Ok(Value::Null));
1386 }
1387
1388 let timeout_result =
1389 tokio::time::timeout(tokio::time::Duration::from_millis(100), response_rx).await;
1390
1391 assert!(timeout_result.is_ok(), "Should not timeout");
1392
1393 let channel_result = timeout_result.unwrap();
1394 assert!(
1395 channel_result.is_ok(),
1396 "Channel should not be closed: {:?}",
1397 channel_result.err()
1398 );
1399
1400 let response = channel_result.unwrap();
1401 assert!(
1402 response.is_ok(),
1403 "Should receive Ok(Value::Null), not Err: {:?}",
1404 response.err()
1405 );
1406
1407 let value = response.unwrap();
1408 assert_eq!(value, Value::Null, "Should receive Value::Null");
1409 }
1410
1411 #[tokio::test]
1412 async fn test_error_response_handling() {
1413 use crate::lsp::types::{JsonRpcError, JsonRpcResponse, RequestId};
1414
1415 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
1416 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
1417
1418 pending_requests
1419 .lock()
1420 .await
1421 .insert(RequestId::Number(1), response_tx);
1422
1423 let error_response = JsonRpcResponse {
1424 jsonrpc: "2.0".to_string(),
1425 id: RequestId::Number(1),
1426 result: None,
1427 error: Some(JsonRpcError {
1428 code: -32601,
1429 message: "Method not found".to_string(),
1430 data: None,
1431 }),
1432 };
1433
1434 let sender = pending_requests.lock().await.remove(&error_response.id);
1435 if let Some(sender) = sender
1436 && let Some(error) = error_response.error
1437 {
1438 let _ = sender.send(Err(Error::LspServerError {
1439 code: error.code,
1440 message: error.message,
1441 data: error.data,
1442 }));
1443 }
1444
1445 let result = response_rx.await.unwrap();
1446 assert!(result.is_err(), "Should receive error");
1447
1448 if let Err(Error::LspServerError { code, message, .. }) = result {
1449 assert_eq!(code, -32601);
1450 assert_eq!(message, "Method not found");
1451 } else {
1452 panic!("Expected LspServerError");
1453 }
1454 }
1455
1456 #[tokio::test]
1457 async fn test_unknown_request_id() {
1458 use crate::lsp::types::{JsonRpcResponse, RequestId};
1459
1460 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
1461
1462 let response = JsonRpcResponse {
1463 jsonrpc: "2.0".to_string(),
1464 id: RequestId::Number(999),
1465 result: Some(Value::Null),
1466 error: None,
1467 };
1468
1469 let sender = pending_requests.lock().await.remove(&response.id);
1470 assert!(sender.is_none(), "Should not find sender for unknown ID");
1471 }
1472
1473 #[test]
1474 fn test_truncate_error_message_for_log_handles_multibyte_boundary() {
1475 let message = format!("{}€{}", "x".repeat(199), "y".repeat(50));
1477
1478 let truncated = LspClient::truncate_error_message_for_log(&message);
1479
1480 assert_eq!(truncated, format!("{}... (truncated)", "x".repeat(199)));
1483 }
1484
1485 #[test]
1486 fn test_truncate_error_message_for_log_no_truncation_at_or_below_limit() {
1487 let exact = "x".repeat(200);
1488 assert_eq!(LspClient::truncate_error_message_for_log(&exact), exact);
1489 assert_eq!(LspClient::truncate_error_message_for_log(""), "");
1490 }
1491
1492 #[test]
1493 fn test_truncate_error_message_for_log_truncates_just_above_limit() {
1494 let message = "x".repeat(201);
1495 assert_eq!(
1496 LspClient::truncate_error_message_for_log(&message),
1497 format!("{}... (truncated)", "x".repeat(200))
1498 );
1499 }
1500
1501 #[test]
1502 fn test_truncate_error_message_for_log_handles_wide_char_at_limit() {
1503 let message = format!("{}{}", "x".repeat(197), "🦀".repeat(10));
1505
1506 let truncated = LspClient::truncate_error_message_for_log(&message);
1507
1508 assert_eq!(truncated, format!("{}... (truncated)", "x".repeat(197)));
1509 }
1510
1511 #[tokio::test]
1512 async fn test_concurrent_request_ids() {
1513 let counter = Arc::new(AtomicI64::new(1));
1514
1515 let counter1 = Arc::clone(&counter);
1516 let counter2 = Arc::clone(&counter);
1517 let counter3 = Arc::clone(&counter);
1518
1519 let handles = vec![
1520 tokio::spawn(async move { counter1.fetch_add(1, Ordering::SeqCst) }),
1521 tokio::spawn(async move { counter2.fetch_add(1, Ordering::SeqCst) }),
1522 tokio::spawn(async move { counter3.fetch_add(1, Ordering::SeqCst) }),
1523 ];
1524
1525 let mut ids = Vec::new();
1526 for handle in handles {
1527 ids.push(handle.await.unwrap());
1528 }
1529
1530 ids.sort_unstable();
1531 assert_eq!(ids, vec![1, 2, 3], "IDs should be unique and sequential");
1532 }
1533
1534 #[test]
1535 fn test_jsonrpc_version_constant() {
1536 assert_eq!(JSONRPC_VERSION, "2.0");
1537 }
1538
1539 #[cfg(unix)]
1548 #[tokio::test]
1549 async fn test_request_timeout_removes_pending_entry() {
1550 let mut child = tokio::process::Command::new("sleep")
1551 .arg("2")
1552 .stdin(std::process::Stdio::piped())
1553 .stdout(std::process::Stdio::piped())
1554 .kill_on_drop(true)
1555 .spawn()
1556 .unwrap();
1557 let stdin = child.stdin.take().unwrap();
1558 let stdout = child.stdout.take().unwrap();
1559
1560 let transport = LspTransport::new(stdin, stdout);
1561 let client = LspClient::from_transport(LspServerConfig::rust_analyzer(), transport);
1562
1563 let result: Result<Value> = client
1564 .request(
1565 "textDocument/hover",
1566 serde_json::json!({}),
1567 Duration::from_millis(50),
1568 )
1569 .await;
1570
1571 assert!(matches!(result, Err(Error::Timeout(_))), "got {result:?}");
1572 assert!(
1573 client.pending_requests.lock().await.is_empty(),
1574 "timed-out request must not remain in pending_requests"
1575 );
1576 }
1577
1578 #[tokio::test]
1582 async fn test_fail_pending_requests_resolves_all_as_server_terminated() {
1583 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
1584 let (command_tx, _command_rx) = mpsc::channel(1);
1585
1586 let client = LspClient {
1587 config: LspServerConfig::rust_analyzer(),
1588 state: Arc::new(Mutex::new(super::super::ServerState::Ready)),
1589 request_counter: Arc::new(AtomicI64::new(1)),
1590 command_tx,
1591 pending_requests: Arc::clone(&pending_requests),
1592 receiver_task: None,
1593 };
1594
1595 let (tx1, rx1) = oneshot::channel::<Result<Value>>();
1596 let (tx2, rx2) = oneshot::channel::<Result<Value>>();
1597 pending_requests
1598 .lock()
1599 .await
1600 .insert(RequestId::Number(1), tx1);
1601 pending_requests
1602 .lock()
1603 .await
1604 .insert(RequestId::Number(2), tx2);
1605
1606 client.fail_pending_requests().await;
1607
1608 assert!(pending_requests.lock().await.is_empty());
1609 assert!(matches!(rx1.await.unwrap(), Err(Error::ServerTerminated)));
1610 assert!(matches!(rx2.await.unwrap(), Err(Error::ServerTerminated)));
1611 }
1612
1613 #[test]
1614 fn test_should_retrigger_defaults_to_true_when_data_absent() {
1615 assert!(LspClient::should_retrigger(None));
1616 }
1617
1618 #[test]
1619 fn test_should_retrigger_false_when_flag_false() {
1620 assert!(!LspClient::should_retrigger(Some(&serde_json::json!({
1621 "retriggerRequest": false
1622 }))));
1623 }
1624
1625 #[test]
1626 fn test_should_retrigger_true_when_flag_true() {
1627 assert!(LspClient::should_retrigger(Some(&serde_json::json!({
1628 "retriggerRequest": true
1629 }))));
1630 }
1631
1632 mod void_params_wire {
1637 use tokio::io::BufReader;
1638
1639 use super::*;
1640 use crate::test_lsp::{fake_lsp_client, read_framed_message, write_response};
1641
1642 #[tokio::test]
1643 async fn test_request_with_null_params_omits_params_key() {
1644 let (client, mut server) = fake_lsp_client();
1645
1646 let request_task = tokio::spawn(async move {
1647 client
1648 .request::<_, Value>("shutdown", Value::Null, Duration::from_secs(5))
1649 .await
1650 });
1651
1652 let mut reader = BufReader::new(&mut server.write_stdout);
1653 let request = read_framed_message(&mut reader).await;
1654
1655 assert_eq!(request["method"], "shutdown");
1656 assert!(
1657 request.get("params").is_none(),
1658 "null params must be omitted, got: {request}"
1659 );
1660
1661 write_response(&mut server.read_half_stdin, &request["id"], Value::Null).await;
1662 request_task.await.unwrap().unwrap();
1663 }
1664
1665 #[tokio::test]
1666 async fn test_notify_with_null_params_omits_params_key() {
1667 let (client, mut server) = fake_lsp_client();
1668
1669 client.notify("exit", Value::Null).await.unwrap();
1670
1671 let mut reader = BufReader::new(&mut server.write_stdout);
1672 let notification = read_framed_message(&mut reader).await;
1673
1674 assert_eq!(notification["method"], "exit");
1675 assert!(
1676 notification.get("params").is_none(),
1677 "null params must be omitted, got: {notification}"
1678 );
1679 }
1680
1681 #[tokio::test]
1682 async fn test_notify_with_empty_object_params_keeps_params_key() {
1683 let (client, mut server) = fake_lsp_client();
1684
1685 client
1686 .notify("initialized", lsp_types::InitializedParams {})
1687 .await
1688 .unwrap();
1689
1690 let mut reader = BufReader::new(&mut server.write_stdout);
1691 let notification = read_framed_message(&mut reader).await;
1692
1693 assert_eq!(notification["method"], "initialized");
1694 assert_eq!(
1695 notification["params"],
1696 serde_json::json!({}),
1697 "non-null params must still be sent"
1698 );
1699 }
1700 }
1701
1702 mod retry_behavior {
1703 use tokio::io::{AsyncWriteExt, BufReader, DuplexStream};
1704
1705 use super::*;
1706 use crate::test_lsp::{
1707 CapturedLogs, fake_lsp_client, read_framed_message, write_error_response,
1708 write_response as write_success_response,
1709 };
1710
1711 async fn write_retryable_error_response(
1719 stdin: &mut DuplexStream,
1720 id: &Value,
1721 code: i32,
1722 message: &str,
1723 retrigger: bool,
1724 ) {
1725 let response = serde_json::json!({
1726 "jsonrpc": "2.0",
1727 "id": id,
1728 "error": {
1729 "code": code,
1730 "message": message,
1731 "data": { "retriggerRequest": retrigger },
1732 },
1733 });
1734 let content = serde_json::to_string(&response).unwrap();
1735 let header = format!("Content-Length: {}\r\n\r\n", content.len());
1736 stdin.write_all(header.as_bytes()).await.unwrap();
1737 stdin.write_all(content.as_bytes()).await.unwrap();
1738 stdin.flush().await.unwrap();
1739 }
1740
1741 #[tokio::test]
1751 async fn test_retry_exhaustion_returns_original_server_cancelled_error() {
1752 use tracing_subscriber::layer::SubscriberExt as _;
1753
1754 let (client, mut server) = fake_lsp_client();
1755 let captured = CapturedLogs::default();
1756 let subscriber = tracing_subscriber::registry().with(captured.clone());
1757 let guard = tracing::subscriber::set_default(subscriber);
1758
1759 let request_task = tokio::spawn(async move {
1760 client
1761 .request::<_, Value>(
1762 "textDocument/hover",
1763 serde_json::json!({}),
1764 Duration::from_secs(30),
1765 )
1766 .await
1767 });
1768
1769 let mut reader = BufReader::new(&mut server.write_stdout);
1770 for _ in 0..=SERVER_CANCELLED_MAX_RETRIES {
1774 let request = read_framed_message(&mut reader).await;
1775 let id = request["id"].clone();
1776 write_retryable_error_response(
1777 &mut server.read_half_stdin,
1778 &id,
1779 SERVER_CANCELLED_CODE,
1780 "server cancelled the request",
1781 true,
1782 )
1783 .await;
1784 }
1785
1786 let result = request_task.await.unwrap();
1787
1788 match result {
1789 Err(Error::LspServerError {
1790 code,
1791 message,
1792 data,
1793 }) => {
1794 assert_eq!(code, SERVER_CANCELLED_CODE);
1798 assert_eq!(message, "server cancelled the request");
1799 assert_eq!(data, Some(serde_json::json!({ "retriggerRequest": true })));
1800 }
1801 other => panic!("expected exhausted ServerCancelled error, got {other:?}"),
1802 }
1803
1804 drop(guard);
1805 let logs = captured.entries();
1806 assert_eq!(
1807 logs.iter()
1808 .filter(|(level, _)| *level == tracing::Level::ERROR)
1809 .count(),
1810 1,
1811 "exactly the final exhausted attempt must log at ERROR, got: {logs:?}"
1812 );
1813 assert!(
1814 logs.iter()
1815 .any(|(level, msg)| *level == tracing::Level::ERROR
1816 && msg.contains("LSP error response")
1817 && msg.contains("retries exhausted")),
1818 "expected an ERROR log sharing the 'LSP error response' prefix and naming \
1819 retry exhaustion, got: {logs:?}"
1820 );
1821 assert_eq!(
1822 logs.iter()
1823 .filter(
1824 |(level, msg)| *level == tracing::Level::WARN && msg.contains("will retry")
1825 )
1826 .count(),
1827 usize::try_from(SERVER_CANCELLED_MAX_RETRIES).unwrap(),
1828 "every attempt before the last must log a WARN 'will retry' line, got: {logs:?}"
1829 );
1830 }
1831
1832 #[tokio::test]
1833 async fn test_retrigger_false_returns_immediately_without_retry() {
1834 let (client, mut server) = fake_lsp_client();
1835
1836 let request_task = tokio::spawn(async move {
1837 client
1838 .request::<_, Value>(
1839 "textDocument/hover",
1840 serde_json::json!({}),
1841 Duration::from_secs(30),
1842 )
1843 .await
1844 });
1845
1846 let mut reader = BufReader::new(&mut server.write_stdout);
1847 let request = read_framed_message(&mut reader).await;
1848 let id = request["id"].clone();
1849 write_retryable_error_response(
1850 &mut server.read_half_stdin,
1851 &id,
1852 SERVER_CANCELLED_CODE,
1853 "server cancelled the request",
1854 false,
1855 )
1856 .await;
1857
1858 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
1865 .await
1866 .unwrap()
1867 .unwrap();
1868
1869 match result {
1870 Err(Error::LspServerError { code, .. }) => {
1871 assert_eq!(code, SERVER_CANCELLED_CODE);
1872 }
1873 other => panic!("expected immediate ServerCancelled error, got {other:?}"),
1874 }
1875
1876 let second_request =
1877 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
1878 .await;
1879 assert!(
1880 second_request.is_err(),
1881 "no retry should have been sent after retriggerRequest: false"
1882 );
1883 }
1884
1885 #[tokio::test]
1886 async fn test_retry_succeeds_after_one_server_cancelled_response() {
1887 let (client, mut server) = fake_lsp_client();
1888
1889 let request_task = tokio::spawn(async move {
1890 client
1891 .request::<_, Value>(
1892 "textDocument/hover",
1893 serde_json::json!({}),
1894 Duration::from_secs(30),
1895 )
1896 .await
1897 });
1898
1899 let mut reader = BufReader::new(&mut server.write_stdout);
1900
1901 let first = read_framed_message(&mut reader).await;
1903 write_retryable_error_response(
1904 &mut server.read_half_stdin,
1905 &first["id"].clone(),
1906 SERVER_CANCELLED_CODE,
1907 "server cancelled the request",
1908 true,
1909 )
1910 .await;
1911
1912 let second = read_framed_message(&mut reader).await;
1915 assert_ne!(
1916 first["id"], second["id"],
1917 "retry must use a fresh request id"
1918 );
1919 let expected_result = serde_json::json!({ "contents": "resolved on retry" });
1920 write_success_response(
1921 &mut server.read_half_stdin,
1922 &second["id"].clone(),
1923 expected_result.clone(),
1924 )
1925 .await;
1926
1927 let result = request_task.await.unwrap();
1928 assert_eq!(result.unwrap(), expected_result);
1929 }
1930
1931 #[tokio::test]
1932 async fn test_retry_exhaustion_returns_original_content_modified_error() {
1933 let (client, mut server) = fake_lsp_client();
1934
1935 let request_task = tokio::spawn(async move {
1936 client
1937 .request::<_, Value>(
1938 "textDocument/hover",
1939 serde_json::json!({}),
1940 Duration::from_secs(30),
1941 )
1942 .await
1943 });
1944
1945 let mut reader = BufReader::new(&mut server.write_stdout);
1946 for _ in 0..=SERVER_CANCELLED_MAX_RETRIES {
1951 let request = read_framed_message(&mut reader).await;
1952 let id = request["id"].clone();
1953 write_retryable_error_response(
1954 &mut server.read_half_stdin,
1955 &id,
1956 i32::from(LspErrorCodes::ContentModified),
1957 "content modified",
1958 true,
1959 )
1960 .await;
1961 }
1962
1963 let result = request_task.await.unwrap();
1964
1965 match result {
1966 Err(Error::LspServerError {
1967 code,
1968 message,
1969 data,
1970 }) => {
1971 assert_eq!(code, i32::from(LspErrorCodes::ContentModified));
1975 assert_eq!(message, "content modified");
1976 assert_eq!(data, Some(serde_json::json!({ "retriggerRequest": true })));
1977 }
1978 other => panic!("expected exhausted ContentModified error, got {other:?}"),
1979 }
1980 }
1981
1982 #[tokio::test]
1983 async fn test_retrigger_false_returns_immediately_without_retry_for_content_modified() {
1984 let (client, mut server) = fake_lsp_client();
1985
1986 let request_task = tokio::spawn(async move {
1987 client
1988 .request::<_, Value>(
1989 "textDocument/hover",
1990 serde_json::json!({}),
1991 Duration::from_secs(30),
1992 )
1993 .await
1994 });
1995
1996 let mut reader = BufReader::new(&mut server.write_stdout);
1997 let request = read_framed_message(&mut reader).await;
1998 let id = request["id"].clone();
1999 write_retryable_error_response(
2000 &mut server.read_half_stdin,
2001 &id,
2002 i32::from(LspErrorCodes::ContentModified),
2003 "content modified",
2004 false,
2005 )
2006 .await;
2007
2008 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
2013 .await
2014 .unwrap()
2015 .unwrap();
2016
2017 match result {
2018 Err(Error::LspServerError { code, .. }) => {
2019 assert_eq!(code, i32::from(LspErrorCodes::ContentModified));
2020 }
2021 other => panic!("expected immediate ContentModified error, got {other:?}"),
2022 }
2023
2024 let second_request =
2025 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
2026 .await;
2027 assert!(
2028 second_request.is_err(),
2029 "no retry should have been sent after retriggerRequest: false"
2030 );
2031 }
2032
2033 #[tokio::test]
2034 async fn test_retry_succeeds_after_one_content_modified_response() {
2035 let (client, mut server) = fake_lsp_client();
2036
2037 let request_task = tokio::spawn(async move {
2038 client
2039 .request::<_, Value>(
2040 "textDocument/hover",
2041 serde_json::json!({}),
2042 Duration::from_secs(30),
2043 )
2044 .await
2045 });
2046
2047 let mut reader = BufReader::new(&mut server.write_stdout);
2048
2049 let first = read_framed_message(&mut reader).await;
2051 write_retryable_error_response(
2052 &mut server.read_half_stdin,
2053 &first["id"].clone(),
2054 i32::from(LspErrorCodes::ContentModified),
2055 "content modified",
2056 true,
2057 )
2058 .await;
2059
2060 let second = read_framed_message(&mut reader).await;
2063 assert_ne!(
2064 first["id"], second["id"],
2065 "retry must use a fresh request id"
2066 );
2067 let expected_result = serde_json::json!({ "contents": "resolved on retry" });
2068 write_success_response(
2069 &mut server.read_half_stdin,
2070 &second["id"].clone(),
2071 expected_result.clone(),
2072 )
2073 .await;
2074
2075 let result = request_task.await.unwrap();
2076 assert_eq!(result.unwrap(), expected_result);
2077 }
2078
2079 #[tokio::test]
2080 async fn test_content_modified_on_non_allowlisted_method_does_not_retry() {
2081 let (client, mut server) = fake_lsp_client();
2082
2083 let request_task = tokio::spawn(async move {
2090 client
2091 .request::<_, Value>(
2092 "textDocument/rename",
2093 serde_json::json!({}),
2094 Duration::from_secs(30),
2095 )
2096 .await
2097 });
2098
2099 let mut reader = BufReader::new(&mut server.write_stdout);
2100 let request = read_framed_message(&mut reader).await;
2101 let id = request["id"].clone();
2102 write_retryable_error_response(
2103 &mut server.read_half_stdin,
2104 &id,
2105 i32::from(LspErrorCodes::ContentModified),
2106 "content modified",
2107 true,
2108 )
2109 .await;
2110
2111 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
2112 .await
2113 .unwrap()
2114 .unwrap();
2115
2116 match result {
2117 Err(Error::LspServerError { code, .. }) => {
2118 assert_eq!(code, i32::from(LspErrorCodes::ContentModified));
2119 }
2120 other => panic!("expected immediate ContentModified error, got {other:?}"),
2121 }
2122
2123 let second_request =
2124 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
2125 .await;
2126 assert!(
2127 second_request.is_err(),
2128 "no retry should have been sent for a non-allowlisted method"
2129 );
2130 }
2131
2132 #[tokio::test]
2139 async fn test_oversized_error_message_truncated_for_caller() {
2140 let (client, mut server) = fake_lsp_client();
2141
2142 let request_task = tokio::spawn(async move {
2143 client
2144 .request::<_, Value>(
2145 "textDocument/hover",
2146 serde_json::json!({}),
2147 Duration::from_secs(30),
2148 )
2149 .await
2150 });
2151
2152 let mut reader = BufReader::new(&mut server.write_stdout);
2153 let request = read_framed_message(&mut reader).await;
2154 let id = request["id"].clone();
2155 let oversized_message = "x".repeat(MAX_ERROR_MESSAGE_CALLER_BYTES + 500);
2156 write_error_response(&mut server.read_half_stdin, &id, -32603, &oversized_message)
2157 .await;
2158
2159 let result = request_task.await.unwrap();
2160
2161 match result {
2162 Err(Error::LspServerError { code, message, .. }) => {
2163 assert_eq!(code, -32603);
2164 assert!(
2165 message.len() < oversized_message.len(),
2166 "caller-facing message must be truncated, got {} bytes",
2167 message.len()
2168 );
2169 assert!(message.ends_with("... (truncated)"));
2170 }
2171 other => panic!("expected truncated LspServerError, got {other:?}"),
2172 }
2173 }
2174
2175 #[tokio::test]
2181 async fn test_error_message_between_log_and_caller_caps_reaches_caller_intact() {
2182 let (client, mut server) = fake_lsp_client();
2183
2184 let request_task = tokio::spawn(async move {
2185 client
2186 .request::<_, Value>(
2187 "textDocument/hover",
2188 serde_json::json!({}),
2189 Duration::from_secs(30),
2190 )
2191 .await
2192 });
2193
2194 let mut reader = BufReader::new(&mut server.write_stdout);
2195 let request = read_framed_message(&mut reader).await;
2196 let id = request["id"].clone();
2197 let message = "x".repeat(crate::util::MAX_LOG_STRING_BYTES + 50);
2198 write_error_response(&mut server.read_half_stdin, &id, -32603, &message).await;
2199
2200 let result = request_task.await.unwrap();
2201
2202 match result {
2203 Err(Error::LspServerError {
2204 message: returned, ..
2205 }) => {
2206 assert_eq!(
2207 returned, message,
2208 "message under the caller cap must not be truncated"
2209 );
2210 }
2211 other => panic!("expected untruncated LspServerError, got {other:?}"),
2212 }
2213 }
2214
2215 #[tokio::test]
2220 async fn test_retried_error_that_recovers_does_not_log_error_level() {
2221 use tracing_subscriber::layer::SubscriberExt as _;
2222
2223 let (client, mut server) = fake_lsp_client();
2224 let captured = CapturedLogs::default();
2225 let subscriber = tracing_subscriber::registry().with(captured.clone());
2226 let guard = tracing::subscriber::set_default(subscriber);
2227
2228 let request_task = tokio::spawn(async move {
2229 client
2230 .request::<_, Value>(
2231 "textDocument/hover",
2232 serde_json::json!({}),
2233 Duration::from_secs(30),
2234 )
2235 .await
2236 });
2237
2238 let mut reader = BufReader::new(&mut server.write_stdout);
2239
2240 let first = read_framed_message(&mut reader).await;
2241 write_retryable_error_response(
2242 &mut server.read_half_stdin,
2243 &first["id"].clone(),
2244 SERVER_CANCELLED_CODE,
2245 "server cancelled the request",
2246 true,
2247 )
2248 .await;
2249
2250 let second = read_framed_message(&mut reader).await;
2251 write_success_response(
2252 &mut server.read_half_stdin,
2253 &second["id"].clone(),
2254 serde_json::json!({ "contents": "resolved on retry" }),
2255 )
2256 .await;
2257
2258 let result = request_task.await.unwrap();
2259 assert!(result.is_ok(), "expected retry to recover, got {result:?}");
2260
2261 drop(guard);
2262 let logs = captured.entries();
2263 assert!(
2264 !logs
2265 .iter()
2266 .any(|(level, _)| *level == tracing::Level::ERROR),
2267 "a retried-and-recovered error must not log at ERROR, got: {logs:?}"
2268 );
2269 assert!(
2270 logs.iter().any(
2271 |(level, msg)| *level == tracing::Level::WARN && msg.contains("will retry")
2272 ),
2273 "expected a WARN 'will retry' log line, got: {logs:?}"
2274 );
2275 }
2276
2277 #[tokio::test]
2281 async fn test_non_retryable_error_logs_error_level() {
2282 use tracing_subscriber::layer::SubscriberExt as _;
2283
2284 let (client, mut server) = fake_lsp_client();
2285 let captured = CapturedLogs::default();
2286 let subscriber = tracing_subscriber::registry().with(captured.clone());
2287 let guard = tracing::subscriber::set_default(subscriber);
2288
2289 let request_task = tokio::spawn(async move {
2290 client
2291 .request::<_, Value>(
2292 "textDocument/rename",
2293 serde_json::json!({}),
2294 Duration::from_secs(30),
2295 )
2296 .await
2297 });
2298
2299 let mut reader = BufReader::new(&mut server.write_stdout);
2300 let request = read_framed_message(&mut reader).await;
2301 let id = request["id"].clone();
2302 write_retryable_error_response(
2303 &mut server.read_half_stdin,
2304 &id,
2305 i32::from(LspErrorCodes::ContentModified),
2306 "content modified",
2307 true,
2308 )
2309 .await;
2310
2311 let result = request_task.await.unwrap();
2312 assert!(result.is_err(), "expected a non-retryable error");
2313
2314 drop(guard);
2315 let logs = captured.entries();
2316 assert!(
2317 logs.iter()
2318 .any(|(level, msg)| *level == tracing::Level::ERROR
2319 && msg.contains("LSP error response")
2320 && msg.contains("content modified")),
2321 "a non-retryable error must still surface an ERROR log sharing the \
2322 'LSP error response' prefix, got: {logs:?}"
2323 );
2324 }
2325 }
2326
2327 mod reader_task_regression {
2331 use tokio::io::BufReader;
2332
2333 use super::*;
2334 use crate::test_lsp::{
2335 fake_lsp_client, inert_transport, read_framed_message, write_response,
2336 };
2337
2338 #[tokio::test]
2345 async fn test_message_loop_inner_treats_reader_channel_close_as_server_terminated() {
2346 let (mut transport, _reader) = inert_transport();
2347 let (_command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
2348 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
2349 let pending_requests: Arc<Mutex<PendingRequests>> =
2350 Arc::new(Mutex::new(HashMap::new()));
2351
2352 drop(msg_tx);
2353
2354 let result = LspClient::message_loop_inner(
2355 &mut transport,
2356 &mut msg_rx,
2357 &mut command_rx,
2358 &pending_requests,
2359 None,
2360 None,
2361 )
2362 .await;
2363
2364 assert!(
2365 matches!(result, Err(Error::ServerTerminated)),
2366 "got {result:?}"
2367 );
2368 }
2369
2370 #[tokio::test]
2382 async fn test_message_loop_inner_reader_gone_then_drain_fails_pending_request() {
2383 let (mut transport, _reader) = inert_transport();
2384 let (_command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
2385 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
2386 let pending_requests: Arc<Mutex<PendingRequests>> =
2387 Arc::new(Mutex::new(HashMap::new()));
2388
2389 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
2390 pending_requests
2391 .lock()
2392 .await
2393 .insert(RequestId::Number(1), response_tx);
2394
2395 drop(msg_tx);
2396
2397 let result = LspClient::message_loop_inner(
2398 &mut transport,
2399 &mut msg_rx,
2400 &mut command_rx,
2401 &pending_requests,
2402 None,
2403 None,
2404 )
2405 .await;
2406 assert!(
2407 matches!(result, Err(Error::ServerTerminated)),
2408 "got {result:?}"
2409 );
2410
2411 LspClient::drain_and_fail_pending(&pending_requests).await;
2412
2413 let received = response_rx.await.unwrap();
2414 assert!(
2415 matches!(received, Err(Error::ServerTerminated)),
2416 "got {received:?}"
2417 );
2418 assert!(pending_requests.lock().await.is_empty());
2419 }
2420
2421 #[tokio::test]
2429 async fn test_shutdown_drains_buffered_responses_instead_of_dropping_them() {
2430 let (mut transport, _reader) = inert_transport();
2431 let (command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
2432 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
2433 let pending_requests: Arc<Mutex<PendingRequests>> =
2434 Arc::new(Mutex::new(HashMap::new()));
2435
2436 let id = RequestId::Number(1);
2437 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
2438 pending_requests
2439 .lock()
2440 .await
2441 .insert(id.clone(), response_tx);
2442
2443 msg_tx
2444 .send(Ok(InboundMessage::Response(JsonRpcResponse {
2445 jsonrpc: "2.0".to_string(),
2446 id: id.clone(),
2447 result: Some(serde_json::json!({ "ok": true })),
2448 error: None,
2449 })))
2450 .await
2451 .unwrap();
2452 command_tx.send(ClientCommand::Shutdown).await.unwrap();
2453 drop(command_tx);
2454
2455 let result = LspClient::message_loop_inner(
2456 &mut transport,
2457 &mut msg_rx,
2458 &mut command_rx,
2459 &pending_requests,
2460 None,
2461 None,
2462 )
2463 .await;
2464
2465 assert!(result.is_ok(), "got {result:?}");
2466 let received = response_rx.await.unwrap();
2469 assert_eq!(received.unwrap(), serde_json::json!({ "ok": true }));
2470 assert!(
2471 pending_requests.lock().await.is_empty(),
2472 "the drained response must resolve its pending request entry"
2473 );
2474 }
2475
2476 #[tokio::test]
2484 async fn test_message_loop_fails_pending_requests_on_transport_error_exit() {
2485 let (transport, reader) = inert_transport();
2486 let (_command_tx, command_rx) = mpsc::channel::<ClientCommand>(1);
2487 let pending_requests: Arc<Mutex<PendingRequests>> =
2488 Arc::new(Mutex::new(HashMap::new()));
2489
2490 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
2491 pending_requests
2492 .lock()
2493 .await
2494 .insert(RequestId::Number(1), response_tx);
2495
2496 let result = LspClient::message_loop(
2497 (transport, reader),
2498 command_rx,
2499 Arc::clone(&pending_requests),
2500 None,
2501 None,
2502 )
2503 .await;
2504
2505 assert!(result.is_err(), "got {result:?}");
2506 let received = response_rx.await.unwrap();
2507 assert!(
2508 matches!(received, Err(Error::ServerTerminated)),
2509 "got {received:?}"
2510 );
2511 assert!(pending_requests.lock().await.is_empty());
2512 }
2513
2514 #[tokio::test]
2524 async fn test_message_loop_shutdown_resolves_answered_and_fails_unanswered_pending() {
2525 let (mut transport, _reader) = inert_transport();
2526 let (command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(1);
2527 let (msg_tx, mut msg_rx) = mpsc::channel::<Result<InboundMessage>>(1);
2528 let pending_requests: Arc<Mutex<PendingRequests>> =
2529 Arc::new(Mutex::new(HashMap::new()));
2530
2531 let answered_id = RequestId::Number(1);
2532 let (answered_tx, answered_rx) = oneshot::channel::<Result<Value>>();
2533 let unanswered_id = RequestId::Number(2);
2534 let (unanswered_tx, unanswered_rx) = oneshot::channel::<Result<Value>>();
2535 pending_requests
2536 .lock()
2537 .await
2538 .insert(answered_id.clone(), answered_tx);
2539 pending_requests
2540 .lock()
2541 .await
2542 .insert(unanswered_id.clone(), unanswered_tx);
2543
2544 msg_tx
2545 .send(Ok(InboundMessage::Response(JsonRpcResponse {
2546 jsonrpc: "2.0".to_string(),
2547 id: answered_id,
2548 result: Some(serde_json::json!({ "ok": true })),
2549 error: None,
2550 })))
2551 .await
2552 .unwrap();
2553 command_tx.send(ClientCommand::Shutdown).await.unwrap();
2554 drop(command_tx);
2555
2556 let inner_result = LspClient::message_loop_inner(
2557 &mut transport,
2558 &mut msg_rx,
2559 &mut command_rx,
2560 &pending_requests,
2561 None,
2562 None,
2563 )
2564 .await;
2565 assert!(inner_result.is_ok(), "got {inner_result:?}");
2566
2567 LspClient::drain_and_fail_pending(&pending_requests).await;
2570
2571 let answered = answered_rx.await.unwrap();
2572 assert_eq!(answered.unwrap(), serde_json::json!({ "ok": true }));
2573
2574 let unanswered = unanswered_rx.await.unwrap();
2575 assert!(
2576 matches!(unanswered, Err(Error::ServerTerminated)),
2577 "got {unanswered:?}"
2578 );
2579 assert!(pending_requests.lock().await.is_empty());
2580 }
2581
2582 #[tokio::test]
2592 async fn test_message_loop_end_to_end_resolves_answered_then_fails_unanswered_on_shutdown()
2593 {
2594 let (client, mut server) = fake_lsp_client();
2595 let mut reader = BufReader::new(&mut server.write_stdout);
2596
2597 let answered_client = client.clone();
2598 let answered_task = tokio::spawn(async move {
2599 answered_client
2600 .request::<_, Value>(
2601 "textDocument/hover",
2602 serde_json::json!({}),
2603 Duration::from_secs(30),
2604 )
2605 .await
2606 });
2607
2608 let request = read_framed_message(&mut reader).await;
2609 let id = request["id"].clone();
2610 write_response(
2611 &mut server.read_half_stdin,
2612 &id,
2613 serde_json::json!({ "ok": true }),
2614 )
2615 .await;
2616
2617 let answered = answered_task.await.unwrap().unwrap();
2618 assert_eq!(answered, serde_json::json!({ "ok": true }));
2619
2620 let unanswered_client = client.clone();
2621 let unanswered_task = tokio::spawn(async move {
2622 unanswered_client
2623 .request::<_, Value>(
2624 "textDocument/definition",
2625 serde_json::json!({}),
2626 Duration::from_secs(30),
2627 )
2628 .await
2629 });
2630
2631 let _unanswered_request = read_framed_message(&mut reader).await;
2635
2636 client.shutdown().await.unwrap();
2637
2638 let unanswered = tokio::time::timeout(Duration::from_millis(200), unanswered_task)
2639 .await
2640 .unwrap_or_else(|_| {
2641 panic!(
2642 "unanswered request must fail immediately on shutdown, not hang until its own timeout"
2643 )
2644 })
2645 .unwrap();
2646 assert!(
2647 matches!(unanswered, Err(Error::ServerTerminated)),
2648 "got {unanswered:?}"
2649 );
2650 }
2651
2652 #[tokio::test]
2659 async fn test_dense_concurrent_requests_all_resolve_to_matching_responses() {
2660 const REQUEST_COUNT: usize = 20;
2661 let (client, mut server) = fake_lsp_client();
2662
2663 let mut request_tasks = Vec::with_capacity(REQUEST_COUNT);
2670 for i in 0..REQUEST_COUNT {
2671 let client = client.clone();
2672 request_tasks.push(tokio::spawn(async move {
2673 client
2674 .request::<_, Value>(
2675 "textDocument/hover",
2676 serde_json::json!({ "n": i }),
2677 Duration::from_secs(30),
2678 )
2679 .await
2680 }));
2681 }
2682
2683 let server_task = tokio::spawn(async move {
2684 let mut reader = BufReader::new(&mut server.write_stdout);
2685 let mut requests = Vec::with_capacity(REQUEST_COUNT);
2686 for _ in 0..REQUEST_COUNT {
2687 requests.push(read_framed_message(&mut reader).await);
2688 }
2689 for request in requests.into_iter().rev() {
2690 let id = request["id"].clone();
2691 let n = request["params"]["n"].clone();
2692 write_response(
2693 &mut server.read_half_stdin,
2694 &id,
2695 serde_json::json!({ "echo": n }),
2696 )
2697 .await;
2698 }
2699 });
2700
2701 server_task.await.unwrap();
2702
2703 for (i, task) in request_tasks.into_iter().enumerate() {
2704 let value = task
2705 .await
2706 .unwrap()
2707 .unwrap_or_else(|e| panic!("request {i} failed: {e:?}"));
2708 assert_eq!(
2709 value["echo"],
2710 serde_json::json!(i),
2711 "response for request {i} carried the wrong payload -- id/response mismatch"
2712 );
2713 }
2714 }
2715
2716 #[tokio::test]
2723 async fn test_request_after_shutdown_fails_fast_instead_of_hanging() {
2724 let (client, _server) = fake_lsp_client();
2725 let post_shutdown_client = client.clone();
2726
2727 client.shutdown().await.unwrap();
2728
2729 let result = tokio::time::timeout(
2730 Duration::from_millis(200),
2731 post_shutdown_client.request::<_, Value>(
2732 "textDocument/hover",
2733 serde_json::json!({}),
2734 Duration::from_secs(30),
2735 ),
2736 )
2737 .await
2738 .unwrap_or_else(|_| {
2739 panic!(
2740 "request after shutdown must fail immediately, not hang until its own timeout"
2741 )
2742 });
2743
2744 assert!(
2745 matches!(result, Err(Error::ServerTerminated)),
2746 "got {result:?}"
2747 );
2748 }
2749 }
2750}