Stop extensions' servers and message loops before removing their files (#34208)

Fixes an issue that caused Windows to fail when removing extension's
directories, as Zed had never stop any related processes.

Now:

* Zed shuts down and waits until the end when the language servers are
shut down

* Adds `impl Drop for WasmExtension` where does
`self.tx.close_channel();` to stop a receiver loop that holds the "lock"
on the extension's work dir.
The extension was dropped, but the channel was not closed for some
reason.

* Does more unregistration to ensure `Arc<WasmExtension>` with the `tx`
does not leak further

* Tidies up the related errors which had never reported a problematic
path before

Release Notes:

- N/A

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Co-authored-by: Smit <smit@zed.dev>
This commit is contained in:
Kirill Bulatov 2025-07-10 22:25:10 +03:00 committed by GitHub
parent c549b712fd
commit c6603e4fba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 273 additions and 84 deletions

View file

@ -21,6 +21,7 @@ fs.workspace = true
gpui.workspace = true
language.workspace = true
lsp.workspace = true
project.workspace = true
serde.workspace = true
serde_json.workspace = true
util.workspace = true

View file

@ -6,21 +6,24 @@ use std::sync::Arc;
use anyhow::{Context as _, Result};
use async_trait::async_trait;
use collections::HashMap;
use collections::{HashMap, HashSet};
use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate};
use fs::Fs;
use futures::{Future, FutureExt};
use gpui::AsyncApp;
use futures::{Future, FutureExt, future::join_all};
use gpui::{App, AppContext, AsyncApp, Task};
use language::{
BinaryStatus, CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore,
LspAdapter, LspAdapterDelegate,
};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
use lsp::{
CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName,
LanguageServerSelector,
};
use serde::Serialize;
use serde_json::Value;
use util::{ResultExt, fs::make_file_executable, maybe};
use crate::LanguageServerRegistryProxy;
use crate::{LanguageServerRegistryProxy, LspAccess};
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>);
@ -71,10 +74,50 @@ impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
fn remove_language_server(
&self,
language: &LanguageName,
language_server_id: &LanguageServerName,
) {
language_server_name: &LanguageServerName,
cx: &mut App,
) -> Task<Result<()>> {
self.language_registry
.remove_lsp_adapter(language, language_server_id);
.remove_lsp_adapter(language, language_server_name);
let mut tasks = Vec::new();
match &self.lsp_access {
LspAccess::ViaLspStore(lsp_store) => lsp_store.update(cx, |lsp_store, cx| {
let stop_task = lsp_store.stop_language_servers_for_buffers(
Vec::new(),
HashSet::from_iter([LanguageServerSelector::Name(
language_server_name.clone(),
)]),
cx,
);
tasks.push(stop_task);
}),
LspAccess::ViaWorkspaces(lsp_store_provider) => {
if let Ok(lsp_stores) = lsp_store_provider(cx) {
for lsp_store in lsp_stores {
lsp_store.update(cx, |lsp_store, cx| {
let stop_task = lsp_store.stop_language_servers_for_buffers(
Vec::new(),
HashSet::from_iter([LanguageServerSelector::Name(
language_server_name.clone(),
)]),
cx,
);
tasks.push(stop_task);
});
}
}
}
LspAccess::Noop => {}
}
cx.background_spawn(async move {
let results = join_all(tasks).await;
for result in results {
result?;
}
Ok(())
})
}
fn update_language_server_status(

View file

@ -5,13 +5,26 @@ use std::sync::Arc;
use anyhow::Result;
use extension::{ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageProxy};
use gpui::{App, Entity};
use language::{LanguageMatcher, LanguageName, LanguageRegistry, LoadedLanguage};
use project::LspStore;
#[derive(Clone)]
pub enum LspAccess {
ViaLspStore(Entity<LspStore>),
ViaWorkspaces(Arc<dyn Fn(&mut App) -> Result<Vec<Entity<LspStore>>> + Send + Sync + 'static>),
Noop,
}
pub fn init(
lsp_access: LspAccess,
extension_host_proxy: Arc<ExtensionHostProxy>,
language_registry: Arc<LanguageRegistry>,
) {
let language_server_registry_proxy = LanguageServerRegistryProxy { language_registry };
let language_server_registry_proxy = LanguageServerRegistryProxy {
language_registry,
lsp_access,
};
extension_host_proxy.register_grammar_proxy(language_server_registry_proxy.clone());
extension_host_proxy.register_language_proxy(language_server_registry_proxy.clone());
extension_host_proxy.register_language_server_proxy(language_server_registry_proxy);
@ -20,6 +33,7 @@ pub fn init(
#[derive(Clone)]
struct LanguageServerRegistryProxy {
language_registry: Arc<LanguageRegistry>,
lsp_access: LspAccess,
}
impl ExtensionGrammarProxy for LanguageServerRegistryProxy {