1use std::collections::HashMap;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicI64, Ordering};
6
7use serde::Serialize;
8use serde::de::DeserializeOwned;
9use serde_json::Value;
10use tokio::sync::{Mutex, mpsc, oneshot};
11use tokio::task::JoinHandle;
12use tokio::time::{Duration, timeout};
13use tracing::{debug, error, trace, warn};
14
15use crate::config::LspServerConfig;
16use crate::error::{Error, Result};
17use crate::lsp::transport::LspTransport;
18use crate::lsp::types::{
19 InboundMessage, JsonRpcError, JsonRpcRequest, JsonRpcResponse, LspNotification, RequestId,
20};
21
22const JSONRPC_VERSION: &str = "2.0";
24
25const SERVER_CANCELLED_CODE: i32 = -32802;
27
28const SERVER_CANCELLED_MAX_RETRIES: u32 = 3;
30
31const SERVER_CANCELLED_INITIAL_DELAY_MS: u64 = 500;
33
34const MAX_ERROR_MESSAGE_LOG_BYTES: usize = 200;
39
40const MAX_ERROR_MESSAGE_CALLER_BYTES: usize = 4 * 1024;
52
53const COMPLETION_TIMEOUT_CAP: Duration = Duration::from_secs(10);
61
62type PendingRequests = HashMap<RequestId, oneshot::Sender<Result<Value>>>;
64
65#[derive(Debug)]
73pub struct LspClient {
74 config: LspServerConfig,
76
77 state: Arc<Mutex<super::ServerState>>,
79
80 request_counter: Arc<AtomicI64>,
82
83 command_tx: mpsc::Sender<ClientCommand>,
85
86 pending_requests: Arc<Mutex<PendingRequests>>,
94
95 receiver_task: Option<JoinHandle<Result<()>>>,
97}
98
99impl Clone for LspClient {
100 fn clone(&self) -> Self {
105 Self {
106 config: self.config.clone(),
107 state: Arc::clone(&self.state),
108 request_counter: Arc::clone(&self.request_counter),
109 command_tx: self.command_tx.clone(),
110 pending_requests: Arc::clone(&self.pending_requests),
111 receiver_task: None,
112 }
113 }
114}
115
116enum ClientCommand {
118 SendRequest {
120 request: JsonRpcRequest,
121 response_tx: oneshot::Sender<Result<Value>>,
122 },
123 SendNotification {
125 method: String,
126 params: Option<Value>,
127 },
128 Shutdown,
130}
131
132impl LspClient {
133 #[must_use]
138 pub fn new(config: LspServerConfig) -> Self {
139 let (command_tx, _command_rx) = mpsc::channel(1); Self {
145 config,
146 state: Arc::new(Mutex::new(super::ServerState::Uninitialized)),
147 request_counter: Arc::new(AtomicI64::new(1)),
148 command_tx,
149 pending_requests: Arc::new(Mutex::new(HashMap::new())),
150 receiver_task: None,
151 }
152 }
153
154 #[cfg(test)]
158 pub(crate) fn from_transport(config: LspServerConfig, transport: LspTransport) -> Self {
159 let state = Arc::new(Mutex::new(super::ServerState::Initializing));
160 let request_counter = Arc::new(AtomicI64::new(1));
161 let pending_requests = Arc::new(Mutex::new(HashMap::new()));
162
163 let (command_tx, command_rx) = mpsc::channel(100);
164
165 let receiver_task = tokio::spawn(Self::message_loop(
166 transport,
167 command_rx,
168 Arc::clone(&pending_requests),
169 None,
170 ));
171
172 Self {
173 config,
174 state,
175 request_counter,
176 command_tx,
177 pending_requests,
178 receiver_task: Some(receiver_task),
179 }
180 }
181
182 pub(crate) fn from_transport_with_notifications(
187 config: LspServerConfig,
188 transport: LspTransport,
189 notification_tx: mpsc::Sender<LspNotification>,
190 ) -> Self {
191 let state = Arc::new(Mutex::new(super::ServerState::Initializing));
192 let request_counter = Arc::new(AtomicI64::new(1));
193 let pending_requests = Arc::new(Mutex::new(HashMap::new()));
194
195 let (command_tx, command_rx) = mpsc::channel(100);
196
197 let receiver_task = tokio::spawn(Self::message_loop(
198 transport,
199 command_rx,
200 Arc::clone(&pending_requests),
201 Some(notification_tx),
202 ));
203
204 Self {
205 config,
206 state,
207 request_counter,
208 command_tx,
209 pending_requests,
210 receiver_task: Some(receiver_task),
211 }
212 }
213
214 #[must_use]
216 pub fn language_id(&self) -> &str {
217 &self.config.language_id
218 }
219
220 pub async fn state(&self) -> super::ServerState {
222 *self.state.lock().await
223 }
224
225 #[must_use]
269 pub fn request_timeout(&self) -> Duration {
270 Duration::from_secs(
271 self.config
272 .request_timeout_seconds
273 .clamp(1, crate::config::MAX_TIMEOUT_SECONDS),
274 )
275 }
276
277 #[must_use]
301 pub fn completion_timeout(&self) -> Duration {
302 self.request_timeout().min(COMPLETION_TIMEOUT_CAP)
303 }
304
305 pub async fn request<P, R>(
324 &self,
325 method: &str,
326 params: P,
327 timeout_duration: Duration,
328 ) -> Result<R>
329 where
330 P: Serialize,
331 R: DeserializeOwned,
332 {
333 let params_value = serde_json::to_value(params)?;
334 let mut delay_ms = SERVER_CANCELLED_INITIAL_DELAY_MS;
335
336 for attempt in 0..=SERVER_CANCELLED_MAX_RETRIES {
337 if attempt > 0 {
338 debug!(
339 "Retrying {} after ServerCancelled (attempt {}/{}), backoff={}ms",
340 method, attempt, SERVER_CANCELLED_MAX_RETRIES, delay_ms
341 );
342 tokio::time::sleep(Duration::from_millis(delay_ms)).await;
343 delay_ms *= 2;
344 }
345
346 let id = RequestId::Number(self.request_counter.fetch_add(1, Ordering::SeqCst));
347 let (response_tx, response_rx) = oneshot::channel();
348 let request = JsonRpcRequest {
349 jsonrpc: JSONRPC_VERSION.to_string(),
350 id: id.clone(),
351 method: method.to_string(),
352 params: Some(params_value.clone()),
353 };
354
355 debug!("Sending request: {} (id={:?})", method, id);
356
357 self.command_tx
358 .send(ClientCommand::SendRequest {
359 request,
360 response_tx,
361 })
362 .await
363 .map_err(|_| Error::ServerTerminated)?;
364
365 let outcome = match timeout(timeout_duration, response_rx).await {
366 Ok(received) => received.map_err(|_| Error::ServerTerminated)?,
367 Err(_elapsed) => {
368 self.pending_requests.lock().await.remove(&id);
373 return Err(Error::Timeout(timeout_duration.as_secs()));
374 }
375 };
376
377 match outcome {
378 Ok(result_value) => {
379 return serde_json::from_value(result_value).map_err(|e| {
380 Error::LspProtocolError(format!("Failed to deserialize response: {e}"))
381 });
382 }
383 Err(Error::LspServerError {
384 code,
385 ref message,
386 ref data,
387 }) if code == SERVER_CANCELLED_CODE && Self::should_retrigger(data.as_ref()) => {
388 warn!(
389 "ServerCancelled (-32802) on '{}', will retry: {}",
390 method, message
391 );
392 if attempt == SERVER_CANCELLED_MAX_RETRIES {
393 return Err(Error::LspServerError {
394 code,
395 message: message.clone(),
396 data: data.clone(),
397 });
398 }
399 }
401 Err(e) => return Err(e),
402 }
403 }
404
405 Err(Error::ServerTerminated)
406 }
407
408 fn should_retrigger(data: Option<&Value>) -> bool {
415 data.is_none_or(|v| {
416 v.get("retriggerRequest")
417 .and_then(Value::as_bool)
418 .unwrap_or(true)
419 })
420 }
421
422 pub(crate) async fn fail_pending_requests(&self) {
430 let mut pending = self.pending_requests.lock().await;
431 for (_, sender) in pending.drain() {
432 let _ = sender.send(Err(Error::ServerTerminated));
433 }
434 }
435
436 pub async fn notify<P>(&self, method: &str, params: P) -> Result<()>
442 where
443 P: Serialize,
444 {
445 let params_value = serde_json::to_value(params)?;
446
447 debug!("Sending notification: {}", method);
448
449 self.command_tx
450 .send(ClientCommand::SendNotification {
451 method: method.to_string(),
452 params: Some(params_value),
453 })
454 .await
455 .map_err(|_| Error::ServerTerminated)?;
456
457 Ok(())
458 }
459
460 pub async fn shutdown(mut self) -> Result<()> {
468 debug!("Shutting down LSP client");
469
470 let _ = self.command_tx.send(ClientCommand::Shutdown).await;
471
472 if let Some(task) = self.receiver_task.take() {
473 task.await
474 .map_err(|e| Error::Transport(format!("Receiver task failed: {e}")))??;
475 }
476
477 *self.state.lock().await = super::ServerState::Shutdown;
478
479 Ok(())
480 }
481
482 async fn message_loop(
489 mut transport: LspTransport,
490 mut command_rx: mpsc::Receiver<ClientCommand>,
491 pending_requests: Arc<Mutex<PendingRequests>>,
492 notification_tx: Option<mpsc::Sender<LspNotification>>,
493 ) -> Result<()> {
494 debug!("Message loop started");
495 let result = Self::message_loop_inner(
496 &mut transport,
497 &mut command_rx,
498 &pending_requests,
499 notification_tx.as_ref(),
500 )
501 .await;
502 if let Err(ref e) = result {
503 error!("Message loop exiting with error: {}", e);
504 } else {
505 debug!("Message loop exiting normally");
506 }
507 result
508 }
509
510 fn truncate_error_message_for_log(message: &str) -> String {
518 crate::util::truncate_str(message, MAX_ERROR_MESSAGE_LOG_BYTES)
519 }
520
521 async fn message_loop_inner(
522 transport: &mut LspTransport,
523 command_rx: &mut mpsc::Receiver<ClientCommand>,
524 pending_requests: &Arc<Mutex<PendingRequests>>,
525 notification_tx: Option<&mpsc::Sender<LspNotification>>,
526 ) -> Result<()> {
527 loop {
528 tokio::select! {
529 Some(command) = command_rx.recv() => {
530 match command {
531 ClientCommand::SendRequest { request, response_tx } => {
532 pending_requests.lock().await.insert(
533 request.id.clone(),
534 response_tx,
535 );
536
537 let value = serde_json::to_value(&request)?;
538 transport.send(&value).await?;
539 }
540 ClientCommand::SendNotification { method, params } => {
541 let notification = serde_json::json!({
542 "jsonrpc": "2.0",
543 "method": method,
544 "params": params,
545 });
546 transport.send(¬ification).await?;
547 }
548 ClientCommand::Shutdown => {
549 debug!("Client shutdown requested");
550 break;
551 }
552 }
553 }
554
555 message = transport.receive() => {
556 let message = match message {
557 Ok(m) => m,
558 Err(e) => {
559 error!("Transport receive error: {}", e);
560 return Err(e);
561 }
562 };
563 match message {
564 InboundMessage::Response(response) => {
565 trace!("Received response: id={:?}", response.id);
566
567 let sender = pending_requests.lock().await.remove(&response.id);
568
569 if let Some(sender) = sender {
570 if let Some(error) = response.error {
571 let log_message = Self::truncate_error_message_for_log(&error.message);
572 error!("LSP error response: {} (code {})", log_message, error.code);
573 let caller_message = crate::util::truncate_str(
579 &error.message,
580 MAX_ERROR_MESSAGE_CALLER_BYTES,
581 );
582 let _ = sender.send(Err(Error::LspServerError {
583 code: error.code,
584 message: caller_message,
585 data: error.data,
586 }));
587 } else if let Some(result) = response.result {
588 let _ = sender.send(Ok(result));
589 } else {
590 trace!("Response with null result: {:?}", response.id);
593 let _ = sender.send(Ok(Value::Null));
594 }
595 } else {
596 warn!("Received response for unknown request ID: {:?}", response.id);
597 }
598 }
599 InboundMessage::Request(request) => {
600 debug!(
601 "Received server request: {} (id={:?})",
602 request.method, request.id
603 );
604 let response = Self::server_request_response(request);
605 let value = serde_json::to_value(&response)?;
606 transport.send(&value).await?;
607 }
608 InboundMessage::Notification(notification) => {
609 debug!("Received notification: {}", notification.method);
610
611 let typed = LspNotification::parse(¬ification.method, notification.params);
613
614 if let Some(tx) = notification_tx {
616 if let LspNotification::PublishDiagnostics(ref params) = typed {
618 debug!(
619 "Forwarding diagnostics for {}: {} items",
620 params.uri.as_str(),
621 params.diagnostics.len()
622 );
623 } else {
624 trace!("Forwarding notification: {:?}", typed);
625 }
626
627 if tx.try_send(typed).is_err() {
629 warn!("Notification channel full or closed, dropping notification");
630 }
631 }
632 }
633 }
634 }
635 }
636 }
637
638 Ok(())
639 }
640
641 fn server_request_response(request: JsonRpcRequest) -> JsonRpcResponse {
642 match Self::server_request_result(&request.method, request.params.as_ref()) {
643 Ok(result) => JsonRpcResponse {
644 jsonrpc: JSONRPC_VERSION.to_string(),
645 id: request.id,
646 result: Some(result),
647 error: None,
648 },
649 Err(error) => JsonRpcResponse {
650 jsonrpc: JSONRPC_VERSION.to_string(),
651 id: request.id,
652 result: None,
653 error: Some(error),
654 },
655 }
656 }
657
658 fn server_request_result(
659 method: &str,
660 params: Option<&Value>,
661 ) -> std::result::Result<Value, JsonRpcError> {
662 match method {
663 "client/registerCapability"
664 | "client/unregisterCapability"
665 | "workspace/workspaceFolders"
666 | "workspace/diagnostic/refresh"
667 | "workspace/semanticTokens/refresh"
668 | "workspace/inlayHint/refresh"
669 | "workspace/codeLens/refresh"
670 | "window/showMessageRequest" => Ok(Value::Null),
671 "workspace/configuration" => Ok(Self::workspace_configuration_result(params)),
672 "workspace/applyEdit" => Ok(serde_json::json!({ "applied": false })),
673 _ => Err(JsonRpcError {
674 code: -32601,
675 message: format!("Unhandled server request: {method}"),
676 data: None,
677 }),
678 }
679 }
680
681 fn workspace_configuration_result(params: Option<&Value>) -> Value {
682 let item_count = params
683 .and_then(|value| value.get("items"))
684 .and_then(Value::as_array)
685 .map_or(0, Vec::len);
686
687 Value::Array(vec![Value::Null; item_count])
688 }
689}
690
691#[cfg(test)]
692#[allow(clippy::unwrap_used)]
693mod tests {
694 use super::*;
695
696 #[test]
697 fn test_request_id_generation() {
698 let counter = AtomicI64::new(1);
699
700 let id1 = counter.fetch_add(1, Ordering::SeqCst);
701 let id2 = counter.fetch_add(1, Ordering::SeqCst);
702 let id3 = counter.fetch_add(1, Ordering::SeqCst);
703
704 assert_eq!(id1, 1);
705 assert_eq!(id2, 2);
706 assert_eq!(id3, 3);
707 }
708
709 #[test]
710 fn test_client_creation() {
711 let config = LspServerConfig::rust_analyzer();
712
713 let client = LspClient::new(config);
714 assert_eq!(client.language_id(), "rust");
715 }
716
717 #[test]
718 fn test_client_clone() {
719 let config = LspServerConfig::rust_analyzer();
720 let client = LspClient::new(config);
721
722 #[allow(clippy::redundant_clone)]
723 let cloned = client.clone();
724 assert_eq!(cloned.language_id(), "rust");
725
726 assert!(
727 cloned.receiver_task.is_none(),
728 "Cloned client should not own receiver task"
729 );
730 }
731
732 #[test]
733 fn test_request_timeout_and_completion_timeout_at_default() {
734 let config = LspServerConfig::rust_analyzer();
735 let client = LspClient::new(config);
736
737 assert_eq!(client.request_timeout(), Duration::from_secs(30));
738 assert_eq!(client.completion_timeout(), Duration::from_secs(10));
739 }
740
741 #[test]
742 fn test_completion_timeout_clamps_to_ten_seconds() {
743 for secs in [1, 2, 3, 30, 300] {
744 let mut config = LspServerConfig::rust_analyzer();
745 config.request_timeout_seconds = secs;
746 let client = LspClient::new(config);
747
748 assert_eq!(
749 client.completion_timeout(),
750 Duration::from_secs(secs.min(10)),
751 "request_timeout_seconds={secs}"
752 );
753 assert!(client.completion_timeout() <= client.request_timeout());
754 }
755 }
756
757 #[test]
758 fn test_request_timeout_clamps_zero_to_one_second() {
759 let mut config = LspServerConfig::rust_analyzer();
760 config.request_timeout_seconds = 0;
761 let client = LspClient::new(config);
762
763 assert_eq!(client.request_timeout(), Duration::from_secs(1));
764 assert_eq!(client.completion_timeout(), Duration::from_secs(1));
765 }
766
767 #[test]
768 fn test_request_timeout_clamps_above_max_to_max() {
769 let mut config = LspServerConfig::rust_analyzer();
770 config.request_timeout_seconds = u64::MAX;
771 let client = LspClient::new(config);
772
773 assert_eq!(
774 client.request_timeout(),
775 Duration::from_secs(crate::config::MAX_TIMEOUT_SECONDS)
776 );
777 }
778
779 #[test]
780 fn test_request_timeout_independent_per_server() {
781 let mut config_a = LspServerConfig::rust_analyzer();
782 config_a.request_timeout_seconds = 5;
783 let mut config_b = LspServerConfig::pyright();
784 config_b.request_timeout_seconds = 15;
785
786 let client_a = LspClient::new(config_a);
787 let client_b = LspClient::new(config_b);
788
789 assert_eq!(client_a.request_timeout(), Duration::from_secs(5));
790 assert_eq!(client_b.request_timeout(), Duration::from_secs(15));
791 }
792
793 #[test]
794 fn test_register_capability_request_is_acknowledged() {
795 let request = JsonRpcRequest {
796 jsonrpc: JSONRPC_VERSION.to_string(),
797 id: RequestId::String("ts1".to_string()),
798 method: "client/registerCapability".to_string(),
799 params: Some(serde_json::json!({ "registrations": [] })),
800 };
801
802 let response = LspClient::server_request_response(request);
803
804 assert_eq!(response.id, RequestId::String("ts1".to_string()));
805 assert_eq!(response.result, Some(Value::Null));
806 assert!(response.error.is_none());
807 }
808
809 #[test]
810 fn test_workspace_configuration_request_returns_null_per_item() {
811 let result = LspClient::workspace_configuration_result(Some(&serde_json::json!({
812 "items": [{ "section": "typescript" }, { "section": "editor" }]
813 })));
814
815 assert_eq!(result, serde_json::json!([null, null]));
816 }
817
818 #[test]
819 fn test_unknown_server_request_returns_method_not_found() {
820 let request = JsonRpcRequest {
821 jsonrpc: JSONRPC_VERSION.to_string(),
822 id: RequestId::String("unknown-1".to_string()),
823 method: "custom/request".to_string(),
824 params: None,
825 };
826
827 let response = LspClient::server_request_response(request);
828
829 assert!(response.result.is_none());
830 match response.error {
831 Some(error) => {
832 assert_eq!(error.code, -32601);
833 assert_eq!(error.message, "Unhandled server request: custom/request");
834 }
835 None => panic!("unknown request should return error"),
836 }
837 }
838
839 #[tokio::test]
840 async fn test_null_response_handling() {
841 use crate::lsp::types::{JsonRpcResponse, RequestId};
842
843 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
844
845 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
846
847 pending_requests
848 .lock()
849 .await
850 .insert(RequestId::Number(1), response_tx);
851
852 let null_response = JsonRpcResponse {
853 jsonrpc: "2.0".to_string(),
854 id: RequestId::Number(1),
855 result: None,
856 error: None,
857 };
858
859 let sender = pending_requests.lock().await.remove(&null_response.id);
860 if let Some(sender) = sender {
861 let _ = sender.send(Ok(Value::Null));
862 }
863
864 let timeout_result =
865 tokio::time::timeout(tokio::time::Duration::from_millis(100), response_rx).await;
866
867 assert!(timeout_result.is_ok(), "Should not timeout");
868
869 let channel_result = timeout_result.unwrap();
870 assert!(
871 channel_result.is_ok(),
872 "Channel should not be closed: {:?}",
873 channel_result.err()
874 );
875
876 let response = channel_result.unwrap();
877 assert!(
878 response.is_ok(),
879 "Should receive Ok(Value::Null), not Err: {:?}",
880 response.err()
881 );
882
883 let value = response.unwrap();
884 assert_eq!(value, Value::Null, "Should receive Value::Null");
885 }
886
887 #[tokio::test]
888 async fn test_error_response_handling() {
889 use crate::lsp::types::{JsonRpcError, JsonRpcResponse, RequestId};
890
891 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
892 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
893
894 pending_requests
895 .lock()
896 .await
897 .insert(RequestId::Number(1), response_tx);
898
899 let error_response = JsonRpcResponse {
900 jsonrpc: "2.0".to_string(),
901 id: RequestId::Number(1),
902 result: None,
903 error: Some(JsonRpcError {
904 code: -32601,
905 message: "Method not found".to_string(),
906 data: None,
907 }),
908 };
909
910 let sender = pending_requests.lock().await.remove(&error_response.id);
911 if let Some(sender) = sender
912 && let Some(error) = error_response.error
913 {
914 let _ = sender.send(Err(Error::LspServerError {
915 code: error.code,
916 message: error.message,
917 data: error.data,
918 }));
919 }
920
921 let result = response_rx.await.unwrap();
922 assert!(result.is_err(), "Should receive error");
923
924 if let Err(Error::LspServerError { code, message, .. }) = result {
925 assert_eq!(code, -32601);
926 assert_eq!(message, "Method not found");
927 } else {
928 panic!("Expected LspServerError");
929 }
930 }
931
932 #[tokio::test]
933 async fn test_unknown_request_id() {
934 use crate::lsp::types::{JsonRpcResponse, RequestId};
935
936 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
937
938 let response = JsonRpcResponse {
939 jsonrpc: "2.0".to_string(),
940 id: RequestId::Number(999),
941 result: Some(Value::Null),
942 error: None,
943 };
944
945 let sender = pending_requests.lock().await.remove(&response.id);
946 assert!(sender.is_none(), "Should not find sender for unknown ID");
947 }
948
949 #[test]
950 fn test_truncate_error_message_for_log_handles_multibyte_boundary() {
951 let message = format!("{}€{}", "x".repeat(199), "y".repeat(50));
953
954 let truncated = LspClient::truncate_error_message_for_log(&message);
955
956 assert_eq!(truncated, format!("{}... (truncated)", "x".repeat(199)));
959 }
960
961 #[test]
962 fn test_truncate_error_message_for_log_no_truncation_at_or_below_limit() {
963 let exact = "x".repeat(200);
964 assert_eq!(LspClient::truncate_error_message_for_log(&exact), exact);
965 assert_eq!(LspClient::truncate_error_message_for_log(""), "");
966 }
967
968 #[test]
969 fn test_truncate_error_message_for_log_truncates_just_above_limit() {
970 let message = "x".repeat(201);
971 assert_eq!(
972 LspClient::truncate_error_message_for_log(&message),
973 format!("{}... (truncated)", "x".repeat(200))
974 );
975 }
976
977 #[test]
978 fn test_truncate_error_message_for_log_handles_wide_char_at_limit() {
979 let message = format!("{}{}", "x".repeat(197), "🦀".repeat(10));
981
982 let truncated = LspClient::truncate_error_message_for_log(&message);
983
984 assert_eq!(truncated, format!("{}... (truncated)", "x".repeat(197)));
985 }
986
987 #[tokio::test]
988 async fn test_concurrent_request_ids() {
989 let counter = Arc::new(AtomicI64::new(1));
990
991 let counter1 = Arc::clone(&counter);
992 let counter2 = Arc::clone(&counter);
993 let counter3 = Arc::clone(&counter);
994
995 let handles = vec![
996 tokio::spawn(async move { counter1.fetch_add(1, Ordering::SeqCst) }),
997 tokio::spawn(async move { counter2.fetch_add(1, Ordering::SeqCst) }),
998 tokio::spawn(async move { counter3.fetch_add(1, Ordering::SeqCst) }),
999 ];
1000
1001 let mut ids = Vec::new();
1002 for handle in handles {
1003 ids.push(handle.await.unwrap());
1004 }
1005
1006 ids.sort_unstable();
1007 assert_eq!(ids, vec![1, 2, 3], "IDs should be unique and sequential");
1008 }
1009
1010 #[test]
1011 fn test_jsonrpc_version_constant() {
1012 assert_eq!(JSONRPC_VERSION, "2.0");
1013 }
1014
1015 #[cfg(unix)]
1024 #[tokio::test]
1025 async fn test_request_timeout_removes_pending_entry() {
1026 let mut child = tokio::process::Command::new("sleep")
1027 .arg("2")
1028 .stdin(std::process::Stdio::piped())
1029 .stdout(std::process::Stdio::piped())
1030 .kill_on_drop(true)
1031 .spawn()
1032 .unwrap();
1033 let stdin = child.stdin.take().unwrap();
1034 let stdout = child.stdout.take().unwrap();
1035
1036 let transport = LspTransport::new(stdin, stdout);
1037 let client = LspClient::from_transport(LspServerConfig::rust_analyzer(), transport);
1038
1039 let result: Result<Value> = client
1040 .request(
1041 "textDocument/hover",
1042 serde_json::json!({}),
1043 Duration::from_millis(50),
1044 )
1045 .await;
1046
1047 assert!(matches!(result, Err(Error::Timeout(_))), "got {result:?}");
1048 assert!(
1049 client.pending_requests.lock().await.is_empty(),
1050 "timed-out request must not remain in pending_requests"
1051 );
1052 }
1053
1054 #[tokio::test]
1058 async fn test_fail_pending_requests_resolves_all_as_server_terminated() {
1059 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
1060 let (command_tx, _command_rx) = mpsc::channel(1);
1061
1062 let client = LspClient {
1063 config: LspServerConfig::rust_analyzer(),
1064 state: Arc::new(Mutex::new(super::super::ServerState::Ready)),
1065 request_counter: Arc::new(AtomicI64::new(1)),
1066 command_tx,
1067 pending_requests: Arc::clone(&pending_requests),
1068 receiver_task: None,
1069 };
1070
1071 let (tx1, rx1) = oneshot::channel::<Result<Value>>();
1072 let (tx2, rx2) = oneshot::channel::<Result<Value>>();
1073 pending_requests
1074 .lock()
1075 .await
1076 .insert(RequestId::Number(1), tx1);
1077 pending_requests
1078 .lock()
1079 .await
1080 .insert(RequestId::Number(2), tx2);
1081
1082 client.fail_pending_requests().await;
1083
1084 assert!(pending_requests.lock().await.is_empty());
1085 assert!(matches!(rx1.await.unwrap(), Err(Error::ServerTerminated)));
1086 assert!(matches!(rx2.await.unwrap(), Err(Error::ServerTerminated)));
1087 }
1088
1089 #[test]
1090 fn test_should_retrigger_defaults_to_true_when_data_absent() {
1091 assert!(LspClient::should_retrigger(None));
1092 }
1093
1094 #[test]
1095 fn test_should_retrigger_false_when_flag_false() {
1096 assert!(!LspClient::should_retrigger(Some(&serde_json::json!({
1097 "retriggerRequest": false
1098 }))));
1099 }
1100
1101 #[test]
1102 fn test_should_retrigger_true_when_flag_true() {
1103 assert!(LspClient::should_retrigger(Some(&serde_json::json!({
1104 "retriggerRequest": true
1105 }))));
1106 }
1107
1108 mod retry_behavior {
1109 use std::process::Stdio;
1110
1111 use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
1112 use tokio::process::{Child, ChildStdin, ChildStdout, Command};
1113
1114 use super::*;
1115 use crate::config::LspServerConfig;
1116
1117 struct FakeServer {
1118 _write_half: Child,
1119 _read_half: Child,
1120 read_half_stdin: ChildStdin,
1121 write_stdout: ChildStdout,
1122 }
1123
1124 fn fake_lsp_client() -> (LspClient, FakeServer) {
1125 let mut write_half = Command::new("cat")
1126 .stdin(Stdio::piped())
1127 .stdout(Stdio::piped())
1128 .kill_on_drop(true)
1129 .spawn()
1130 .unwrap();
1131 let write_stdin = write_half.stdin.take().unwrap();
1132 let write_stdout = write_half.stdout.take().unwrap();
1133
1134 let mut read_half = Command::new("cat")
1135 .stdin(Stdio::piped())
1136 .stdout(Stdio::piped())
1137 .kill_on_drop(true)
1138 .spawn()
1139 .unwrap();
1140 let read_stdout = read_half.stdout.take().unwrap();
1141 let read_stdin = read_half.stdin.take().unwrap();
1142
1143 let transport = LspTransport::new(write_stdin, read_stdout);
1144 let client = LspClient::from_transport(LspServerConfig::rust_analyzer(), transport);
1145
1146 (
1147 client,
1148 FakeServer {
1149 _write_half: write_half,
1150 _read_half: read_half,
1151 read_half_stdin: read_stdin,
1152 write_stdout,
1153 },
1154 )
1155 }
1156
1157 async fn read_framed_message(reader: &mut BufReader<&mut ChildStdout>) -> Value {
1159 let mut content_length = None;
1160 let mut line = String::new();
1161 loop {
1162 line.clear();
1163 reader.read_line(&mut line).await.unwrap();
1164 if line == "\r\n" || line == "\n" {
1165 break;
1166 }
1167 if let Some((key, value)) = line.trim_end().split_once(':')
1168 && key.trim().eq_ignore_ascii_case("content-length")
1169 {
1170 content_length = Some(value.trim().parse::<usize>().unwrap());
1171 }
1172 }
1173 let mut buf = vec![0u8; content_length.unwrap()];
1174 reader.read_exact(&mut buf).await.unwrap();
1175 serde_json::from_slice(&buf).unwrap()
1176 }
1177
1178 async fn write_server_cancelled_response(
1180 stdin: &mut ChildStdin,
1181 id: &Value,
1182 retrigger: bool,
1183 ) {
1184 let response = serde_json::json!({
1185 "jsonrpc": "2.0",
1186 "id": id,
1187 "error": {
1188 "code": SERVER_CANCELLED_CODE,
1189 "message": "server cancelled the request",
1190 "data": { "retriggerRequest": retrigger },
1191 },
1192 });
1193 let content = serde_json::to_string(&response).unwrap();
1194 let header = format!("Content-Length: {}\r\n\r\n", content.len());
1195 stdin.write_all(header.as_bytes()).await.unwrap();
1196 stdin.write_all(content.as_bytes()).await.unwrap();
1197 stdin.flush().await.unwrap();
1198 }
1199
1200 async fn write_error_response(
1202 stdin: &mut ChildStdin,
1203 id: &Value,
1204 code: i32,
1205 message: &str,
1206 ) {
1207 let response = serde_json::json!({
1208 "jsonrpc": "2.0",
1209 "id": id,
1210 "error": { "code": code, "message": message },
1211 });
1212 let content = serde_json::to_string(&response).unwrap();
1213 let header = format!("Content-Length: {}\r\n\r\n", content.len());
1214 stdin.write_all(header.as_bytes()).await.unwrap();
1215 stdin.write_all(content.as_bytes()).await.unwrap();
1216 stdin.flush().await.unwrap();
1217 }
1218
1219 async fn write_success_response(stdin: &mut ChildStdin, id: &Value, result: Value) {
1221 let response = serde_json::json!({
1222 "jsonrpc": "2.0",
1223 "id": id,
1224 "result": result,
1225 });
1226 let content = serde_json::to_string(&response).unwrap();
1227 let header = format!("Content-Length: {}\r\n\r\n", content.len());
1228 stdin.write_all(header.as_bytes()).await.unwrap();
1229 stdin.write_all(content.as_bytes()).await.unwrap();
1230 stdin.flush().await.unwrap();
1231 }
1232
1233 #[tokio::test]
1237 async fn test_retry_exhaustion_returns_original_server_cancelled_error() {
1238 let (client, mut server) = fake_lsp_client();
1239
1240 let request_task = tokio::spawn(async move {
1241 client
1242 .request::<_, Value>(
1243 "textDocument/hover",
1244 serde_json::json!({}),
1245 Duration::from_secs(30),
1246 )
1247 .await
1248 });
1249
1250 let mut reader = BufReader::new(&mut server.write_stdout);
1251 for _ in 0..=SERVER_CANCELLED_MAX_RETRIES {
1255 let request = read_framed_message(&mut reader).await;
1256 let id = request["id"].clone();
1257 write_server_cancelled_response(&mut server.read_half_stdin, &id, true).await;
1258 }
1259
1260 let result = request_task.await.unwrap();
1261
1262 match result {
1263 Err(Error::LspServerError {
1264 code,
1265 message,
1266 data,
1267 }) => {
1268 assert_eq!(code, SERVER_CANCELLED_CODE);
1272 assert_eq!(message, "server cancelled the request");
1273 assert_eq!(data, Some(serde_json::json!({ "retriggerRequest": true })));
1274 }
1275 other => panic!("expected exhausted ServerCancelled error, got {other:?}"),
1276 }
1277 }
1278
1279 #[tokio::test]
1280 async fn test_retrigger_false_returns_immediately_without_retry() {
1281 let (client, mut server) = fake_lsp_client();
1282
1283 let request_task = tokio::spawn(async move {
1284 client
1285 .request::<_, Value>(
1286 "textDocument/hover",
1287 serde_json::json!({}),
1288 Duration::from_secs(30),
1289 )
1290 .await
1291 });
1292
1293 let mut reader = BufReader::new(&mut server.write_stdout);
1294 let request = read_framed_message(&mut reader).await;
1295 let id = request["id"].clone();
1296 write_server_cancelled_response(&mut server.read_half_stdin, &id, false).await;
1297
1298 let result = tokio::time::timeout(Duration::from_millis(200), request_task)
1305 .await
1306 .unwrap()
1307 .unwrap();
1308
1309 match result {
1310 Err(Error::LspServerError { code, .. }) => {
1311 assert_eq!(code, SERVER_CANCELLED_CODE);
1312 }
1313 other => panic!("expected immediate ServerCancelled error, got {other:?}"),
1314 }
1315
1316 let second_request =
1317 tokio::time::timeout(Duration::from_millis(200), read_framed_message(&mut reader))
1318 .await;
1319 assert!(
1320 second_request.is_err(),
1321 "no retry should have been sent after retriggerRequest: false"
1322 );
1323 }
1324
1325 #[tokio::test]
1326 async fn test_retry_succeeds_after_one_server_cancelled_response() {
1327 let (client, mut server) = fake_lsp_client();
1328
1329 let request_task = tokio::spawn(async move {
1330 client
1331 .request::<_, Value>(
1332 "textDocument/hover",
1333 serde_json::json!({}),
1334 Duration::from_secs(30),
1335 )
1336 .await
1337 });
1338
1339 let mut reader = BufReader::new(&mut server.write_stdout);
1340
1341 let first = read_framed_message(&mut reader).await;
1343 write_server_cancelled_response(
1344 &mut server.read_half_stdin,
1345 &first["id"].clone(),
1346 true,
1347 )
1348 .await;
1349
1350 let second = read_framed_message(&mut reader).await;
1353 assert_ne!(
1354 first["id"], second["id"],
1355 "retry must use a fresh request id"
1356 );
1357 let expected_result = serde_json::json!({ "contents": "resolved on retry" });
1358 write_success_response(
1359 &mut server.read_half_stdin,
1360 &second["id"].clone(),
1361 expected_result.clone(),
1362 )
1363 .await;
1364
1365 let result = request_task.await.unwrap();
1366 assert_eq!(result.unwrap(), expected_result);
1367 }
1368
1369 #[tokio::test]
1376 async fn test_oversized_error_message_truncated_for_caller() {
1377 let (client, mut server) = fake_lsp_client();
1378
1379 let request_task = tokio::spawn(async move {
1380 client
1381 .request::<_, Value>(
1382 "textDocument/hover",
1383 serde_json::json!({}),
1384 Duration::from_secs(30),
1385 )
1386 .await
1387 });
1388
1389 let mut reader = BufReader::new(&mut server.write_stdout);
1390 let request = read_framed_message(&mut reader).await;
1391 let id = request["id"].clone();
1392 let oversized_message = "x".repeat(MAX_ERROR_MESSAGE_CALLER_BYTES + 500);
1393 write_error_response(&mut server.read_half_stdin, &id, -32603, &oversized_message)
1394 .await;
1395
1396 let result = request_task.await.unwrap();
1397
1398 match result {
1399 Err(Error::LspServerError { code, message, .. }) => {
1400 assert_eq!(code, -32603);
1401 assert!(
1402 message.len() < oversized_message.len(),
1403 "caller-facing message must be truncated, got {} bytes",
1404 message.len()
1405 );
1406 assert!(message.ends_with("... (truncated)"));
1407 }
1408 other => panic!("expected truncated LspServerError, got {other:?}"),
1409 }
1410 }
1411
1412 #[tokio::test]
1418 async fn test_error_message_between_log_and_caller_caps_reaches_caller_intact() {
1419 let (client, mut server) = fake_lsp_client();
1420
1421 let request_task = tokio::spawn(async move {
1422 client
1423 .request::<_, Value>(
1424 "textDocument/hover",
1425 serde_json::json!({}),
1426 Duration::from_secs(30),
1427 )
1428 .await
1429 });
1430
1431 let mut reader = BufReader::new(&mut server.write_stdout);
1432 let request = read_framed_message(&mut reader).await;
1433 let id = request["id"].clone();
1434 let message = "x".repeat(MAX_ERROR_MESSAGE_LOG_BYTES + 50);
1435 write_error_response(&mut server.read_half_stdin, &id, -32603, &message).await;
1436
1437 let result = request_task.await.unwrap();
1438
1439 match result {
1440 Err(Error::LspServerError {
1441 message: returned, ..
1442 }) => {
1443 assert_eq!(
1444 returned, message,
1445 "message under the caller cap must not be truncated"
1446 );
1447 }
1448 other => panic!("expected untruncated LspServerError, got {other:?}"),
1449 }
1450 }
1451 }
1452}