use nix::libc::{self, c_int}; use nix::{errno::Errno, sys::wait::WaitStatus, unistd::Pid}; use std::convert::{TryFrom, TryInto}; use std::process::Child; use std::sync::mpsc; use std::thread; use std::time::{Duration, Instant}; mod error; mod rusage_ffi; pub use rusage_ffi::Rusage; use error::ProcessSignalInfo; pub use error::WaitError; #[derive(Debug)] struct WaitInfo { pub status: WaitStatus, pub usage: Rusage, pub wall_time: Duration, } #[derive(Debug)] pub struct ChildExitStatus { pub usage: Rusage, pub wall_time: Duration, } impl TryFrom for ChildExitStatus { type Error = WaitError; fn try_from(info: WaitInfo) -> Result { match info.status { WaitStatus::Exited(pid, ret) => match ret { 0 => Ok(ChildExitStatus { usage: info.usage, wall_time: info.wall_time, }), _ => Err(WaitError::ReturnNonZero(ret, pid)), }, WaitStatus::Signaled(pid, signal, coredump) => { Err(WaitError::Signaled(ProcessSignalInfo { pid, signal, coredump, })) } _ => Err(WaitError::NotExited), } } } #[cfg(target_os = "macos")] #[link(name = "c")] extern "C" { fn wait4( pid: libc::pid_t, status: *mut c_int, options: c_int, rusage: *mut libc::rusage, ) -> libc::pid_t; } #[cfg(target_os = "linux")] use libc::wait4; fn wait4_pid( pid: Pid, chan: mpsc::Sender>, timer: Instant, ) { let mut status: c_int = 0; let mut usg: libc::rusage; let wait_ret; unsafe { usg = std::mem::zeroed(); wait_ret = wait4(pid.as_raw(), &mut status, 0 as c_int, &mut usg); } #[allow(unused_must_use)] { chan.send(match wait_ret { -1 => Err(nix::Error::Sys(Errno::last())), _ => WaitStatus::from_raw(pid, status).map(|nix_status| WaitInfo { status: nix_status, usage: usg.into(), wall_time: timer.elapsed(), }), }); }; } pub fn wait_child( mut child: Child, timeout: Duration, timer: Instant, ) -> Result { let pid = Pid::from_raw(child.id() as i32); let (send, recv) = mpsc::channel(); let thr = thread::spawn(move || wait4_pid(pid, send, timer)); match recv.recv_timeout(timeout) { Ok(Ok(wait_info)) => wait_info.try_into(), Ok(Err(err)) => Err(err.into()), Err(mpsc::RecvTimeoutError::Timeout) => { drop(recv); drop(thr); #[allow(unused_must_use)] { child.kill(); } Err(WaitError::TimedOut(timeout)) } Err(mpsc::RecvTimeoutError::Disconnected) => unreachable!(), } }