Limit language server reinstallation attempts (#3177)

Julia created

Release Notes:

- Fixed a case where a sufficiently broken language server system
configuration could cause an infinite repeated server reinstallation,
consuming resources indefinitely.

Change summary

crates/language/src/language.rs |  4 ++
crates/project/src/project.rs   | 48 ++++++++++++++++++++++------------
2 files changed, 34 insertions(+), 18 deletions(-)

Detailed changes

crates/language/src/language.rs 🔗

@@ -38,7 +38,7 @@ use std::{
     path::{Path, PathBuf},
     str,
     sync::{
-        atomic::{AtomicUsize, Ordering::SeqCst},
+        atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
         Arc,
     },
 };
@@ -115,6 +115,7 @@ pub struct CachedLspAdapter {
     pub disk_based_diagnostics_progress_token: Option<String>,
     pub language_ids: HashMap<String, String>,
     pub adapter: Arc<dyn LspAdapter>,
+    pub reinstall_attempt_count: AtomicU64,
 }
 
 impl CachedLspAdapter {
@@ -133,6 +134,7 @@ impl CachedLspAdapter {
             disk_based_diagnostics_progress_token,
             language_ids,
             adapter,
+            reinstall_attempt_count: AtomicU64::new(0),
         })
     }
 

crates/project/src/project.rs 🔗

@@ -91,6 +91,8 @@ pub use fs::*;
 pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
 pub use worktree::*;
 
+const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
+
 pub trait Item {
     fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
@@ -2722,6 +2724,10 @@ impl Project {
         language: Arc<Language>,
         cx: &mut ModelContext<Self>,
     ) {
+        if adapter.reinstall_attempt_count.load(SeqCst) > MAX_SERVER_REINSTALL_ATTEMPT_COUNT {
+            return;
+        }
+
         let key = (worktree_id, adapter.name.clone());
         if self.language_server_ids.contains_key(&key) {
             return;
@@ -2772,27 +2778,35 @@ impl Project {
                     }
 
                     Err(err) => {
-                        log::error!("failed to start language server {:?}: {}", server_name, err);
+                        log::error!("failed to start language server {server_name:?}: {err}");
                         log::error!("server stderr: {:?}", stderr_capture.lock().take());
 
-                        if let Some(this) = this.upgrade(&cx) {
-                            if let Some(container_dir) = container_dir {
-                                let installation_test_binary = adapter
-                                    .installation_test_binary(container_dir.to_path_buf())
-                                    .await;
-
-                                this.update(&mut cx, |_, cx| {
-                                    Self::check_errored_server(
-                                        language,
-                                        adapter,
-                                        server_id,
-                                        installation_test_binary,
-                                        cx,
-                                    )
-                                });
-                            }
+                        let this = this.upgrade(&cx)?;
+                        let container_dir = container_dir?;
+
+                        let attempt_count = adapter.reinstall_attempt_count.fetch_add(1, SeqCst);
+                        if attempt_count >= MAX_SERVER_REINSTALL_ATTEMPT_COUNT {
+                            let max = MAX_SERVER_REINSTALL_ATTEMPT_COUNT;
+                            log::error!(
+                                "Hit {max} max reinstallation attempts for {server_name:?}"
+                            );
+                            return None;
                         }
 
+                        let installation_test_binary = adapter
+                            .installation_test_binary(container_dir.to_path_buf())
+                            .await;
+
+                        this.update(&mut cx, |_, cx| {
+                            Self::check_errored_server(
+                                language,
+                                adapter,
+                                server_id,
+                                installation_test_binary,
+                                cx,
+                            )
+                        });
+
                         None
                     }
                 }