fix(run): stop runs and terminals when the app exits

Closing a window stops what its project started, but a quit that doesn't
close the windows first (the Dock's Quit, the app menu, a logout) exited
with the runs still alive: a server kept its port and the next run of
the configuration failed with "port already in use". On exit the app
now kills every run's process tree and every terminal shell, as the
remote server already does when it shuts down.

Fixes #1691
This commit is contained in:
l0ng-ai
2026-11-06 14:46:40 +08:00
parent 1c2eceb55f
commit 7093d35124
3 changed files with 33 additions and 0 deletions
+8
View File
@@ -349,6 +349,14 @@ impl Host {
}
}
/// Called on desktop exit: kills the runs and terminal shells still alive. Closing a
/// window stops what it started, but a quit from the Dock, the app menu or a logout
/// exits without closing the windows, and the process trees would outlive the app.
pub fn stop_processes_on_exit(&self) {
self.processes.kill_all();
self.terminals.kill_all();
}
/// Called before desktop exit, which may bypass document destructors.
pub fn clear_workspace_edit_history_on_exit(&self) {
for project in self.projects() {
+24
View File
@@ -529,6 +529,30 @@ fn revoking_one_project_stops_only_its_runs() {
h.pm.stop(theirs.id).unwrap();
}
/// Quitting the app kills what every project's runs started, forked children included (#1691).
#[test]
fn kill_all_stops_every_projects_process_trees() {
let h = harness();
let other = tempfile::tempdir().unwrap();
let (mine, chunks) = start(&h, &cfg("mine", "/bin/sh", &["-c", "sleep 30 & echo $!; wait"]));
let grandchild: i32 = wait_for(&chunks, "\n").trim().parse().expect("pid");
let theirs =
h.pm.start(
other.path(),
&cfg("theirs", "/bin/sh", &["-c", "sleep 30"]),
&StartOptions::default(),
h.sink.clone(),
Box::new(|_| {}),
)
.unwrap();
h.pm.kill_all();
let deadline = Instant::now() + Duration::from_secs(8);
while alive(grandchild) || h.pm.list().iter().any(|r| (r.id == mine || r.id == theirs.id) && r.running) {
assert!(Instant::now() < deadline, "a run survived kill_all");
std::thread::sleep(Duration::from_millis(50));
}
}
#[test]
#[ignore = "requires CMake, CTest and a C++ compiler"]
fn cmake_configure_discovers_builds_and_runs_a_multifile_target() {
+1
View File
@@ -234,6 +234,7 @@ pub fn run() {
let _ = &early_opens;
if let tauri::RunEvent::Exit = event {
if let Some(state) = app.try_state::<AppState>() {
state.host.stop_processes_on_exit();
state.host.clear_workspace_edit_history_on_exit();
state.host.shutdown_notebooks_on_exit();
}