pub(crate) mod capture;
#[cfg(windows)]
pub(crate) mod job;
pub(crate) mod termination;
pub(crate) mod timed;
#[cfg(unix)]
pub(crate) fn configure_process_group(command: &mut std::process::Command) {
use std::os::unix::process::CommandExt;
command.process_group(0);
}
#[cfg(not(unix))]
pub(crate) fn configure_process_group(_command: &mut std::process::Command) {}
#[cfg(unix)]
pub(crate) fn kill_process_tree(child: &mut std::process::Child, _tracked: &termination::TrackedProcessGroup) {
let process_group = format!("-{}", child.id());
let killed_group = std::process::Command::new("kill")
.args(["-KILL", "--", &process_group])
.status()
.is_ok_and(|status| status.success());
if !killed_group {
let _ = child.kill();
}
}
#[cfg(windows)]
pub(crate) fn kill_process_tree(child: &mut std::process::Child, tracked: &termination::TrackedProcessGroup) {
if !tracked.terminate_tree() {
let _ = child.kill();
}
}
#[cfg(not(any(unix, windows)))]
pub(crate) fn kill_process_tree(child: &mut std::process::Child, _tracked: &termination::TrackedProcessGroup) {
let _ = child.kill();
}
pub(crate) const INITIAL_WAIT_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(1);
const MAX_WAIT_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(50);
pub(crate) fn next_poll_interval(current: std::time::Duration) -> std::time::Duration {
current
.checked_mul(2)
.unwrap_or(MAX_WAIT_POLL_INTERVAL)
.min(MAX_WAIT_POLL_INTERVAL)
}
pub(crate) const STOPPED_PROCESS_GRACE: std::time::Duration = std::time::Duration::from_secs(2);
pub(crate) const STOPPED_PROCESS_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(200);
pub(crate) const KILL_REAP_LIMIT: std::time::Duration = std::time::Duration::from_secs(5);
#[cfg(unix)]
pub(crate) fn is_process_stopped(pid: u32) -> bool {
let Ok(output) = std::process::Command::new("ps")
.args(["-o", "state=", "-p", &pid.to_string()])
.output()
else {
return false;
};
String::from_utf8_lossy(&output.stdout).trim().starts_with(['T', 't'])
}
#[cfg(not(unix))]
pub(crate) fn is_process_stopped(_pid: u32) -> bool {
false
}
pub(crate) trait WaitTimeout {
fn wait_timeout(&mut self, timeout: std::time::Duration) -> std::io::Result<Option<std::process::ExitStatus>>;
}
impl WaitTimeout for std::process::Child {
fn wait_timeout(&mut self, timeout: std::time::Duration) -> std::io::Result<Option<std::process::ExitStatus>> {
let start = std::time::Instant::now();
let mut poll_interval = INITIAL_WAIT_POLL_INTERVAL;
loop {
if let Some(status) = self.try_wait()? {
return Ok(Some(status));
}
let elapsed = start.elapsed();
if elapsed >= timeout {
return Ok(None);
}
std::thread::sleep(poll_interval.min(timeout - elapsed));
poll_interval = next_poll_interval(poll_interval);
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
#[test]
fn the_wait_backoff_starts_at_one_millisecond_and_caps_at_fifty() {
assert_eq!(super::INITIAL_WAIT_POLL_INTERVAL, Duration::from_millis(1));
let intervals = std::iter::successors(Some(super::INITIAL_WAIT_POLL_INTERVAL), |current| {
Some(super::next_poll_interval(*current))
})
.take(8)
.collect::<Vec<_>>();
assert_eq!(
intervals,
vec![
Duration::from_millis(1),
Duration::from_millis(2),
Duration::from_millis(4),
Duration::from_millis(8),
Duration::from_millis(16),
Duration::from_millis(32),
Duration::from_millis(50),
Duration::from_millis(50),
]
);
}
}