debugger: Kill debug sessions on app quit (#33273)

Before this PR force quitting Zed would leave hanging debug adapter
processes and not allow debug adapters to clean up their sessions
properly.

This PR fixes this problem by sending a disconnect/terminate to all
debug adapters and force shutting down their processes after they
respond.

Co-authored-by: Cole Miller \<cole@zed.dev\>

Release Notes:

- debugger: Shutdown and clean up debug processes when force quitting
Zed

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
Anthony Eid 2025-06-23 16:41:53 -04:00 committed by GitHub
parent c610ebfb03
commit d34d4f2ef1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 288 additions and 63 deletions

View file

@ -790,7 +790,7 @@ impl Session {
BreakpointStoreEvent::SetDebugLine | BreakpointStoreEvent::ClearDebugLines => {}
})
.detach();
cx.on_app_quit(Self::on_app_quit).detach();
// cx.on_app_quit(Self::on_app_quit).detach();
let this = Self {
mode: Mode::Building,
@ -945,6 +945,37 @@ impl Session {
self.parent_session.as_ref()
}
pub fn on_app_quit(&mut self, cx: &mut Context<Self>) -> Task<()> {
let Some(client) = self.adapter_client() else {
return Task::ready(());
};
let supports_terminate = self
.capabilities
.support_terminate_debuggee
.unwrap_or(false);
cx.background_spawn(async move {
if supports_terminate {
client
.request::<dap::requests::Terminate>(dap::TerminateArguments {
restart: Some(false),
})
.await
.ok();
} else {
client
.request::<dap::requests::Disconnect>(dap::DisconnectArguments {
restart: Some(false),
terminate_debuggee: Some(true),
suspend_debuggee: Some(false),
})
.await
.ok();
}
})
}
pub fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
@ -1818,17 +1849,11 @@ impl Session {
}
}
fn on_app_quit(&mut self, cx: &mut Context<Self>) -> Task<()> {
let debug_adapter = self.adapter_client();
cx.background_spawn(async move {
if let Some(client) = debug_adapter {
client.shutdown().await.log_err();
}
})
}
pub fn shutdown(&mut self, cx: &mut Context<Self>) -> Task<()> {
if self.is_session_terminated {
return Task::ready(());
}
self.is_session_terminated = true;
self.thread_states.exit_all_threads();
cx.notify();
@ -1859,14 +1884,8 @@ impl Session {
cx.emit(SessionStateEvent::Shutdown);
let debug_client = self.adapter_client();
cx.background_spawn(async move {
let _ = task.await;
if let Some(client) = debug_client {
client.shutdown().await.log_err();
}
cx.spawn(async move |_, _| {
task.await;
})
}