use std::time::Duration;
use processkit::Command;
fn pid_alive(pid: i32) -> bool {
let probed = unsafe { libc::kill(pid, 0) };
probed == 0 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
}
fn reaped_or_gone(pid: i32) -> bool {
let mut status = 0i32;
let reaped = unsafe { libc::waitpid(pid, &mut status, libc::WNOHANG) };
reaped == pid || reaped == -1
}
fn spawn_leaked_from_short_lived_thread(armed: bool) -> i32 {
std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("build runtime");
let pid = rt.block_on(async {
let mut cmd = Command::new("sleep").arg("300");
if armed {
cmd = cmd.kill_on_parent_death();
}
let process = cmd.start().await.expect("spawn sleeper");
let pid = process.pid().expect("sleeper pid") as i32;
std::mem::forget(process);
pid
});
drop(rt);
pid
})
.join()
.expect("spawner thread")
}
#[tokio::test]
#[ignore = "leaks a real containment group to isolate the pdeathsig knob"]
async fn dead_spawner_takes_its_armed_child_down() {
let pid = spawn_leaked_from_short_lived_thread(true);
let deadline = std::time::Instant::now() + Duration::from_secs(10);
while !reaped_or_gone(pid) && std::time::Instant::now() < deadline {
tokio::time::sleep(Duration::from_millis(50)).await;
}
assert!(
reaped_or_gone(pid),
"armed child {pid} must die with its spawning thread"
);
}
#[tokio::test]
#[ignore = "leaks a real containment group to isolate the pdeathsig knob"]
async fn dead_spawner_leaves_an_unarmed_child_alive() {
let pid = spawn_leaked_from_short_lived_thread(false);
tokio::time::sleep(Duration::from_secs(1)).await;
let alive = pid_alive(pid);
unsafe {
libc::kill(pid, libc::SIGKILL);
let mut status = 0i32;
libc::waitpid(pid, &mut status, 0);
}
assert!(
alive,
"unarmed child {pid} must outlive its spawning thread"
);
}