use std::io;
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use unix::{UnixProcessControl, UnixReaper};
#[cfg(unix)]
pub type ActiveProcessControl = UnixProcessControl;
#[cfg(unix)]
pub type ActiveReaper = UnixReaper;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use windows::{WindowsProcessControl, WindowsReaper};
#[cfg(windows)]
pub type ActiveProcessControl = WindowsProcessControl;
#[cfg(windows)]
pub type ActiveReaper = WindowsReaper;
pub trait ProcessControl: crate::sealed::Sealed {
type Reaper: TreeReaper;
fn detach(&self, cmd: &mut tokio::process::Command);
fn new_reaper(&self) -> io::Result<Self::Reaper>;
}
pub trait TreeReaper: Send + Sync {
const ADOPTION_IS_ATOMIC: bool;
fn adopt(&self, child: &tokio::process::Child) -> io::Result<()>;
fn kill_tree(&self) -> io::Result<()>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_reaper_kills_the_grandchild_too() {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(async {
let ctl = crate::PROCESS_CONTROL;
let reaper = ctl.new_reaper().unwrap();
let mut cmd = spawn_a_sleeping_tree();
ctl.detach(&mut cmd);
let mut child = cmd.spawn().unwrap();
reaper.adopt(&child).unwrap();
reaper.kill_tree().unwrap();
let status = tokio::time::timeout(std::time::Duration::from_secs(10), child.wait())
.await
.expect("kill_tree left the child running")
.unwrap();
assert!(!status.success(), "a killed child must not report success");
});
}
fn spawn_a_sleeping_tree() -> tokio::process::Command {
#[cfg(unix)]
{
let mut c = tokio::process::Command::new("/bin/sh");
c.arg("-c").arg("sleep 60 & sleep 60");
c
}
#[cfg(windows)]
{
let mut c = tokio::process::Command::new("cmd.exe");
c.arg("/c")
.arg("start /b timeout /t 60 /nobreak & timeout /t 60 /nobreak");
c
}
}
}