fix(tauri): stop CLI process group on exit
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user