fix: clean up MCP subprocesses after abrupt parent exit (#8242)
Signed-off-by: fre <anonwurcod@proton.me> Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Oz <oz-agent@warp.dev> Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
#![cfg(target_os = "linux")]
|
||||
|
||||
use goose::subprocess::configure_subprocess;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const HELPER_ENV: &str = "GOOSE_SUBPROCESS_PARENT_DEATH_HELPER";
|
||||
|
||||
#[ctor::ctor]
|
||||
fn maybe_run_helper() {
|
||||
if std::env::var_os(HELPER_ENV).is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("runtime");
|
||||
|
||||
let pid = runtime.block_on(async {
|
||||
let mut command = tokio::process::Command::new("sleep");
|
||||
command.arg("30");
|
||||
command.stdin(Stdio::null());
|
||||
command.stdout(Stdio::null());
|
||||
command.stderr(Stdio::null());
|
||||
configure_subprocess(&mut command);
|
||||
|
||||
let child = command.spawn().expect("spawn child");
|
||||
let pid = child.id().expect("child pid");
|
||||
std::mem::forget(child);
|
||||
pid
|
||||
});
|
||||
|
||||
println!("{pid}");
|
||||
std::io::stdout().flush().expect("flush pid");
|
||||
|
||||
unsafe {
|
||||
libc::_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_process_exits_when_parent_process_dies() {
|
||||
let current_exe = std::env::current_exe().expect("current test binary");
|
||||
let mut helper = Command::new(current_exe)
|
||||
.env(HELPER_ENV, "1")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.expect("spawn helper");
|
||||
|
||||
let pid_line = {
|
||||
let stdout = helper.stdout.take().expect("helper stdout");
|
||||
let mut reader = BufReader::new(stdout);
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line).expect("read child pid");
|
||||
line
|
||||
};
|
||||
|
||||
let child_pid = pid_line.trim().parse::<u32>().expect("parse child pid");
|
||||
|
||||
let status = helper.wait().expect("wait for helper");
|
||||
assert!(status.success(), "helper exited unsuccessfully: {status}");
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
while process_exists(child_pid) {
|
||||
assert!(
|
||||
Instant::now() < deadline,
|
||||
"child process {child_pid} still exists after helper exit"
|
||||
);
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
fn process_exists(pid: u32) -> bool {
|
||||
PathBuf::from(format!("/proc/{pid}")).exists()
|
||||
}
|
||||
Reference in New Issue
Block a user