1use std::collections::HashSet;
11use std::sync::Arc;
12use std::time::Duration;
13
14use futures::StreamExt;
15use futures::future::BoxFuture;
16use schemars::JsonSchema;
17use serde::Serialize;
18use tokio::sync::mpsc;
19use tokio::task::AbortHandle;
20
21use super::Translator;
22use super::respawn::{BackoffPolicy, RespawnConfig};
23use super::servers::{Backend, Phase};
24use crate::bridge::indexing::IndexingReset;
25use crate::bridge::{DiagnosticsKey, IndexingState, NotificationCache};
26use crate::config::{LanguageId, ServerId};
27use crate::error::{Error, ErrorChain, Result};
28use crate::lsp::{ExitGrace, LspNotification};
29use crate::redaction::{Redactions, ServerText};
30use crate::util::lock_std;
31
32const RESTART_COOLDOWN: Duration = Duration::from_secs(5);
34
35const RESTART_HANDSHAKE_BUDGET: Duration = Duration::from_secs(3);
39
40const RESTART_EXIT_GRACE: Duration = crate::lsp::SHUTDOWN_TIMEOUT;
44
45pub const MAX_RESTART_SERVER_IDS: usize = 64;
47
48const RESTART_CONCURRENCY: usize = 4;
50
51pub const MAX_SERVER_ID_BYTES: usize = 256;
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
56pub enum ServerIdsError {
57 #[error("`servers` must not be empty")]
59 Empty,
60 #[error("`servers` may name at most {MAX_RESTART_SERVER_IDS} servers")]
62 TooMany,
63 #[error("server ids must not be blank")]
65 Blank,
66 #[error("server ids must be at most {MAX_SERVER_ID_BYTES} bytes")]
68 TooLong,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct ServerIds(Vec<ServerId>);
86
87impl ServerIds {
88 pub fn try_new(ids: Vec<ServerId>) -> std::result::Result<Self, ServerIdsError> {
95 if ids.len() > MAX_RESTART_SERVER_IDS {
96 return Err(ServerIdsError::TooMany);
97 }
98 if ids.iter().any(|id| id.as_str().len() > MAX_SERVER_ID_BYTES) {
99 return Err(ServerIdsError::TooLong);
100 }
101 let mut seen = HashSet::new();
102 let unique: Vec<ServerId> = ids
103 .into_iter()
104 .filter(|id| seen.insert(id.clone()))
105 .collect();
106 (!unique.is_empty())
107 .ok_or(ServerIdsError::Empty)
108 .map(|()| Self(unique))
109 }
110
111 pub fn parse(ids: Vec<String>) -> std::result::Result<Self, ServerIdsError> {
128 if ids.len() > MAX_RESTART_SERVER_IDS {
129 return Err(ServerIdsError::TooMany);
130 }
131 let ids = ids
132 .into_iter()
133 .map(|id| ServerId::new(id).map_err(|_| ServerIdsError::Blank))
134 .collect::<std::result::Result<Vec<_>, _>>()?;
135 Self::try_new(ids)
136 }
137
138 #[must_use]
140 pub fn as_slice(&self) -> &[ServerId] {
141 &self.0
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
157pub enum RestartTarget {
158 All,
160 Servers(ServerIds),
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
171#[serde(tag = "kind", rename_all = "snake_case")]
172#[non_exhaustive]
173pub enum RestartFailure {
174 Refused {
180 message: String,
182 },
183 SpawnFailed {
185 message: String,
187 },
188 InitializeFailed {
190 message: String,
192 },
193 ShuttingDown,
195}
196
197impl RestartFailure {
198 fn from_error(error: &Error) -> Self {
199 let message = ErrorChain(error).to_string();
200 match error {
201 Error::ServerNotFound { .. }
202 | Error::ServerSpawnFailed { .. }
203 | Error::ServerFailedToStart(_) => Self::SpawnFailed { message },
204 Error::LspInitFailed { .. }
205 | Error::ServerExitedDuringInit { .. }
206 | Error::ServerTerminated
207 | Error::TaskFailed { .. }
208 | Error::StdioCapture(_)
209 | Error::Timeout(_)
210 | Error::LspServerError(..)
211 | Error::LspProtocolError(_)
212 | Error::ShutdownTimeout
213 | Error::Io(_)
214 | Error::Json(_)
215 | Error::McpServerStart(_)
218 | Error::PathToUri(_)
219 | Error::HttpBind { .. }
220 | Error::InvalidClientPath(_)
221 | Error::MalformedPath { .. }
222 | Error::DocumentNotFound(_)
223 | Error::NoServerForLanguage { .. }
224 | Error::NoServerForTool { .. }
225 | Error::ServerInitializing { .. }
226 | Error::ServerRestarted { .. }
227 | Error::SymbolResolution(_)
228 | Error::UnknownServers { .. }
229 | Error::WorkspaceServersInitializing
230 | Error::NoServerConfigured
231 | Error::NoServerForWorkspaceTool { .. }
232 | Error::ConfigNotFound(_)
233 | Error::ConfigInsideWorkspace { .. }
234 | Error::Config(_)
235 | Error::TomlDe(_)
236 | Error::TomlSer(_)
237 | Error::NoResolvableListenUris
238 | Error::ResourceUri(_)
239 | Error::InvalidPositionInput(_)
240 | Error::InvalidRangeInput(_)
241 | Error::InvalidHierarchyItemInput(_)
242 | Error::PositionBeyondDocument { .. }
243 | Error::ServerUnavailable { .. }
244 | Error::InvalidToolParams(_)
245 | Error::FileIo { .. }
246 | Error::PathOutsideWorkspace(_)
247 | Error::NoWorkspaceRoots(_)
248 | Error::DocumentLimitExceeded { .. }
249 | Error::SubscriptionLimitReached { .. }
250 | Error::ListenStreamsExhausted { .. }
251 | Error::ListenFilterTooLarge { .. }
252 | Error::FileSizeLimitExceeded { .. }
253 | Error::NotARegularFile(_)
254 | Error::AllServersFailedToInit { .. }
255 | Error::CapabilityNotSupported { .. }
256 | Error::WorkspaceIndexing { .. } => Self::InitializeFailed { message },
257 }
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
272#[serde(tag = "status", rename_all = "snake_case")]
273#[non_exhaustive]
274pub enum RestartOutcome {
275 Restarted {
277 coalesced: bool,
279 indexing_state: IndexingState,
281 push_notifications_degraded: bool,
285 },
286 Failed {
288 reason: RestartFailure,
290 },
291 Throttled {
293 retry_in_ms: u64,
295 },
296 Initializing,
298 NotRunning {
301 message: String,
303 },
304}
305
306impl RestartOutcome {
307 pub(crate) fn not_running(id: &ServerId) -> Self {
309 Self::NotRunning {
310 message: format!("LSP server '{id}' is not running"),
311 }
312 }
313}
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317enum RestartRun {
318 Performed,
320 Coalesced,
322}
323
324#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
326pub struct ServerRestartEntry {
327 pub server_id: ServerId,
329 #[serde(flatten)]
331 pub outcome: RestartOutcome,
332}
333
334#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
336pub struct RestartServerResult {
337 pub servers: Vec<ServerRestartEntry>,
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
343pub(super) struct RestartGeneration(u64);
344
345impl RestartGeneration {
346 const fn bump(&mut self) {
347 self.0 = self.0.saturating_add(1);
348 }
349}
350
351#[derive(Debug)]
353pub struct NotificationReceivers {
354 pub(crate) notifications: crate::lsp::NotificationInbox,
356 pub(crate) lifecycle: mpsc::Receiver<LspNotification>,
358 pub(crate) pinned_tsserver: Option<std::path::PathBuf>,
361}
362
363impl NotificationReceivers {
364 pub(crate) fn new(
366 lanes: crate::lsp::ServerLanes,
367 pinned_tsserver: Option<std::path::PathBuf>,
368 ) -> Self {
369 let crate::lsp::ServerLanes {
370 notifications,
371 lifecycle,
372 } = lanes;
373 Self {
374 notifications,
375 lifecycle,
376 pinned_tsserver,
377 }
378 }
379}
380
381#[derive(Clone, Copy)]
383pub(super) enum NotificationRouting<'a> {
384 Discard,
387 Pump(&'a dyn NotificationWiring),
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq)]
402pub enum DiagnosticsRole {
403 Authoritative,
406 Secondary,
410}
411
412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
414pub(super) enum PushDelivery {
415 Live,
417 Degraded,
419}
420
421pub(super) fn reset_server_cache(
426 cache: &mut NotificationCache,
427 id: &ServerId,
428 indexing: IndexingReset,
429 role: DiagnosticsRole,
430 push: PushDelivery,
431) -> Vec<DiagnosticsKey> {
432 cache.reset_indexing_state(id, indexing);
433 if role != DiagnosticsRole::Authoritative {
434 return Vec::new();
435 }
436 let cleared = cache.clear_server_diagnostics(id);
437 match push {
438 PushDelivery::Live => cache.clear_push_degraded(id),
439 PushDelivery::Degraded => cache.mark_push_degraded(id),
440 }
441 cleared
442}
443
444impl DiagnosticsRole {
445 pub(crate) const fn from_route(is_diagnostics_route: bool) -> Self {
447 if is_diagnostics_route {
448 Self::Authoritative
449 } else {
450 Self::Secondary
451 }
452 }
453}
454
455pub trait NotificationWiring: std::fmt::Debug + Send + Sync {
459 fn spawn_pump(
462 &self,
463 id: ServerId,
464 receivers: NotificationReceivers,
465 role: DiagnosticsRole,
466 ) -> AbortHandle;
467
468 fn publish_invalidated<'a>(&'a self, cleared: &'a [DiagnosticsKey]) -> BoxFuture<'a, ()>;
472
473 fn has_subscriptions(&self) -> BoxFuture<'_, bool> {
477 Box::pin(async { true })
478 }
479
480 fn publish_changed<'a>(&'a self, file: &'a lsp_types::Uri) -> BoxFuture<'a, ()>;
484}
485
486#[derive(Debug)]
489pub struct StartupGuard<'a>(&'a Translator);
490
491impl Drop for StartupGuard<'_> {
492 fn drop(&mut self) {
493 lock_std(&self.0.phase).finish_startup();
494 }
495}
496
497struct Deregistered<'a> {
500 translator: &'a Translator,
501 id: ServerId,
502 backend: Option<Backend>,
503}
504
505impl<'a> Deregistered<'a> {
506 fn take(translator: &'a Translator, id: &ServerId) -> Option<Self> {
510 let backend = lock_std(&translator.servers).take_for_restart(id)?;
511 let held = Self {
512 translator,
513 id: id.clone(),
514 backend: Some(backend),
515 };
516 held.backend
517 .as_ref()
518 .is_some_and(|backend| backend.server().is_some())
519 .then_some(held)
520 }
521
522 async fn terminate(&mut self) {
525 let Some(backend) = self.backend.as_mut() else {
526 return;
527 };
528 backend.client().mark_restarted(self.id.clone());
529 let Some(server) = backend.server_mut() else {
530 return;
531 };
532 let stopped = server
533 .terminate(
534 RESTART_HANDSHAKE_BUDGET,
535 ExitGrace::IfAnswered(RESTART_EXIT_GRACE),
536 )
537 .await;
538 if let Err(error) = stopped {
539 tracing::warn!(id = %self.id, error = %ErrorChain(&error), "old LSP server did not stop cleanly; continuing with restart");
540 }
541 }
542
543 fn restore(&mut self) {
545 if let Some(backend) = self.backend.take() {
546 let refused = lock_std(&self.translator.servers).restore(&self.id, backend);
547 if refused.is_some() {
548 tracing::warn!(id = %self.id, "restored server dropped: its slot was settled meanwhile");
549 }
550 drop(refused);
551 }
552 }
553}
554
555impl Drop for Deregistered<'_> {
556 fn drop(&mut self) {
557 self.restore();
558 }
559}
560
561impl Translator {
562 pub async fn restart_servers(&self, target: RestartTarget) -> Result<RestartServerResult> {
593 let known = self.known_server_ids();
594 let ids = match target {
595 RestartTarget::All => known,
596 RestartTarget::Servers(requested) => {
597 let unknown: Vec<ServerId> = requested
598 .as_slice()
599 .iter()
600 .filter(|id| !known.contains(id))
601 .cloned()
602 .collect();
603 if !unknown.is_empty() {
604 return Err(Error::UnknownServers {
605 unknown,
606 configured: known,
607 });
608 }
609 requested.0
610 }
611 };
612
613 let requests: Vec<(ServerId, RestartGeneration)> = ids
616 .into_iter()
617 .map(|id| {
618 let generation = self.restart_generation(&id);
619 (id, generation)
620 })
621 .collect();
622 let mut servers: Vec<ServerRestartEntry> = futures::stream::iter(requests)
623 .map(|(id, generation)| async move {
624 let outcome = Box::pin(self.restart_one(&id, generation)).await;
625 ServerRestartEntry {
626 server_id: id,
627 outcome,
628 }
629 })
630 .buffer_unordered(RESTART_CONCURRENCY)
631 .collect()
632 .await;
633 servers.sort_by(|a, b| a.server_id.as_str().cmp(b.server_id.as_str()));
634 Ok(RestartServerResult { servers })
635 }
636
637 fn known_server_ids(&self) -> Vec<ServerId> {
640 let mut ids: Vec<ServerId> = lock_std(&self.servers).ids().cloned().collect();
641 ids.sort_by(|a, b| a.as_str().cmp(b.as_str()));
642 ids
643 }
644
645 fn restart_generation(&self, id: &ServerId) -> RestartGeneration {
646 lock_std(&self.servers)
647 .get(id)
648 .map_or_default(|slot| slot.restart.generation)
649 }
650
651 pub(crate) fn install_wiring(&self, wiring: Arc<dyn NotificationWiring>) {
658 if self.wiring.set(wiring).is_err() {
659 tracing::debug!("notification wiring already installed");
660 }
661 }
662
663 pub(crate) fn set_notification_task(&self, id: &ServerId, handle: AbortHandle) {
666 if let Some(slot) = lock_std(&self.servers).get_mut(id) {
667 slot.notification_task = Some(handle);
668 }
669 }
670
671 pub(super) fn take_notification_task(&self, id: &ServerId) -> Option<AbortHandle> {
673 lock_std(&self.servers)
674 .get_mut(id)
675 .and_then(|slot| slot.notification_task.take())
676 }
677
678 pub(crate) fn begin_startup(&self) -> StartupGuard<'_> {
683 lock_std(&self.phase).begin_startup();
684 StartupGuard(self)
685 }
686
687 pub(crate) fn begin_shutdown(&self) {
689 lock_std(&self.phase).begin_shutdown();
690 }
691
692 fn is_shutting_down(&self) -> bool {
693 *lock_std(&self.phase) == Phase::ShuttingDown
694 }
695
696 fn restart_cooldown_remaining(&self, id: &ServerId) -> Option<Duration> {
698 let last = lock_std(&self.servers).get(id)?.restart.last_attempt?;
699 let elapsed = self.clock.now().saturating_duration_since(last);
700 RESTART_COOLDOWN.checked_sub(elapsed)
701 }
702
703 async fn restarted_outcome(&self, id: &ServerId, run: RestartRun) -> RestartOutcome {
704 let (indexing_state, push_notifications_degraded) = match &self.notification_cache {
705 Some(cache) => {
706 let cache = cache.lock().await;
707 (cache.indexing_state(id), cache.is_push_degraded(id))
708 }
709 None => (IndexingState::Unknown, false),
710 };
711 RestartOutcome::Restarted {
712 coalesced: run == RestartRun::Coalesced,
713 indexing_state,
714 push_notifications_degraded,
715 }
716 }
717
718 fn restart_permit(
723 &self,
724 id: &ServerId,
725 ) -> std::result::Result<&Arc<dyn NotificationWiring>, RestartOutcome> {
726 let phase = *lock_std(&self.phase);
727 if phase == Phase::ShuttingDown {
728 return Err(RestartOutcome::Failed {
729 reason: RestartFailure::ShuttingDown,
730 });
731 }
732 let not_running = {
733 let servers = lock_std(&self.servers);
734 let outcome = servers
735 .server(id)
736 .is_none()
737 .then(|| match servers.failure(id) {
738 Some(failure) => RestartOutcome::NotRunning {
739 message: Error::ServerFailedToStart(Box::new(failure.clone())).to_string(),
740 },
741 None if servers.is_expected(id) => RestartOutcome::Initializing,
742 None => RestartOutcome::not_running(id),
743 });
744 drop(servers);
745 outcome
746 };
747 if let Some(outcome) = not_running {
748 return Err(outcome);
749 }
750 if phase == Phase::InitPanicked {
751 return Err(RestartOutcome::NotRunning {
752 message: "startup was interrupted by a panic, so servers cannot be restarted; restart mcpls".to_string(),
753 });
754 }
755 let wiring = match (phase, self.wiring.get()) {
756 (Phase::Settling, _) | (_, None) => return Err(RestartOutcome::Initializing),
757 (_, Some(wiring)) => wiring,
758 };
759 self.restart_cooldown_remaining(id)
760 .map_or(Ok(wiring), |remaining| {
761 Err(RestartOutcome::Throttled {
762 retry_in_ms: u64::try_from(remaining.as_millis()).unwrap_or(u64::MAX),
763 })
764 })
765 }
766
767 async fn restart_one(&self, id: &ServerId, seen: RestartGeneration) -> RestartOutcome {
768 let lock = self.respawn_lock(id);
769 let _serialized = lock.lock().await;
770
771 if self.restart_generation(id) != seen {
772 return self.restarted_outcome(id, RestartRun::Coalesced).await;
773 }
774 let wiring = match self.restart_permit(id) {
775 Ok(wiring) => wiring,
776 Err(outcome) => return outcome,
777 };
778
779 let running = lock_std(&self.servers)
780 .server(id)
781 .map(|server| server.init_config().clone());
782 let Some(running) = running else {
783 return RestartOutcome::not_running(id);
784 };
785 let vetted = match running.for_respawn().await {
786 Ok(vetted) => vetted,
787 Err(error) => {
788 tracing::warn!(%id, error = %ErrorChain(&error), "LSP server restart refused");
789 return RestartOutcome::Failed {
790 reason: RestartFailure::Refused {
791 message: ErrorChain(&error).to_string(),
792 },
793 };
794 }
795 };
796
797 self.note_restart_attempt(id);
799 tracing::info!(%id, "restarting LSP server on request");
800
801 let Some(mut old) = Deregistered::take(self, id) else {
802 return RestartOutcome::not_running(id);
803 };
804 let language_id = vetted.server_config().language_id.clone();
805 old.terminate().await;
806 drop(old);
807
808 if self.is_shutting_down() {
809 self.invalidate_stopped_server(id, &language_id).await;
810 return RestartOutcome::Failed {
811 reason: RestartFailure::ShuttingDown,
812 };
813 }
814 let respawned = self
815 .respawn_locked(
816 id,
817 RespawnConfig::Vetted(vetted),
818 BackoffPolicy::Bypass,
819 NotificationRouting::Pump(wiring.as_ref()),
820 )
821 .await;
822 self.note_restart_attempt(id);
824 if let Err(error) = respawned {
825 tracing::warn!(%id, error = %ErrorChain(&error), "LSP server restart failed");
826 self.invalidate_stopped_server(id, &language_id).await;
827 return RestartOutcome::Failed {
828 reason: RestartFailure::from_error(&error),
829 };
830 }
831
832 if self.is_shutting_down() {
833 self.discard_registered(id).await;
834 return RestartOutcome::Failed {
835 reason: RestartFailure::ShuttingDown,
836 };
837 }
838 if let Some(slot) = lock_std(&self.servers).get_mut(id) {
839 slot.restart.generation.bump();
840 }
841 tracing::info!(%id, "LSP server restarted");
842 self.restarted_outcome(id, RestartRun::Performed).await
843 }
844
845 fn note_restart_attempt(&self, id: &ServerId) {
846 if let Some(slot) = lock_std(&self.servers).get_mut(id) {
847 slot.restart.last_attempt = Some(self.clock.now());
848 }
849 }
850
851 async fn invalidate_stopped_server(&self, id: &ServerId, language_id: &LanguageId) {
854 if let Some(pump) = self.take_notification_task(id) {
858 pump.abort();
859 tokio::task::yield_now().await;
860 }
861 let cleared = match &self.notification_cache {
862 Some(cache) => {
863 let mut cache = cache.lock().await;
864 reset_server_cache(
865 &mut cache,
866 id,
867 IndexingReset::Forget,
868 DiagnosticsRole::from_route(self.is_diagnostics_route(language_id, id)),
869 PushDelivery::Degraded,
870 )
871 }
872 None => Vec::new(),
873 };
874 if let Some(wiring) = self.wiring.get() {
875 wiring.publish_invalidated(&cleared).await;
876 }
877 }
878
879 async fn discard_registered(&self, id: &ServerId) {
882 let server = lock_std(&self.servers).remove_server(id);
883 if let Some(server) = server
884 && let Err(error) = server.shutdown().await
885 {
886 tracing::warn!(%id, error = %ErrorChain(&error), "replacement LSP server shutdown failed");
887 }
888 }
889}
890
891impl ServerText for RestartFailure {
892 fn redact_server_text(&mut self, redactions: &Redactions) {
893 match self {
894 Self::Refused { message }
895 | Self::SpawnFailed { message }
896 | Self::InitializeFailed { message } => {
897 redactions.redact_in_place(message);
898 }
899 Self::ShuttingDown => {}
900 }
901 }
902}
903
904impl ServerText for RestartOutcome {
905 fn redact_server_text(&mut self, redactions: &Redactions) {
906 match self {
907 Self::Restarted {
908 coalesced: _,
909 indexing_state: _,
910 push_notifications_degraded: _,
911 }
912 | Self::Throttled { retry_in_ms: _ }
913 | Self::Initializing => {}
914 Self::Failed { reason } => reason.redact_server_text(redactions),
915 Self::NotRunning { message } => redactions.redact_in_place(message),
916 }
917 }
918}
919
920impl ServerText for ServerRestartEntry {
921 fn redact_server_text(&mut self, redactions: &Redactions) {
922 let Self {
923 server_id: _,
924 outcome,
925 } = self;
926 outcome.redact_server_text(redactions);
927 }
928}
929
930impl ServerText for RestartServerResult {
931 fn redact_server_text(&mut self, redactions: &Redactions) {
932 let Self { servers } = self;
933 servers.redact_server_text(redactions);
934 }
935}
936
937#[cfg(test)]
938mod tests {
939 use std::assert_matches;
940
941 use super::*;
942
943 #[test]
944 fn server_ids_drop_repeats_keep_order_and_reject_empty() {
945 let ids = ServerIds::try_new(vec![
946 ServerId::from_static("b"),
947 ServerId::from_static("a"),
948 ServerId::from_static("b"),
949 ])
950 .unwrap();
951 assert_eq!(
952 ids.as_slice(),
953 [ServerId::from_static("b"), ServerId::from_static("a")]
954 );
955 assert_eq!(ServerIds::try_new(Vec::new()), Err(ServerIdsError::Empty));
956 let ids = |count: usize| {
957 (0..count)
958 .map(|i| ServerId::new(format!("s{i}")).unwrap())
959 .collect()
960 };
961 assert!(ServerIds::try_new(ids(MAX_RESTART_SERVER_IDS)).is_ok());
962 assert_eq!(
963 ServerIds::try_new(ids(MAX_RESTART_SERVER_IDS + 1)),
964 Err(ServerIdsError::TooMany)
965 );
966 assert_eq!(
967 ServerIds::parse(vec![" ".to_owned()]),
968 Err(ServerIdsError::Blank)
969 );
970 assert_eq!(
971 ServerIds::try_new(vec![
972 ServerId::new("x".repeat(MAX_SERVER_ID_BYTES + 1)).unwrap()
973 ]),
974 Err(ServerIdsError::TooLong)
975 );
976 assert_eq!(
977 ServerIds::parse(vec![" ".to_owned(); MAX_RESTART_SERVER_IDS + 1]),
978 Err(ServerIdsError::TooMany)
979 );
980 assert_eq!(
981 ServerIds::parse(vec!["x".repeat(MAX_SERVER_ID_BYTES + 1), " ".to_owned()]),
982 Err(ServerIdsError::Blank)
983 );
984 }
985
986 #[test]
987 fn a_refusal_serializes_with_its_own_kind() {
988 let refused = RestartFailure::Refused {
989 message: "no".to_string(),
990 };
991 assert_eq!(
992 serde_json::to_value(refused).unwrap(),
993 serde_json::json!({"kind": "refused", "message": "no"})
994 );
995 }
996
997 #[test]
998 fn restart_entry_flattens_the_outcome_next_to_the_server_id() {
999 let entry = ServerRestartEntry {
1000 server_id: ServerId::from_static("rust"),
1001 outcome: RestartOutcome::Failed {
1002 reason: RestartFailure::SpawnFailed {
1003 message: "gone".to_string(),
1004 },
1005 },
1006 };
1007 assert_eq!(
1008 serde_json::to_value(entry).unwrap(),
1009 serde_json::json!({
1010 "server_id": "rust",
1011 "status": "failed",
1012 "reason": {"kind": "spawn_failed", "message": "gone"},
1013 })
1014 );
1015 }
1016
1017 #[test]
1018 fn spawn_errors_map_to_spawn_failed_and_the_rest_to_initialize_failed() {
1019 let not_found = Error::ServerNotFound {
1020 command: crate::config::ServerCommand::from_static("x"),
1021 io: std::io::Error::from(std::io::ErrorKind::NotFound),
1022 };
1023 assert_matches!(
1024 RestartFailure::from_error(¬_found),
1025 RestartFailure::SpawnFailed { .. }
1026 );
1027 assert_matches!(
1028 RestartFailure::from_error(&Error::Timeout(Duration::from_secs(5))),
1029 RestartFailure::InitializeFailed { .. }
1030 );
1031 }
1032
1033 #[test]
1034 fn restart_failure_keeps_the_os_cause() {
1035 let spawn = Error::ServerSpawnFailed {
1036 command: crate::config::ServerCommand::from_static("x"),
1037 source: std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied"),
1038 };
1039 let RestartFailure::SpawnFailed { message } = RestartFailure::from_error(&spawn) else {
1040 panic!("expected SpawnFailed");
1041 };
1042 assert_eq!(message, "failed to spawn LSP server 'x': permission denied");
1043 }
1044
1045 #[tokio::test]
1050 async fn test_refused_restart_leaves_the_cooldown_untouched() {
1051 use tokio::sync::Mutex;
1052 use tokio_util::sync::CancellationToken;
1053
1054 use crate::bridge::translator::clock::{Clock, FakeClock};
1055 use crate::bridge::{NotificationCache, WorkspaceRoots};
1056 use crate::config::{LanguageId, ToolRouter};
1057 use crate::mcp::SubscriptionRegistry;
1058 use crate::runtime::pump::{DiagnosticsRoles, PumpShared, PumpWiring};
1059
1060 let dir = tempfile::TempDir::new().unwrap();
1061 let tool = dir.path().join("tool.exe");
1062 std::fs::write(&tool, "").unwrap();
1063 #[cfg(unix)]
1064 {
1065 use std::os::unix::fs::PermissionsExt as _;
1066 std::fs::set_permissions(&tool, std::fs::Permissions::from_mode(0o755)).unwrap();
1067 }
1068 let roots = WorkspaceRoots::from_paths(&[dir.path().to_path_buf()]).unwrap();
1069
1070 let id = ServerId::from_static("rust");
1071 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1072 let clock = Arc::new(FakeClock::new());
1073 let mut translator = Translator::new()
1074 .with_router(ToolRouter::catch_all([(
1075 id.clone(),
1076 LanguageId::from_static("rust"),
1077 )]))
1078 .with_notification_cache(Arc::clone(&cache))
1079 .with_clock(Arc::clone(&clock) as Arc<dyn Clock>);
1080 translator.set_workspace_roots(roots.clone());
1081 translator.register_server_complete(crate::lsp::fake_lsp_server_with_config(
1082 crate::config::LspServerConfig::rust_analyzer(),
1083 ));
1084 translator.install_wiring(Arc::new(PumpWiring::new(
1085 PumpShared {
1086 roles: DiagnosticsRoles::default(),
1087 notification_cache: cache,
1088 subs: SubscriptionRegistry::new(),
1089 workspace_roots: translator.workspace_roots.clone(),
1090 },
1091 CancellationToken::new(),
1092 )));
1093
1094 let init = lock_std(&translator.servers)
1095 .server_mut(&id)
1096 .unwrap()
1097 .init_config()
1098 .clone();
1099 let command = init.server_config().command.server_command().clone();
1100 let mut init = init.with_trust(crate::lsp::SpawnTrust::Untrusted(
1101 crate::lsp::vetting::UntrustedVetting::new(roots, command),
1102 ));
1103 init.server_config_mut().args =
1104 crate::config::ServerArgs::new([tool.to_string_lossy().into_owned()]).unwrap();
1105 lock_std(&translator.servers)
1106 .server_mut(&id)
1107 .unwrap()
1108 .set_init_config(init);
1109
1110 let result = translator
1111 .restart_servers(RestartTarget::All)
1112 .await
1113 .unwrap();
1114
1115 assert_matches!(
1116 &result.servers[0].outcome,
1117 RestartOutcome::Failed {
1118 reason: RestartFailure::Refused { .. }
1119 },
1120 "{result:?}"
1121 );
1122 assert_eq!(translator.restart_cooldown_remaining(&id), None);
1123 }
1124
1125 #[cfg(unix)]
1126 mod restart_tests {
1127 use std::fs;
1128 use std::path::{Path, PathBuf};
1129
1130 use tempfile::TempDir;
1131 use tokio::sync::Mutex;
1132 use tokio_util::sync::CancellationToken;
1133
1134 use super::*;
1135 use crate::bridge::translator::clock::{Clock, FakeClock};
1136 use crate::bridge::translator::testing::{
1137 stub_server_config, write_crash_after_init_script, write_protocol_server_script,
1138 write_responder_script, write_slow_exit_server_script,
1139 };
1140 use crate::bridge::{NotificationCache, WorkspaceRoots};
1141 use crate::config::{LanguageId, ServerCommand, ToolRouter};
1142 use crate::error::{ServerSpawnFailure, StartupFailure};
1143 use crate::mcp::SubscriptionRegistry;
1144 use crate::runtime::pump::{PumpShared, PumpWiring};
1145
1146 struct Fixture {
1147 translator: Arc<Translator>,
1148 cache: Arc<Mutex<NotificationCache>>,
1149 clock: Arc<FakeClock>,
1150 id: ServerId,
1151 dir: TempDir,
1152 }
1153
1154 fn server_ids(names: &[&str]) -> RestartTarget {
1155 RestartTarget::Servers(
1156 ServerIds::try_new(names.iter().map(|n| ServerId::new(*n).unwrap()).collect())
1157 .unwrap(),
1158 )
1159 }
1160
1161 async fn fixture(dir: TempDir, script: &Path, wired: bool) -> Fixture {
1162 let id = ServerId::from_static("rust");
1163 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1164 let clock = Arc::new(FakeClock::new());
1165 let mut translator = Translator::new()
1166 .with_router(ToolRouter::catch_all([(
1167 id.clone(),
1168 LanguageId::from_static("rust"),
1169 )]))
1170 .with_notification_cache(Arc::clone(&cache))
1171 .with_clock(clock.clone());
1172 translator.set_workspace_roots(
1173 WorkspaceRoots::from_paths(&[dir.path().to_path_buf()]).unwrap(),
1174 );
1175
1176 let crate::lsp::SpawnedServer { server, lanes } =
1177 crate::lsp::LspServer::spawn(stub_server_config("rust", script))
1178 .await
1179 .unwrap();
1180 let receivers = NotificationReceivers::new(lanes, None);
1181 translator.register_server_complete(server);
1182
1183 let wiring = PumpWiring::new(
1184 PumpShared {
1185 roles: crate::runtime::pump::DiagnosticsRoles::default(),
1186 notification_cache: Arc::clone(&cache),
1187 subs: SubscriptionRegistry::new(),
1188 workspace_roots: translator.workspace_roots.clone(),
1189 },
1190 CancellationToken::new(),
1191 );
1192 let pump = wiring.spawn_pump(id.clone(), receivers, DiagnosticsRole::Authoritative);
1193 translator.set_notification_task(&id, pump);
1194 if wired {
1195 translator.install_wiring(Arc::new(wiring));
1196 }
1197 Fixture {
1198 translator: Arc::new(translator),
1199 cache,
1200 clock,
1201 id,
1202 dir,
1203 }
1204 }
1205
1206 async fn protocol_fixture(publish: Option<&str>) -> (Fixture, PathBuf) {
1207 let dir = TempDir::new().unwrap();
1208 let log = dir.path().join("server.log");
1209 let script = write_protocol_server_script(dir.path(), &log, publish);
1210 (fixture(dir, &script, true).await, log)
1211 }
1212
1213 fn read_log(log: &Path) -> String {
1214 fs::read_to_string(log).unwrap_or_default()
1215 }
1216
1217 fn only_outcome(result: &RestartServerResult) -> &RestartOutcome {
1218 assert_eq!(result.servers.len(), 1, "{result:?}");
1219 &result.servers[0].outcome
1220 }
1221
1222 impl Fixture {
1223 fn root(&self) -> &Path {
1224 self.dir.path()
1225 }
1226
1227 #[cfg(unix)]
1228 fn vet_arguments_against(&self, boundary: WorkspaceRoots, tool: &Path) {
1229 let init = lock_std(&self.translator.servers)
1230 .server_mut(&self.id)
1231 .unwrap()
1232 .init_config()
1233 .clone();
1234 let command = init.server_config().command.server_command().clone();
1235 let mut init = init.with_trust(crate::lsp::SpawnTrust::Untrusted(
1236 crate::lsp::vetting::UntrustedVetting::new(boundary, command),
1237 ));
1238 let args = init.server_config_mut();
1239 args.args = crate::config::ServerArgs::new(
1240 args.args
1241 .iter()
1242 .cloned()
1243 .chain([tool.to_string_lossy().into_owned()]),
1244 )
1245 .unwrap();
1246 lock_std(&self.translator.servers)
1247 .server_mut(&self.id)
1248 .unwrap()
1249 .set_init_config(init);
1250 }
1251
1252 fn registered(&self) -> bool {
1253 let servers = lock_std(&self.translator.servers);
1254 servers.server(&self.id).is_some() && servers.client(&self.id).is_some()
1255 }
1256
1257 fn expected(&self) -> bool {
1258 lock_std(&self.translator.servers).is_expected(&self.id)
1259 }
1260
1261 fn is_dead(&self) -> bool {
1262 lock_std(&self.translator.servers)
1263 .server_mut(&self.id)
1264 .unwrap()
1265 .is_dead()
1266 .unwrap()
1267 }
1268
1269 async fn wait_until_deregistered(&self) {
1270 tokio::time::timeout(Duration::from_secs(5), async {
1271 while !self.expected() {
1272 tokio::time::sleep(Duration::from_millis(10)).await;
1273 }
1274 })
1275 .await
1276 .expect("the restart must deregister the server");
1277 }
1278 }
1279
1280 #[tokio::test]
1281 async fn test_restart_replaces_the_process_after_a_graceful_stop() {
1282 let (fx, log) = protocol_fixture(None).await;
1283
1284 let result = fx
1285 .translator
1286 .restart_servers(RestartTarget::All)
1287 .await
1288 .unwrap();
1289
1290 assert_matches!(
1291 only_outcome(&result),
1292 RestartOutcome::Restarted {
1293 coalesced: false,
1294 ..
1295 },
1296 "{result:?}"
1297 );
1298 let log = read_log(&log);
1299 assert_eq!(log.matches("started").count(), 2, "{log}");
1300 assert!(log.contains(r#""method":"shutdown""#), "{log}");
1301 assert!(log.contains(r#""method":"exit""#), "{log}");
1302 assert!(fx.registered() && !fx.expected() && !fx.is_dead());
1303 }
1304
1305 #[cfg(unix)]
1308 #[tokio::test]
1309 async fn test_restart_refused_by_the_argument_recheck_keeps_the_old_server_running() {
1310 use std::os::unix::fs::PermissionsExt as _;
1311
1312 let (fx, log) = protocol_fixture(None).await;
1313 let workspace = fx.root().join("ws");
1314 fs::create_dir_all(&workspace).unwrap();
1315 let tool = workspace.join("tool");
1316 fs::write(&tool, "").unwrap();
1317 fx.vet_arguments_against(WorkspaceRoots::from_paths(&[workspace]).unwrap(), &tool);
1318 fx.translator
1319 .restart_servers(RestartTarget::All)
1320 .await
1321 .unwrap();
1322 fx.clock.advance(RESTART_COOLDOWN + Duration::from_secs(1));
1323 fs::set_permissions(&tool, fs::Permissions::from_mode(0o755)).unwrap();
1324
1325 let result = fx
1326 .translator
1327 .restart_servers(RestartTarget::All)
1328 .await
1329 .unwrap();
1330
1331 let RestartOutcome::Failed {
1332 reason: RestartFailure::Refused { message },
1333 } = only_outcome(&result)
1334 else {
1335 panic!("expected a refused restart, got {result:?}");
1336 };
1337 assert!(
1338 message.contains("executable inside the workspace"),
1339 "{message}"
1340 );
1341 let log = read_log(&log);
1342 assert_eq!(log.matches("started").count(), 2, "{log}");
1343 assert!(fx.registered() && !fx.expected() && !fx.is_dead());
1344 }
1345
1346 #[cfg(unix)]
1348 #[tokio::test]
1349 async fn test_refused_restart_does_not_start_the_cooldown() {
1350 use std::os::unix::fs::PermissionsExt as _;
1351
1352 let (fx, _log) = protocol_fixture(None).await;
1353 let workspace = fx.root().join("ws");
1354 fs::create_dir_all(&workspace).unwrap();
1355 let tool = workspace.join("tool");
1356 fs::write(&tool, "").unwrap();
1357 fx.vet_arguments_against(WorkspaceRoots::from_paths(&[workspace]).unwrap(), &tool);
1358 fx.translator
1359 .restart_servers(RestartTarget::All)
1360 .await
1361 .unwrap();
1362 fx.clock.advance(RESTART_COOLDOWN + Duration::from_secs(1));
1363 fs::set_permissions(&tool, fs::Permissions::from_mode(0o755)).unwrap();
1364 let refused = fx
1365 .translator
1366 .restart_servers(RestartTarget::All)
1367 .await
1368 .unwrap();
1369 assert_matches!(
1370 only_outcome(&refused),
1371 RestartOutcome::Failed {
1372 reason: RestartFailure::Refused { .. }
1373 },
1374 "{refused:?}"
1375 );
1376
1377 fs::set_permissions(&tool, fs::Permissions::from_mode(0o644)).unwrap();
1378 let retried = fx
1379 .translator
1380 .restart_servers(RestartTarget::All)
1381 .await
1382 .unwrap();
1383
1384 assert_matches!(
1385 only_outcome(&retried),
1386 RestartOutcome::Restarted { .. },
1387 "{retried:?}"
1388 );
1389 }
1390
1391 #[tokio::test]
1392 async fn test_restart_of_a_wedged_server_completes_despite_the_terminate_error() {
1393 let dir = TempDir::new().unwrap();
1394 let script = write_responder_script(dir.path(), 600);
1395 let fx = fixture(dir, &script, true).await;
1396
1397 let result = fx
1398 .translator
1399 .restart_servers(RestartTarget::All)
1400 .await
1401 .unwrap();
1402
1403 assert_matches!(
1404 only_outcome(&result),
1405 RestartOutcome::Restarted { .. },
1406 "{result:?}"
1407 );
1408 assert!(fx.registered() && !fx.expected() && !fx.is_dead());
1409 }
1410
1411 #[tokio::test]
1412 async fn test_cancelled_restart_restores_the_registry() {
1413 let dir = TempDir::new().unwrap();
1414 let script = write_responder_script(dir.path(), 600);
1415 let fx = fixture(dir, &script, true).await;
1416
1417 let translator = Arc::clone(&fx.translator);
1418 let task =
1419 tokio::spawn(async move { translator.restart_servers(RestartTarget::All).await });
1420 fx.wait_until_deregistered().await;
1421 assert!(!fx.registered());
1422
1423 task.abort();
1424 assert!(task.await.unwrap_err().is_cancelled());
1425
1426 assert!(fx.registered() && !fx.expected());
1427 }
1428
1429 #[tokio::test]
1430 async fn test_restart_of_a_startup_failed_server_reports_not_running() {
1431 let id = ServerId::from_static("rust");
1432 let translator = Translator::new().with_router(ToolRouter::catch_all([(
1433 id.clone(),
1434 LanguageId::from_static("rust"),
1435 )]));
1436 translator.record_startup_failures(&[ServerSpawnFailure {
1437 server_id: id.clone(),
1438 language_id: LanguageId::from_static("rust"),
1439 command: ServerCommand::from_static("missing"),
1440 reason: StartupFailure::InitTaskPanicked,
1441 }]);
1442 translator.rebind_router(&HashSet::new());
1443 translator.clear_expected_servers();
1444
1445 let result = translator
1446 .restart_servers(server_ids(&["rust"]))
1447 .await
1448 .unwrap();
1449
1450 assert_matches!(
1451 only_outcome(&result),
1452 RestartOutcome::NotRunning { .. },
1453 "{result:?}"
1454 );
1455 }
1456
1457 #[tokio::test]
1458 async fn test_restart_all_reports_a_refused_server_as_not_running() {
1459 let config = crate::config::LspServerConfig::rust_analyzer();
1460 let translator =
1461 Translator::new().with_router(ToolRouter::from_configs([&config]).unwrap());
1462 translator.record_refusals(&[ServerSpawnFailure {
1463 server_id: config.id(),
1464 language_id: config.language_id.clone(),
1465 command: config.command.server_command().clone(),
1466 reason: StartupFailure::RefusedUntrustedWorkspace(
1467 crate::error::UntrustedRefusal::NotAllowed { builtin: None },
1468 ),
1469 }]);
1470
1471 let result = translator
1472 .restart_servers(RestartTarget::All)
1473 .await
1474 .unwrap();
1475
1476 let outcome = only_outcome(&result);
1477 assert_matches!(
1478 outcome,
1479 RestartOutcome::NotRunning { message } if message.contains("--allow-server rust"),
1480 "{result:?}"
1481 );
1482 }
1483
1484 #[tokio::test]
1485 async fn test_restart_of_a_settled_server_while_startup_settles_reports_initializing() {
1486 let dir = TempDir::new().unwrap();
1487 let log = dir.path().join("server.log");
1488 let script = write_protocol_server_script(dir.path(), &log, None);
1489 let fx = fixture(dir, &script, true).await;
1490 let startup = fx.translator.begin_startup();
1491
1492 let during = fx
1493 .translator
1494 .restart_servers(server_ids(&["rust"]))
1495 .await
1496 .unwrap();
1497 assert_eq!(*only_outcome(&during), RestartOutcome::Initializing);
1498
1499 drop(startup);
1500 let after = fx
1501 .translator
1502 .restart_servers(server_ids(&["rust"]))
1503 .await
1504 .unwrap();
1505 assert_matches!(
1506 only_outcome(&after),
1507 RestartOutcome::Restarted { .. },
1508 "{after:?}"
1509 );
1510 }
1511
1512 #[tokio::test]
1513 async fn test_restart_permit_classifies_in_order() {
1514 let id = ServerId::from_static("rust");
1515 let translator = Translator::new();
1516 assert_matches!(
1517 translator.restart_permit(&id),
1518 Err(RestartOutcome::NotRunning { .. })
1519 );
1520
1521 translator.set_expected_servers(HashSet::from([id.clone()]));
1522 assert_matches!(
1523 translator.restart_permit(&id),
1524 Err(RestartOutcome::Initializing)
1525 );
1526
1527 translator.clear_expected_servers();
1528 translator.record_startup_failures(&[ServerSpawnFailure {
1529 server_id: id.clone(),
1530 language_id: crate::config::LanguageId::from_static("rust"),
1531 command: ServerCommand::from_static("missing"),
1532 reason: StartupFailure::InitTaskPanicked,
1533 }]);
1534 assert_matches!(
1535 translator.restart_permit(&id),
1536 Err(RestartOutcome::NotRunning { .. })
1537 );
1538
1539 let config = crate::config::LspServerConfig::rust_analyzer();
1540 let running = Translator::new();
1541 running
1542 .register_server_complete(crate::lsp::fake_lsp_server_with_config(config.clone()));
1543 let rid = config.id();
1544 assert_matches!(
1545 running.restart_permit(&rid),
1546 Err(RestartOutcome::Initializing)
1547 );
1548
1549 lock_std(&running.phase).init_panicked();
1550 assert_matches!(
1551 running.restart_permit(&rid),
1552 Err(RestartOutcome::NotRunning { .. })
1553 );
1554
1555 running.begin_shutdown();
1556 assert_matches!(
1557 running.restart_permit(&rid),
1558 Err(RestartOutcome::Failed {
1559 reason: RestartFailure::ShuttingDown
1560 })
1561 );
1562 }
1563
1564 #[test]
1565 fn test_dropping_the_startup_guard_clears_the_flag() {
1566 let translator = Translator::new();
1567 let startup = translator.begin_startup();
1568 assert_eq!(*lock_std(&translator.phase), Phase::Settling);
1569 drop(startup);
1570 assert_eq!(*lock_std(&translator.phase), Phase::Settled);
1571 }
1572
1573 #[tokio::test]
1574 async fn test_restart_of_a_startup_failed_server_while_startup_settles_reports_not_running()
1575 {
1576 let id = ServerId::from_static("rust");
1577 let translator = Translator::new().with_router(ToolRouter::catch_all([(
1578 id.clone(),
1579 LanguageId::from_static("rust"),
1580 )]));
1581 translator.record_startup_failures(&[ServerSpawnFailure {
1582 server_id: id,
1583 language_id: LanguageId::from_static("rust"),
1584 command: ServerCommand::from_static("missing"),
1585 reason: StartupFailure::InitTaskPanicked,
1586 }]);
1587 let _startup = translator.begin_startup();
1588
1589 let result = translator
1590 .restart_servers(server_ids(&["rust"]))
1591 .await
1592 .unwrap();
1593
1594 assert_matches!(
1595 only_outcome(&result),
1596 RestartOutcome::NotRunning { .. },
1597 "{result:?}"
1598 );
1599 }
1600
1601 #[tokio::test]
1602 async fn test_restart_of_a_still_expected_server_reports_initializing() {
1603 let id = ServerId::from_static("rust");
1604 let translator = Translator::new().with_router(ToolRouter::catch_all([(
1605 id.clone(),
1606 LanguageId::from_static("rust"),
1607 )]));
1608 translator.set_expected_servers(HashSet::from([id]));
1609
1610 let result = translator
1611 .restart_servers(server_ids(&["rust"]))
1612 .await
1613 .unwrap();
1614
1615 assert_eq!(*only_outcome(&result), RestartOutcome::Initializing);
1616 }
1617
1618 #[tokio::test]
1619 async fn test_restart_before_the_wiring_is_installed_reports_initializing() {
1620 let dir = TempDir::new().unwrap();
1621 let log = dir.path().join("server.log");
1622 let script = write_protocol_server_script(dir.path(), &log, None);
1623 let fx = fixture(dir, &script, false).await;
1624
1625 let result = fx
1626 .translator
1627 .restart_servers(RestartTarget::All)
1628 .await
1629 .unwrap();
1630
1631 assert_eq!(*only_outcome(&result), RestartOutcome::Initializing);
1632 assert_eq!(read_log(&log).matches("started").count(), 1);
1633 assert!(fx.registered());
1634 }
1635
1636 #[tokio::test]
1637 async fn test_restart_rejects_unknown_ids_and_restarts_nothing() {
1638 let (fx, log) = protocol_fixture(None).await;
1639
1640 let err = fx
1641 .translator
1642 .restart_servers(server_ids(&["rust", "nope"]))
1643 .await
1644 .unwrap_err();
1645
1646 let Error::UnknownServers {
1647 unknown,
1648 configured,
1649 } = err
1650 else {
1651 panic!("expected UnknownServers, got {err:?}");
1652 };
1653 assert_eq!(unknown, [ServerId::from_static("nope")]);
1654 assert_eq!(configured, [ServerId::from_static("rust")]);
1655 assert_eq!(read_log(&log).matches("started").count(), 1);
1656 }
1657
1658 #[tokio::test]
1659 async fn test_restart_is_throttled_until_the_cooldown_elapses() {
1660 let (fx, _log) = protocol_fixture(None).await;
1661 let first = fx
1662 .translator
1663 .restart_servers(RestartTarget::All)
1664 .await
1665 .unwrap();
1666 assert_matches!(only_outcome(&first), RestartOutcome::Restarted { .. });
1667
1668 let second = fx
1669 .translator
1670 .restart_servers(RestartTarget::All)
1671 .await
1672 .unwrap();
1673 let RestartOutcome::Throttled { retry_in_ms } = only_outcome(&second) else {
1674 panic!("expected Throttled, got {second:?}");
1675 };
1676 assert!((1..=5000).contains(retry_in_ms));
1677
1678 fx.clock.advance(RESTART_COOLDOWN + Duration::from_secs(1));
1679 let third = fx
1680 .translator
1681 .restart_servers(RestartTarget::All)
1682 .await
1683 .unwrap();
1684 assert_matches!(only_outcome(&third), RestartOutcome::Restarted { .. });
1685 }
1686
1687 #[tokio::test]
1688 async fn test_concurrent_restarts_of_one_server_coalesce() {
1689 let (fx, log) = protocol_fixture(None).await;
1690
1691 let (a, b) = tokio::join!(
1692 fx.translator.restart_servers(RestartTarget::All),
1693 fx.translator.restart_servers(RestartTarget::All),
1694 );
1695
1696 let outcomes = [a.unwrap(), b.unwrap()];
1697 let coalesced = outcomes
1698 .iter()
1699 .filter(|result| {
1700 matches!(
1701 only_outcome(result),
1702 RestartOutcome::Restarted {
1703 coalesced: true,
1704 ..
1705 }
1706 )
1707 })
1708 .count();
1709 assert_eq!(coalesced, 1, "{outcomes:?}");
1710 assert_eq!(read_log(&log).matches("started").count(), 2);
1711 }
1712
1713 #[tokio::test]
1714 async fn test_restart_clears_degradation_and_keeps_the_early_diagnostics_push() {
1715 let dir = TempDir::new().unwrap();
1716 let file = dunce::canonicalize(dir.path()).unwrap().join("main.rs");
1717 fs::write(&file, "fn main() {}").unwrap();
1718 let uri = crate::bridge::try_path_to_uri(&file).unwrap();
1719 let log = dir.path().join("server.log");
1720 let script = write_protocol_server_script(dir.path(), &log, Some(uri.as_ref()));
1721 let fx = fixture(dir, &script, true).await;
1722 {
1723 let mut cache = fx.cache.lock().await;
1724 cache.mark_push_degraded(&fx.id);
1725 cache.clear_server_diagnostics(&fx.id);
1726 }
1727
1728 let result = fx
1729 .translator
1730 .restart_servers(RestartTarget::All)
1731 .await
1732 .unwrap();
1733
1734 assert_matches!(
1735 only_outcome(&result),
1736 RestartOutcome::Restarted {
1737 push_notifications_degraded: false,
1738 ..
1739 },
1740 "{result:?}"
1741 );
1742 tokio::time::timeout(Duration::from_secs(5), async {
1743 while !fx.cache.lock().await.has_diagnostics(&uri) {
1744 tokio::time::sleep(Duration::from_millis(20)).await;
1745 }
1746 })
1747 .await
1748 .expect("the diagnostics pushed during the new handshake must reach the cache");
1749 }
1750
1751 #[tokio::test]
1752 async fn test_restart_fails_requests_in_flight_with_server_restarted() {
1753 let (fx, _log) = protocol_fixture(None).await;
1754 let client = lock_std(&fx.translator.servers).client(&fx.id).unwrap();
1755 let in_flight = tokio::spawn(async move {
1756 client
1757 .request::<_, serde_json::Value>(
1758 "textDocument/hover",
1759 serde_json::json!({}),
1760 Duration::from_secs(30),
1761 )
1762 .await
1763 });
1764 tokio::time::sleep(Duration::from_millis(300)).await;
1765
1766 fx.translator
1767 .restart_servers(RestartTarget::All)
1768 .await
1769 .unwrap();
1770
1771 let failed = tokio::time::timeout(Duration::from_secs(5), in_flight)
1772 .await
1773 .expect("the in-flight request must fail promptly")
1774 .unwrap();
1775 assert_matches!(&failed, Err(Error::ServerRestarted { server_id }) if *server_id == fx.id, "{failed:?}");
1776 }
1777
1778 #[tokio::test]
1779 async fn test_restart_during_shutdown_starts_nothing() {
1780 let (fx, log) = protocol_fixture(None).await;
1781 fx.translator.begin_shutdown();
1782
1783 let result = fx
1784 .translator
1785 .restart_servers(RestartTarget::All)
1786 .await
1787 .unwrap();
1788
1789 assert_eq!(
1790 *only_outcome(&result),
1791 RestartOutcome::Failed {
1792 reason: RestartFailure::ShuttingDown
1793 }
1794 );
1795 assert_eq!(read_log(&log).matches("started").count(), 1);
1796 assert!(fx.registered());
1797 }
1798
1799 #[tokio::test]
1800 async fn test_shutdown_beginning_while_the_old_server_stops_prevents_the_respawn() {
1801 let dir = TempDir::new().unwrap();
1802 let script = write_responder_script(dir.path(), 600);
1803 let fx = fixture(dir, &script, true).await;
1804
1805 let translator = Arc::clone(&fx.translator);
1806 let task =
1807 tokio::spawn(async move { translator.restart_servers(RestartTarget::All).await });
1808 fx.wait_until_deregistered().await;
1809 fx.translator.begin_shutdown();
1810
1811 let result = task.await.unwrap().unwrap();
1812
1813 assert_eq!(
1814 *only_outcome(&result),
1815 RestartOutcome::Failed {
1816 reason: RestartFailure::ShuttingDown
1817 }
1818 );
1819 assert!(fx.registered() && !fx.expected() && fx.is_dead());
1820 }
1821
1822 #[tokio::test]
1823 async fn test_failed_replacement_leaves_the_server_registered_and_reports_spawn_failed() {
1824 let dir = TempDir::new().unwrap();
1825 let log = dir.path().join("server.log");
1826 let script = write_protocol_server_script(dir.path(), &log, None);
1827 let fx = fixture(dir, &script, true).await;
1828 let mut broken = stub_server_config("rust", &script);
1829 broken.server_config_mut().command =
1830 ServerCommand::from_static("mcpls-test-missing-server").into();
1831 lock_std(&fx.translator.servers)
1832 .server_mut(&fx.id)
1833 .unwrap()
1834 .set_init_config(broken);
1835
1836 let result = fx
1837 .translator
1838 .restart_servers(RestartTarget::All)
1839 .await
1840 .unwrap();
1841
1842 assert_matches!(
1843 only_outcome(&result),
1844 RestartOutcome::Failed {
1845 reason: RestartFailure::SpawnFailed { .. }
1846 },
1847 "{result:?}"
1848 );
1849 assert!(fx.registered() && !fx.expected() && fx.is_dead());
1850 }
1851
1852 #[tokio::test]
1853 async fn test_take_leaves_the_server_expected_in_the_same_step_and_restore_runs_it_again() {
1854 let config = crate::config::LspServerConfig::rust_analyzer();
1855 let id = config.id();
1856 let translator = Translator::new();
1857 translator.register_server_complete(crate::lsp::fake_lsp_server_with_config(config));
1858
1859 let held = Deregistered::take(&translator, &id).unwrap();
1860 {
1861 let servers = lock_std(&translator.servers);
1862 assert!(
1863 servers.is_expected(&id),
1864 "the id must never be in neither state"
1865 );
1866 assert!(servers.server(&id).is_none());
1867 drop(servers);
1868 }
1869 drop(held);
1870
1871 let servers = lock_std(&translator.servers);
1872 assert!(!servers.is_expected(&id));
1873 assert!(servers.server(&id).is_some());
1874 drop(servers);
1875 }
1876
1877 #[tokio::test]
1881 async fn test_overlapping_requests_over_more_than_four_servers_restart_each_once() {
1882 let dir = TempDir::new().unwrap();
1883 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1884 let mut translator = Translator::new().with_notification_cache(Arc::clone(&cache));
1885 translator.set_workspace_roots(
1886 WorkspaceRoots::from_paths(&[dir.path().to_path_buf()]).unwrap(),
1887 );
1888 let wiring = PumpWiring::new(
1889 PumpShared {
1890 roles: crate::runtime::pump::DiagnosticsRoles::default(),
1891 notification_cache: Arc::clone(&cache),
1892 subs: SubscriptionRegistry::new(),
1893 workspace_roots: translator.workspace_roots.clone(),
1894 },
1895 CancellationToken::new(),
1896 );
1897 let mut logs = Vec::new();
1898 for index in 0..6 {
1899 let name = format!("s{index}");
1900 let log = dir.path().join(format!("{name}.log"));
1901 let script_dir = dir.path().join(&name);
1902 fs::create_dir(&script_dir).unwrap();
1903 let script = write_protocol_server_script(&script_dir, &log, None);
1904 let crate::lsp::SpawnedServer { server, lanes } =
1905 crate::lsp::LspServer::spawn(stub_server_config(&name, &script))
1906 .await
1907 .unwrap();
1908 let receivers = NotificationReceivers::new(lanes, None);
1909 translator.register_server_complete(server);
1910 let id = ServerId::new(name.as_str()).unwrap();
1911 let pump = wiring.spawn_pump(id.clone(), receivers, DiagnosticsRole::Secondary);
1912 translator.set_notification_task(&id, pump);
1913 logs.push(log);
1914 }
1915 translator.install_wiring(Arc::new(wiring));
1916
1917 let (a, b) = tokio::join!(
1918 translator.restart_servers(RestartTarget::All),
1919 translator.restart_servers(RestartTarget::All),
1920 );
1921 let (a, b) = (a.unwrap(), b.unwrap());
1922
1923 assert_eq!((a.servers.len(), b.servers.len()), (6, 6));
1924 for (first, second) in a.servers.iter().zip(&b.servers) {
1925 let coalesced = |entry: &ServerRestartEntry| match entry.outcome {
1926 RestartOutcome::Restarted { coalesced, .. } => coalesced,
1927 ref other => panic!("{}: unexpected outcome {other:?}", entry.server_id),
1928 };
1929 assert_ne!(
1930 coalesced(first),
1931 coalesced(second),
1932 "{}: exactly one request restarts it",
1933 first.server_id
1934 );
1935 }
1936 for log in logs {
1937 assert_eq!(read_log(&log).matches("started").count(), 2, "{log:?}");
1938 }
1939 }
1940
1941 #[tokio::test]
1942 async fn test_an_automatic_respawn_does_not_count_towards_coalescing() {
1943 let dir = TempDir::new().unwrap();
1944 let script = write_crash_after_init_script(dir.path());
1945 let fx = fixture(dir, &script, true).await;
1946 tokio::time::timeout(Duration::from_secs(3), async {
1947 while !fx.is_dead() {
1948 tokio::time::sleep(Duration::from_millis(20)).await;
1949 }
1950 })
1951 .await
1952 .expect("the seed server crashes after initialize");
1953 let before = fx.translator.restart_generation(&fx.id);
1954 fx.translator.respawn_if_dead(&fx.id).await.unwrap();
1955 assert_eq!(fx.translator.restart_generation(&fx.id), before);
1956
1957 let result = fx
1958 .translator
1959 .restart_servers(RestartTarget::All)
1960 .await
1961 .unwrap();
1962
1963 assert_matches!(
1964 only_outcome(&result),
1965 RestartOutcome::Restarted {
1966 coalesced: false,
1967 ..
1968 },
1969 "{result:?}"
1970 );
1971 }
1972
1973 #[tokio::test]
1974 async fn test_an_unregistered_server_is_never_throttled() {
1975 let id = ServerId::from_static("rust");
1976 let clock = Arc::new(FakeClock::new());
1977 let translator = Translator::new().with_clock(clock.clone());
1978 translator.set_expected_servers(HashSet::from([id.clone()]));
1979 lock_std(&translator.servers)
1980 .get_mut(&id)
1981 .unwrap()
1982 .restart
1983 .last_attempt = Some(clock.now());
1984 let starting = translator
1985 .restart_servers(server_ids(&["rust"]))
1986 .await
1987 .unwrap();
1988 assert_eq!(*only_outcome(&starting), RestartOutcome::Initializing);
1989
1990 translator.clear_expected_servers();
1991 translator.record_startup_failures(&[ServerSpawnFailure {
1992 server_id: id,
1993 language_id: LanguageId::from_static("rust"),
1994 command: ServerCommand::from_static("missing"),
1995 reason: StartupFailure::InitTaskPanicked,
1996 }]);
1997 let failed = translator
1998 .restart_servers(server_ids(&["rust"]))
1999 .await
2000 .unwrap();
2001 assert_matches!(
2002 only_outcome(&failed),
2003 RestartOutcome::NotRunning { .. },
2004 "{failed:?}"
2005 );
2006 }
2007
2008 #[tokio::test]
2009 async fn test_restart_after_an_init_panic_without_wiring_reports_not_running() {
2010 let dir = TempDir::new().unwrap();
2011 let log = dir.path().join("server.log");
2012 let script = write_protocol_server_script(dir.path(), &log, None);
2013 let fx = fixture(dir, &script, false).await;
2014 fx.translator.settle_after_init_panic(&[]).await;
2015
2016 let result = fx
2017 .translator
2018 .restart_servers(RestartTarget::All)
2019 .await
2020 .unwrap();
2021
2022 assert_matches!(
2023 only_outcome(&result),
2024 RestartOutcome::NotRunning { .. },
2025 "{result:?}"
2026 );
2027 }
2028
2029 #[tokio::test]
2030 async fn test_restart_after_an_init_panic_with_wiring_installed_reports_not_running() {
2031 let dir = TempDir::new().unwrap();
2032 let log = dir.path().join("server.log");
2033 let script = write_protocol_server_script(dir.path(), &log, None);
2034 let fx = fixture(dir, &script, true).await;
2035 fx.translator.settle_after_init_panic(&[]).await;
2036
2037 let result = fx
2038 .translator
2039 .restart_servers(RestartTarget::All)
2040 .await
2041 .unwrap();
2042
2043 assert_matches!(
2044 only_outcome(&result),
2045 RestartOutcome::NotRunning { .. },
2046 "{result:?}"
2047 );
2048 }
2049
2050 #[tokio::test]
2051 async fn test_a_failed_restart_invalidates_the_stopped_servers_cached_diagnostics() {
2052 let dir = TempDir::new().unwrap();
2053 let file = dunce::canonicalize(dir.path()).unwrap().join("main.rs");
2054 fs::write(&file, "fn main() {}").unwrap();
2055 let uri = crate::bridge::try_path_to_uri(&file).unwrap();
2056 let log = dir.path().join("server.log");
2057 let script = write_protocol_server_script(dir.path(), &log, Some(uri.as_ref()));
2058 let fx = fixture(dir, &script, true).await;
2059 tokio::time::timeout(Duration::from_secs(5), async {
2060 while !fx.cache.lock().await.has_diagnostics(&uri) {
2061 tokio::time::sleep(Duration::from_millis(20)).await;
2062 }
2063 })
2064 .await
2065 .expect("the seed server's diagnostics reach the cache");
2066 let mut broken = stub_server_config("rust", &script);
2067 broken.server_config_mut().command =
2068 ServerCommand::from_static("mcpls-test-missing-server").into();
2069 lock_std(&fx.translator.servers)
2070 .server_mut(&fx.id)
2071 .unwrap()
2072 .set_init_config(broken);
2073
2074 let result = fx
2075 .translator
2076 .restart_servers(RestartTarget::All)
2077 .await
2078 .unwrap();
2079
2080 assert_matches!(only_outcome(&result), RestartOutcome::Failed { .. });
2081 let (stale, degraded) = {
2082 let cache = fx.cache.lock().await;
2083 (cache.has_diagnostics(&uri), cache.is_push_degraded(&fx.id))
2084 };
2085 assert!(!stale, "stale diagnostics must go");
2086 assert!(degraded);
2087 assert!(
2088 lock_std(&fx.translator.servers)
2089 .get(&fx.id)
2090 .is_none_or(|slot| slot.notification_task.is_none()),
2091 "the dead server's pump handle must be dropped"
2092 );
2093 }
2094
2095 #[tokio::test]
2096 async fn test_cooldown_counts_from_the_completion_of_a_slow_restart() {
2097 let dir = TempDir::new().unwrap();
2098 let script = write_responder_script(dir.path(), 600);
2099 let fx = fixture(dir, &script, true).await;
2100 let translator = Arc::clone(&fx.translator);
2101 let restart =
2102 tokio::spawn(async move { translator.restart_servers(RestartTarget::All).await });
2103 fx.wait_until_deregistered().await;
2104 fx.clock.advance(Duration::from_secs(30));
2105
2106 restart.await.unwrap().unwrap();
2107 let again = fx
2108 .translator
2109 .restart_servers(RestartTarget::All)
2110 .await
2111 .unwrap();
2112
2113 assert_matches!(
2114 only_outcome(&again),
2115 RestartOutcome::Throttled { .. },
2116 "{again:?}"
2117 );
2118 }
2119
2120 #[tokio::test]
2121 async fn test_a_request_queued_behind_a_discarded_restart_is_not_reported_coalesced() {
2122 let dir = TempDir::new().unwrap();
2123 let script = write_responder_script(dir.path(), 600);
2124 let fx = fixture(dir, &script, true).await;
2125 let (first_tx, second_tx) = (Arc::clone(&fx.translator), Arc::clone(&fx.translator));
2126 let first =
2127 tokio::spawn(async move { first_tx.restart_servers(RestartTarget::All).await });
2128 fx.wait_until_deregistered().await;
2129 let second =
2130 tokio::spawn(async move { second_tx.restart_servers(RestartTarget::All).await });
2131 tokio::time::sleep(Duration::from_millis(100)).await;
2132 fx.translator.begin_shutdown();
2133
2134 for result in [
2135 first.await.unwrap().unwrap(),
2136 second.await.unwrap().unwrap(),
2137 ] {
2138 assert_eq!(
2139 *only_outcome(&result),
2140 RestartOutcome::Failed {
2141 reason: RestartFailure::ShuttingDown
2142 }
2143 );
2144 }
2145 }
2146
2147 #[tokio::test]
2150 async fn test_a_healthy_server_slow_to_exit_is_not_killed_at_the_handshake_budget() {
2151 let dir = TempDir::new().unwrap();
2152 let log = dir.path().join("server.log");
2153 let delay = u32::try_from(RESTART_HANDSHAKE_BUDGET.as_secs()).unwrap() + 1;
2154 let script = write_slow_exit_server_script(dir.path(), &log, delay);
2155 let fx = fixture(dir, &script, true).await;
2156
2157 let result = fx
2158 .translator
2159 .restart_servers(RestartTarget::All)
2160 .await
2161 .unwrap();
2162
2163 assert_matches!(
2164 only_outcome(&result),
2165 RestartOutcome::Restarted { .. },
2166 "{result:?}"
2167 );
2168 assert!(
2169 read_log(&log).contains("exiting"),
2170 "the old server must have exited by itself, not been killed"
2171 );
2172 }
2173
2174 #[tokio::test]
2175 async fn test_restart_recovers_a_crashed_server() {
2176 let dir = TempDir::new().unwrap();
2177 let script = write_crash_after_init_script(dir.path());
2178 let fx = fixture(dir, &script, true).await;
2179 tokio::time::timeout(Duration::from_secs(3), async {
2180 while !fx.is_dead() {
2181 tokio::time::sleep(Duration::from_millis(20)).await;
2182 }
2183 })
2184 .await
2185 .expect("the seed server crashes after initialize");
2186
2187 let result = fx
2188 .translator
2189 .restart_servers(RestartTarget::All)
2190 .await
2191 .unwrap();
2192
2193 assert_matches!(
2194 only_outcome(&result),
2195 RestartOutcome::Restarted { .. },
2196 "{result:?}"
2197 );
2198 }
2199
2200 #[tokio::test]
2204 async fn test_restart_of_a_signalling_server_gates_until_its_signal() {
2205 let (fx, _log) = protocol_fixture(None).await;
2206 fx.cache.lock().await.observe_indexing_signal(
2207 &fx.id,
2208 "experimental/serverStatus",
2209 Some(&serde_json::json!({"quiescent": true})),
2210 );
2211
2212 let result = fx
2213 .translator
2214 .restart_servers(RestartTarget::All)
2215 .await
2216 .unwrap();
2217 assert_matches!(
2218 only_outcome(&result),
2219 RestartOutcome::Restarted {
2220 indexing_state: IndexingState::Loading,
2221 ..
2222 },
2223 "{result:?}"
2224 );
2225
2226 let translator = Arc::clone(&fx.translator);
2227 let id = fx.id.clone();
2228 let gated = tokio::spawn(async move { translator.wait_for_indexing_ready(&id).await });
2229 tokio::time::sleep(Duration::from_millis(150)).await;
2230 assert!(!gated.is_finished(), "the gate must hold before the signal");
2231
2232 fx.cache.lock().await.observe_indexing_signal(
2233 &fx.id,
2234 "experimental/serverStatus",
2235 Some(&serde_json::json!({"quiescent": true})),
2236 );
2237 tokio::time::timeout(Duration::from_secs(5), gated)
2238 .await
2239 .expect("the gate releases once the replacement is ready")
2240 .unwrap()
2241 .unwrap();
2242 }
2243
2244 #[tokio::test]
2247 async fn test_restart_of_a_silent_server_reports_unknown() {
2248 let (fx, _log) = protocol_fixture(None).await;
2249
2250 let result = fx
2251 .translator
2252 .restart_servers(RestartTarget::All)
2253 .await
2254 .unwrap();
2255
2256 assert_matches!(
2257 only_outcome(&result),
2258 RestartOutcome::Restarted {
2259 indexing_state: IndexingState::Unknown,
2260 ..
2261 },
2262 "{result:?}"
2263 );
2264 fx.translator.wait_for_indexing_ready(&fx.id).await.unwrap();
2265 }
2266 }
2267}
2268
2269#[cfg(test)]
2270mod server_text_tests {
2271 use super::*;
2272
2273 #[test]
2274 fn test_restart_messages_are_redacted() {
2275 let secret = "SuperSecretValue123";
2276 let set = Redactions::new([("API_TOKEN".to_owned(), secret.to_owned())]);
2277 let mut result = RestartServerResult {
2278 servers: vec![
2279 ServerRestartEntry {
2280 server_id: ServerId::from_static("a"),
2281 outcome: RestartOutcome::Failed {
2282 reason: RestartFailure::SpawnFailed {
2283 message: format!("spawn {secret}"),
2284 },
2285 },
2286 },
2287 ServerRestartEntry {
2288 server_id: ServerId::from_static("b"),
2289 outcome: RestartOutcome::Failed {
2290 reason: RestartFailure::InitializeFailed {
2291 message: format!("init {secret}"),
2292 },
2293 },
2294 },
2295 ServerRestartEntry {
2296 server_id: ServerId::from_static("c"),
2297 outcome: RestartOutcome::NotRunning {
2298 message: format!("gone {secret}"),
2299 },
2300 },
2301 ],
2302 };
2303
2304 result.redact_server_text(&set);
2305
2306 let json = serde_json::to_string(&result).unwrap();
2307 assert!(!json.contains(secret), "{json}");
2308 assert_eq!(json.matches("[redacted:API_TOKEN]").count(), 3, "{json}");
2309 }
2310}