Skip to content

Commit e838aaa

Browse files
echobtfactorydroid
andauthored
fix(gui): comprehensive cleanup of child processes on Windows exit (#429)
When quitting the Tauri application on Windows, remaining child processes were preventing clean exit, causing Windows to prompt to terminate the command process. This fix adds comprehensive cleanup in the ExitRequested event handler to properly terminate all spawned child processes: - Close all SSH terminal sessions - Stop all LSP language servers - Stop all debugger (DAP) sessions - Disconnect all MCP context servers - Stop all test watchers - Stop live metrics monitoring thread - Shutdown all REPL kernels - Stop the MCP socket server (debug builds) Also added a shutdown_all() method to KernelManager for cleaner REPL kernel shutdown during app exit. Fixes issue where npm run tauri:dev would leave orphan processes on Windows. Co-authored-by: Droid Agent <droid@factory.ai>
1 parent baecb8f commit e838aaa

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

cortex-gui/src-tauri/src/lib.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,18 +1485,70 @@ pub fn run() {
14851485
*exiting = true;
14861486
}
14871487

1488+
info!("Application exit requested, cleaning up all child processes...");
1489+
14881490
// Close all terminals when the app exits
14891491
let terminal_state = app.state::<terminal::TerminalState>();
14901492
let _ = terminal_state.close_all(app);
14911493
info!("All terminals closed on app exit");
14921494

1495+
// Close all SSH sessions
1496+
let ssh_state = app.state::<ssh_terminal::SSHTerminalState>();
1497+
let _ = ssh_state.close_all(app);
1498+
info!("All SSH sessions closed on app exit");
1499+
1500+
// Stop all LSP servers
1501+
let lsp_state = app.state::<LspState>();
1502+
let _ = lsp_state.stop_all_servers();
1503+
info!("All LSP servers stopped on app exit");
1504+
1505+
// Stop all debugger sessions
1506+
let debugger_state = app.state::<DebuggerState>();
1507+
debugger_state.stop_all_sessions();
1508+
info!("All debugger sessions stopped on app exit");
1509+
1510+
// Disconnect all MCP context servers
1511+
let context_server_state = app.state::<ContextServerState>();
1512+
context_server_state.disconnect_all();
1513+
info!("All context servers disconnected on app exit");
1514+
1515+
// Stop all test watchers
1516+
let test_watcher_state = app.state::<testing::TestWatcherState>();
1517+
let _ = test_watcher_state.stop_all();
1518+
info!("All test watchers stopped on app exit");
1519+
1520+
// Stop live metrics if running
1521+
{
1522+
let live_metrics_state = app.state::<Arc<system_specs::LiveMetricsState>>();
1523+
live_metrics_state.stop();
1524+
info!("Live metrics stopped on app exit");
1525+
}
1526+
1527+
// Shutdown all REPL kernels
1528+
if let Ok(mut guard) = app.state::<REPLState>().0.lock() {
1529+
if let Some(manager) = guard.as_mut() {
1530+
manager.shutdown_all();
1531+
info!("All REPL kernels shut down on app exit");
1532+
}
1533+
}
1534+
1535+
// Stop the MCP socket server (debug only)
1536+
#[cfg(debug_assertions)]
1537+
{
1538+
let mcp_state = app.state::<mcp::McpState<tauri::Wry>>();
1539+
mcp_state.stop();
1540+
info!("MCP socket server stopped on app exit");
1541+
}
1542+
14931543
// Stop the extension host when the app exits
14941544
if let Ok(mut manager) = app.state::<ExtensionsState>().0.lock() {
14951545
if let Some(mut host) = manager.host.take() {
14961546
host.stop();
14971547
info!("Extension host stopped on app exit");
14981548
}
14991549
}
1550+
1551+
info!("All child processes cleaned up, exiting application");
15001552
}
15011553
_ => {}
15021554
}

cortex-gui/src-tauri/src/repl/kernel.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,14 @@ impl KernelManager {
380380
self.kernels.values().map(|k| k.info.clone()).collect()
381381
}
382382

383+
/// Shutdown all running kernels
384+
pub fn shutdown_all(&mut self) {
385+
let kernel_ids: Vec<String> = self.kernels.keys().cloned().collect();
386+
for kernel_id in kernel_ids {
387+
let _ = self.shutdown(&kernel_id);
388+
}
389+
}
390+
383391
/// Get variables from a Python kernel
384392
pub fn get_variables(&mut self, kernel_id: &str) -> Result<Vec<Variable>, String> {
385393
let kernel = self

0 commit comments

Comments
 (0)