use nix::sys::signal;
use nix::unistd;
use std::{io, process, sync, thread, time};
#[derive(Debug)]
pub struct Supervisor {
children: Vec<process::Child>,
kill_timeout: time::Duration,
poll_interval: time::Duration,
}
impl Supervisor {
pub fn add_child(&mut self, child: process::Child) {
self.children.push(child)
}
}
impl Drop for Supervisor {
fn drop(&mut self) {
for child in self.children.iter_mut().rev() {
let _ = shutdown_process(child, self.kill_timeout, self.poll_interval);
}
}
}
impl Supervisor {
pub fn new(kill_timeout: time::Duration) -> Self {
Supervisor {
children: Vec::new(),
kill_timeout,
poll_interval: time::Duration::from_millis(100),
}
}
}
impl Default for Supervisor {
fn default() -> Self {
Supervisor::new(time::Duration::from_secs(10))
}
}
pub fn shutdown_process(
child: &mut process::Child,
kill_timeout: time::Duration,
poll_interval: time::Duration,
) -> io::Result<process::ExitStatus> {
let start = time::Instant::now();
let pid = unistd::Pid::from_raw(child.id() as i32);
signal::kill(pid, signal::Signal::SIGTERM)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
while time::Instant::now() - start < kill_timeout {
if let Some(exit_status) = child.try_wait()? {
return Ok(exit_status);
}
thread::sleep(poll_interval);
}
signal::kill(pid, signal::Signal::SIGKILL)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(child.wait()?)
}
pub fn setup_term_flag() -> Result<sync::Arc<sync::atomic::AtomicBool>, io::Error> {
let term = sync::Arc::new(sync::atomic::AtomicBool::new(false));
for &signal in &[
signal_hook::SIGINT,
signal_hook::SIGTERM,
signal_hook::SIGQUIT,
] {
signal_hook::flag::register(signal, term.clone())?;
}
Ok(term)
}