From 9c203477e0201c8a711d949655b7308e497abb84 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 5 Mar 2026 00:05:57 +0100 Subject: [PATCH] Handle project's node processes when releasing a Project --- crates/copilot/src/copilot.rs | 2 +- crates/edit_prediction/src/edit_prediction.rs | 8 ++- crates/project/src/context_server_store.rs | 7 +++ crates/project/src/lsp_store.rs | 17 +++++- crates/project/src/project.rs | 22 ++++++- .../tests/integration/project_tests.rs | 58 +++++++++++++++++++ 6 files changed, 110 insertions(+), 4 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 179e217d207554bcf226ce905aa9226c1c334b72..b0ebb316fe3650a27a81c79a76c2ecc203442c70 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -433,7 +433,7 @@ impl Copilot { this } - fn shutdown_language_server( + pub fn shutdown_language_server( &mut self, _cx: &mut Context, ) -> impl Future + use<> { diff --git a/crates/edit_prediction/src/edit_prediction.rs b/crates/edit_prediction/src/edit_prediction.rs index 5c7ce045121739f341b84dd87d827878550f4048..b35c7b05dfbf5539b20bf3209149824657a706bb 100644 --- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -1037,7 +1037,13 @@ impl EditPredictionStore { _subscriptions: [ cx.subscribe(&project, Self::handle_project_event), cx.observe_release(&project, move |this, _, cx| { - this.projects.remove(&entity_id); + if let Some(state) = this.projects.remove(&entity_id) { + if let Some(copilot) = state.copilot { + let shutdown = copilot + .update(cx, |copilot, cx| copilot.shutdown_language_server(cx)); + cx.background_spawn(shutdown).detach(); + } + } cx.notify(); }), ], diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index 88dc64fcbe8795ae4826dcaa2813744f525b9258..cf713a2fbb20a0a723f4ce1c3054913768563f6c 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -560,6 +560,13 @@ impl ContextServerStore { result } + pub fn stop_all_servers(&mut self, cx: &mut Context) { + let server_ids: Vec<_> = self.servers.keys().cloned().collect(); + for id in server_ids { + self.stop_server(&id, cx).log_err(); + } + } + fn run_server( &mut self, server: Arc, diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 75f9702e12cf31ce4f555940d7d1918884bbc22a..20e17f1718825f560101b7654b46b6f713f46f82 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -1310,15 +1310,22 @@ impl LocalLspStore { clangd_ext::register_notifications(lsp_store, language_server, adapter); } - fn shutdown_language_servers_on_quit(&mut self) -> impl Future + use<> { + pub(crate) fn shutdown_language_servers_on_quit(&mut self) -> impl Future + use<> { let shutdown_futures = self .language_servers .drain() .map(|(_, server_state)| Self::shutdown_server(server_state)) .collect::>(); + let supplementary_shutdown_futures = self + .supplementary_language_servers + .drain() + .filter_map(|(_, (_, server))| server.shutdown()) + .collect::>(); + async move { join_all(shutdown_futures).await; + join_all(supplementary_shutdown_futures).await; } } @@ -10858,12 +10865,20 @@ impl LspStore { .map(|state| state.id) .collect(); local.lsp_tree.remove_nodes(&language_servers_to_stop); + + let supplementary_shutdown_futures: Vec<_> = local + .supplementary_language_servers + .drain() + .filter_map(|(_, (_, server))| server.shutdown()) + .collect(); + let tasks = language_servers_to_stop .into_iter() .map(|server| self.stop_local_language_server(server, cx)) .collect::>(); cx.background_spawn(async move { futures::future::join_all(tasks).await; + futures::future::join_all(supplementary_shutdown_futures).await; }) } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 756f095511a9688678df013458710e69d720c52e..42339bc3fefbd66e08aeb01ef27518fee24b886b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1928,9 +1928,29 @@ impl Project { .detach() } + let process_shutdown = |this: &mut Project, cx: &mut App| { + this.lsp_store.update(cx, |lsp_store, cx| { + if let Some(local) = lsp_store.as_local_mut() { + let shutdown = local.shutdown_language_servers_on_quit(); + cx.background_spawn(shutdown).detach(); + } + }); + + this.dap_store.update(cx, |dap_store, cx| { + dap_store.shutdown_sessions(cx).detach(); + }); + + this.context_server_store.update(cx, |store, cx| { + store.stop_all_servers(cx); + }); + }; + match &self.client_state { - ProjectClientState::Local => {} + ProjectClientState::Local => { + process_shutdown(self, cx); + } ProjectClientState::Shared { .. } => { + process_shutdown(self, cx); let _ = self.unshare_internal(cx); } ProjectClientState::Remote { remote_id, .. } => { diff --git a/crates/project/tests/integration/project_tests.rs b/crates/project/tests/integration/project_tests.rs index d86b969e61ed173ee314cde6f584f2dbab6859f9..814764429ff8de10b1152f61baba97cc0b0931f0 100644 --- a/crates/project/tests/integration/project_tests.rs +++ b/crates/project/tests/integration/project_tests.rs @@ -11320,6 +11320,64 @@ async fn test_undo_encoding_change(cx: &mut gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_language_servers_shut_down_on_project_release(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/project"), + json!({ + ".git": {}, + "main.rs": "fn main() {}" + }), + ) + .await; + + let project = Project::test(fs, [path!("/project").as_ref()], cx).await; + + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + language_registry.add(rust_lang()); + let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default()); + + let (buffer, _handle) = project + .update(cx, |project, cx| { + project.open_local_buffer_with_lsp(path!("/project/main.rs"), cx) + }) + .await + .unwrap(); + + let mut fake_server = fake_servers.next().await.unwrap(); + cx.executor().run_until_parked(); + + // Verify the server is running. + project.read_with(cx, |project, cx| { + assert!( + project.language_server_statuses(cx).count() > 0, + "language server should be running" + ); + }); + + let held_server = project.read_with(cx, |project, cx| { + let lsp_store = project.lsp_store().read(cx); + let (server_id, _status) = lsp_store.language_server_statuses().next().unwrap(); + lsp_store.language_server_for_id(server_id).unwrap() + }); + + drop(_handle); + drop(buffer); + drop(project); + + cx.update(|_| {}); + cx.executor().run_until_parked(); + + fake_server + .receive_notification::() + .await; + + drop(held_server); +} + pub fn init_test(cx: &mut gpui::TestAppContext) { zlog::init_test();