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

@ -9712,29 +9712,31 @@ impl LspStore {
} else {
let buffers =
lsp_store.buffer_ids_to_buffers(envelope.payload.buffer_ids.into_iter(), cx);
lsp_store.stop_language_servers_for_buffers(
buffers,
envelope
.payload
.also_servers
.into_iter()
.filter_map(|selector| {
Some(match selector.selector? {
proto::language_server_selector::Selector::ServerId(server_id) => {
LanguageServerSelector::Id(LanguageServerId::from_proto(
lsp_store
.stop_language_servers_for_buffers(
buffers,
envelope
.payload
.also_servers
.into_iter()
.filter_map(|selector| {
Some(match selector.selector? {
proto::language_server_selector::Selector::ServerId(
server_id,
))
}
proto::language_server_selector::Selector::Name(name) => {
LanguageServerSelector::Name(LanguageServerName(
SharedString::from(name),
))
}
) => LanguageServerSelector::Id(LanguageServerId::from_proto(
server_id,
)),
proto::language_server_selector::Selector::Name(name) => {
LanguageServerSelector::Name(LanguageServerName(
SharedString::from(name),
))
}
})
})
})
.collect(),
cx,
);
.collect(),
cx,
)
.detach_and_log_err(cx);
}
})?;
@ -10290,9 +10292,9 @@ impl LspStore {
pub fn stop_language_servers_for_buffers(
&mut self,
buffers: Vec<Entity<Buffer>>,
also_restart_servers: HashSet<LanguageServerSelector>,
also_stop_servers: HashSet<LanguageServerSelector>,
cx: &mut Context<Self>,
) {
) -> Task<Result<()>> {
if let Some((client, project_id)) = self.upstream_client() {
let request = client.request(proto::StopLanguageServers {
project_id,
@ -10300,7 +10302,7 @@ impl LspStore {
.into_iter()
.map(|b| b.read(cx).remote_id().to_proto())
.collect(),
also_servers: also_restart_servers
also_servers: also_stop_servers
.into_iter()
.map(|selector| {
let selector = match selector {
@ -10322,24 +10324,31 @@ impl LspStore {
.collect(),
all: false,
});
cx.background_spawn(request).detach_and_log_err(cx);
cx.background_spawn(async move {
let _ = request.await?;
Ok(())
})
} else {
self.stop_local_language_servers_for_buffers(&buffers, also_restart_servers, cx)
.detach();
let task =
self.stop_local_language_servers_for_buffers(&buffers, also_stop_servers, cx);
cx.background_spawn(async move {
task.await;
Ok(())
})
}
}
fn stop_local_language_servers_for_buffers(
&mut self,
buffers: &[Entity<Buffer>],
also_restart_servers: HashSet<LanguageServerSelector>,
also_stop_servers: HashSet<LanguageServerSelector>,
cx: &mut Context<Self>,
) -> Task<()> {
let Some(local) = self.as_local_mut() else {
return Task::ready(());
};
let mut language_server_names_to_stop = BTreeSet::default();
let mut language_servers_to_stop = also_restart_servers
let mut language_servers_to_stop = also_stop_servers
.into_iter()
.flat_map(|selector| match selector {
LanguageServerSelector::Id(id) => Some(id),