use crate::error::{Context, SystemError};
use crate::io::epoll::{Epoll, Message};
use crate::signal::{SigMask, SignalFd};
use std::io;
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
lazy_static! {
static ref MANAGER: Mutex<SubprocessManager> = Mutex::new(SubprocessManager::new());
}
pub fn terminate_all() {
match MANAGER.lock() {
Ok(mut lock) => lock.terminate_all(),
Err(_) => eprintln!("Failed to terminate subprocesses: internal lock poisoned."),
}
}
pub fn try_spawn(program: String, args: Vec<String>) -> Result<(), SystemError> {
let printable_cmd: String = vec![program.clone()]
.into_iter()
.chain(args.iter().map(|arg| {
if arg.contains(' ') {
format!("\"{}\"", arg)
} else {
arg.clone()
}
}))
.collect::<Vec<String>>()
.join(" ");
let child_res: Result<Child, io::Error> = Command::new(program)
.args(args)
.stdin(Stdio::null())
.spawn();
let child = match child_res {
Ok(proc) => proc,
Err(error) => {
return Err(SystemError::from(error)
.with_context(format!("While trying to run {}:", printable_cmd)));
}
};
let process = Subprocess {
child,
printable_cmd,
};
MANAGER
.lock()
.expect("Internal lock poisoned.")
.add_process(process);
Ok(())
}
struct SubprocessManager {
processes: Vec<Subprocess>,
cleanup_thread_is_running: bool,
}
impl SubprocessManager {
fn new() -> SubprocessManager {
SubprocessManager {
processes: Vec::new(),
cleanup_thread_is_running: false,
}
}
fn cleanup(&mut self) {
self.processes = self
.processes
.drain(..)
.filter_map(Subprocess::try_cleanup)
.collect();
}
fn add_process(&mut self, process: Subprocess) {
self.processes.push(process);
if !self.cleanup_thread_is_running {
start_cleanup_thread();
self.cleanup_thread_is_running = true;
}
}
fn terminate_all(&mut self) {
for process in self.processes.drain(..) {
process.terminate();
}
}
}
struct Subprocess {
printable_cmd: String,
child: Child,
}
impl Subprocess {
#[must_use]
pub fn try_cleanup(mut self) -> Option<Subprocess> {
match self.child.try_wait() {
Err(error) => {
eprintln!("Error while waiting on {}: {}", self.printable_cmd, error);
None
}
Ok(status_opt) => match status_opt {
None => Some(self),
Some(status) => {
if status.success() {
None
} else {
match status.code() {
Some(code) => eprintln!(
"Failed to run {}: return code {}.",
self.printable_cmd, code
),
None => eprintln!(
"Failed to run {}: interrupted by signal.",
self.printable_cmd
),
};
None
}
}
},
}
}
pub fn terminate(self) {
if let Some(mut process) = self.try_cleanup() {
unsafe { libc::kill(process.child.id() as i32, libc::SIGTERM) };
std::thread::spawn(move || process.child.wait());
}
}
}
fn start_cleanup_thread() {
std::thread::spawn(move || {
let mut sigmask = SigMask::new();
sigmask.add(libc::SIGCHLD);
let signal_fd = SignalFd::new(&sigmask)
.expect("Subprocess cleanup thread failed to create a signal fd.");
let mut epoll: Epoll<SignalFd> =
Epoll::new().expect("Subprocess cleanup thread failed to create an epoll.");
epoll
.add_file(signal_fd)
.expect("Subprocess cleanup thread failed to register a signal fd with an epoll.");
loop {
for message in epoll
.poll(crate::io::epoll::INDEFINITE_TIMEOUT)
.expect("Failed to poll an epoll.")
{
match message {
Message::Ready(index) => {
let siginfo = epoll[index]
.read_raw()
.expect("Subprocess cleanup thread failed to read its signal fd.");
let signal_no = siginfo.ssi_signo as i32;
assert!(signal_no == libc::SIGCHLD);
MANAGER.lock().expect("Internal lock poisoned.").cleanup();
}
Message::Broken(_index) | Message::Hup(_index) => {
panic!("Signal fd in subprocess cleanup thread broken.");
}
}
}
}
});
}