fix(tauri): stop CLI process group on exit

This commit is contained in:
Shantur Rathore
2026-03-18 07:29:09 +00:00
parent b12825f923
commit eaab5e2e9f

View File

@@ -9,6 +9,8 @@ use std::ffi::OsStr;
use std::fs; use std::fs;
use std::io::{BufRead, BufReader, Read, Write}; use std::io::{BufRead, BufReader, Read, Write};
use std::net::TcpStream; use std::net::TcpStream;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{Child, Command, Stdio}; use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@@ -50,12 +52,45 @@ const SESSION_COOKIE_NAME: &str = "codenomad_session";
const CLI_STOP_GRACE_SECS: u64 = 30; const CLI_STOP_GRACE_SECS: u64 = 30;
#[cfg(unix)]
fn configure_posix_process_group(command: &mut Command) {
// Ensure the CLI runs in its own process group so we can terminate wrapper
// processes (login shell/tsx) without leaving the server orphaned.
unsafe {
command.pre_exec(|| {
if libc::setpgid(0, 0) != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
});
}
}
#[cfg(windows)] #[cfg(windows)]
fn kill_process_tree_windows(pid: u32) { fn kill_process_tree_windows(pid: u32, force: bool) -> bool {
let mut args = vec!["/PID".to_string(), pid.to_string(), "/T".to_string()];
if force {
args.push("/F".to_string());
}
let mut command = Command::new("taskkill"); let mut command = Command::new("taskkill");
command.args(["/PID", &pid.to_string(), "/T", "/F"]); command.args(&args);
configure_spawn(&mut command); configure_spawn(&mut command);
let _ = command.status();
match command.output() {
Ok(output) => {
if output.status.success() {
return true;
}
// If the PID is already gone, treat it as success.
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
let stderr = String::from_utf8_lossy(&output.stderr).to_lowercase();
let combined = format!("{stdout}\n{stderr}");
combined.contains("not found") || combined.contains("no running instance")
}
Err(_) => false,
}
} }
fn navigate_main(app: &AppHandle, url: &str) { fn navigate_main(app: &AppHandle, url: &str) {
if let Some(win) = app.webview_windows().get("main") { if let Some(win) = app.webview_windows().get("main") {
@@ -369,11 +404,19 @@ impl CliProcessManager {
log_line(&format!("stopping CLI pid={}", child.id())); log_line(&format!("stopping CLI pid={}", child.id()));
#[cfg(unix)] #[cfg(unix)]
unsafe { unsafe {
libc::kill(child.id() as i32, libc::SIGTERM); let pid = child.id() as i32;
// Prefer signaling the process group to avoid orphaning children
// when the CLI was launched via a wrapper shell.
let group_res = libc::kill(-pid, libc::SIGTERM);
if group_res != 0 {
let _ = libc::kill(pid, libc::SIGTERM);
}
} }
#[cfg(windows)] #[cfg(windows)]
{ {
kill_process_tree_windows(child.id()); if !kill_process_tree_windows(child.id(), false) {
let _ = child.kill();
}
} }
let start = Instant::now(); let start = Instant::now();
@@ -389,11 +432,17 @@ impl CliProcessManager {
)); ));
#[cfg(unix)] #[cfg(unix)]
unsafe { unsafe {
libc::kill(child.id() as i32, libc::SIGKILL); let pid = child.id() as i32;
let group_res = libc::kill(-pid, libc::SIGKILL);
if group_res != 0 {
let _ = libc::kill(pid, libc::SIGKILL);
}
} }
#[cfg(windows)] #[cfg(windows)]
{ {
kill_process_tree_windows(child.id()); if !kill_process_tree_windows(child.id(), true) {
let _ = child.kill();
}
} }
break; break;
} }
@@ -475,6 +524,8 @@ impl CliProcessManager {
if let Some(ref cwd) = cwd { if let Some(ref cwd) = cwd {
c.current_dir(cwd); c.current_dir(cwd);
} }
#[cfg(unix)]
configure_posix_process_group(&mut c);
c.spawn()? c.spawn()?
} }
ShellCommandType::Direct(cmd) => { ShellCommandType::Direct(cmd) => {
@@ -488,6 +539,8 @@ impl CliProcessManager {
if let Some(ref cwd) = cwd { if let Some(ref cwd) = cwd {
c.current_dir(cwd); c.current_dir(cwd);
} }
#[cfg(unix)]
configure_posix_process_group(&mut c);
c.spawn()? c.spawn()?
} }
}; };
@@ -560,9 +613,21 @@ impl CliProcessManager {
locked.error = Some("CLI did not start in time".to_string()); locked.error = Some("CLI did not start in time".to_string());
log_line("timeout waiting for CLI readiness"); log_line("timeout waiting for CLI readiness");
if let Some(child) = child_holder_clone.lock().as_mut() { if let Some(child) = child_holder_clone.lock().as_mut() {
#[cfg(unix)]
unsafe {
let pid = child.id() as i32;
let group_res = libc::kill(-pid, libc::SIGKILL);
if group_res != 0 {
let _ = libc::kill(pid, libc::SIGKILL);
}
}
#[cfg(windows)] #[cfg(windows)]
kill_process_tree_windows(child.id()); {
#[cfg(not(windows))] if !kill_process_tree_windows(child.id(), true) {
let _ = child.kill();
}
}
#[cfg(not(any(unix, windows)))]
{ {
let _ = child.kill(); let _ = child.kill();
} }