use std::io;
use std::os::unix::process::parent_id;
use std::process::{self, Child, Command};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use std::thread;
use std::time::Duration;
const POLL: Duration = Duration::from_secs(1);
#[derive(Default)]
struct State {
child: Option<u32>,
finished: bool,
}
pub struct Guard(Arc<Mutex<State>>);
impl Guard {
pub fn watch() -> Guard {
let state = Arc::new(Mutex::new(State::default()));
let watched = Arc::clone(&state);
let parent = parent_id();
let _ = thread::Builder::new()
.name("orphan-guard".into())
.spawn(move || watch(&watched, parent));
Guard(state)
}
pub fn spawn(&self, command: &mut Command) -> io::Result<Child> {
let mut state = lock(&self.0);
let child = command.spawn()?;
state.child = Some(child.id());
Ok(child)
}
pub fn finished(&self) {
lock(&self.0).finished = true;
}
}
fn lock(state: &Mutex<State>) -> MutexGuard<'_, State> {
state.lock().unwrap_or_else(PoisonError::into_inner)
}
fn watch(state: &Mutex<State>, parent: u32) {
while !orphaned(parent, parent_id()) {
thread::sleep(POLL);
}
let state = lock(state);
if state.finished {
return;
}
match state.child {
Some(child) => terminate(child),
None => process::exit(128 + libc::SIGTERM),
}
}
fn orphaned(original: u32, now: u32) -> bool {
if cfg!(target_os = "macos") {
now == 1 && original != 1
} else {
now != original
}
}
fn terminate(child: u32) {
let below = crate::proxy::descendant_pids(child);
for pid in std::iter::once(child).chain(below) {
let Ok(pid) = libc::pid_t::try_from(pid) else {
continue;
};
if pid > 1 {
unsafe { libc::kill(pid, libc::SIGTERM) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_the_parents_exit_counts_as_orphaned() {
assert!(!orphaned(4121, 4121));
assert!(orphaned(4121, 1));
assert!(!orphaned(1, 1));
assert_eq!(orphaned(4121, 977), !cfg!(target_os = "macos"));
}
}