diff options
author | syn <isaqtm@gmail.com> | 2020-02-09 19:17:57 +0300 |
---|---|---|
committer | syn <isaqtm@gmail.com> | 2020-02-09 19:17:57 +0300 |
commit | 30e1e3a13dc19c47afc517d1178e1dbf5b401596 (patch) | |
tree | b57b23cb10d468325bc259e05dfb6901b9b56f59 | |
parent | df090c4c8caa0632ce1fa802faf8752c2c861517 (diff) | |
download | evr-30e1e3a13dc19c47afc517d1178e1dbf5b401596.tar.gz |
timeout'ed waiter for process
-rw-r--r-- | src/backends/clang.rs | 60 | ||||
-rw-r--r-- | src/backends/mod.rs | 19 | ||||
-rw-r--r-- | src/backends/python.rs | 34 | ||||
-rw-r--r-- | src/backends/run_error.rs | 38 | ||||
-rw-r--r-- | src/conf.rs | 47 | ||||
-rw-r--r-- | src/main.rs | 11 | ||||
-rw-r--r-- | src/wait/error.rs | 32 | ||||
-rw-r--r-- | src/wait/mod.rs | 81 | ||||
-rw-r--r-- | src/wait/rusage_ffi.rs | 53 |
9 files changed, 275 insertions, 100 deletions
diff --git a/src/backends/clang.rs b/src/backends/clang.rs index dd566c7..0947b49 100644 --- a/src/backends/clang.rs +++ b/src/backends/clang.rs @@ -1,11 +1,12 @@ use serde_derive::{ Serialize, Deserialize }; -use crate::backends::{ Backend, mk_tmp_dir, RunStatus }; +use crate::backends::{ Backend, mk_tmp_dir, RunError }; use std::path::{ Path, PathBuf }; -use std::io::{ Result, Error, ErrorKind }; +use std::io::{ Result as IoResult, Error, ErrorKind }; use std::process::{ Command }; use std::collections::hash_map::DefaultHasher; use std::hash::{ Hash, Hasher }; -use nix::{ sys::wait, unistd::Pid }; +use crate::wait::{ WaitInfo, wait_child }; +use std::time::Duration; #[derive(Debug, Serialize, Deserialize, Default)] @@ -17,6 +18,9 @@ pub struct ClangBackend { #[serde(default = "default_cc")] cc: String, + + #[serde(default = "default_timeout")] + timeout: Duration } @@ -24,8 +28,12 @@ fn default_cc() -> String { "clang++".to_string() } +fn default_timeout() -> Duration { + Duration::from_secs(1) +} + -fn get_binary_by_filename(fname: &Path) -> Result<PathBuf> { +fn get_binary_by_filename(fname: &Path) -> IoResult<PathBuf> { let hashed_fname = { let mut hasher = DefaultHasher::new(); fname.hash(&mut hasher); @@ -37,7 +45,7 @@ fn get_binary_by_filename(fname: &Path) -> Result<PathBuf> { impl ClangBackend { - fn build(&self, fname: &Path) -> Result<PathBuf> { + fn build(&self, fname: &Path) -> IoResult<PathBuf> { let binary_fname = get_binary_by_filename(fname)?; let get_mtime = |file| { std::fs::metadata(file)? @@ -73,48 +81,12 @@ impl Backend for ClangBackend { } } - fn run(&self, fname: &Path) -> Result<RunStatus> { + fn run(&self, fname: &Path) -> Result<WaitInfo, RunError> { let binary_fname = self.build(fname)?; - let binary_proc = Command::new(&binary_fname) + let proc = Command::new(&binary_fname) .spawn()?; - get_status(binary_proc) + Ok(wait_child(proc, self.timeout, std::time::Instant::now())?) } } - - -#[cfg(unix)] -fn get_status(proc: std::process::Child) -> Result<RunStatus> { - let pid = Pid::from_raw(proc.id() as i32); - - loop { - let status_result = wait::waitpid(Some(pid), None) - .map_err(|err| Error::new(ErrorKind::Other, err)); - - let status = status_result?; - - match status { - wait::WaitStatus::Exited(ret_pid, code) => { - assert_eq!(ret_pid, pid); - - if code == 0 { - return Ok(RunStatus::Success); - } else { - return Ok(RunStatus::ErrorCode(code)); - } - }, - wait::WaitStatus::Signaled(ret_pid, sig, coredump) => { - assert_eq!(ret_pid, pid); - - return Ok(RunStatus::Signal(sig, coredump)); - }, - _ => continue, - } - } -} - -#[cfg(not(unix))] -fn get_status(proc: std::process::Child) -> Result<RunStatus> { - compile_error!("currently only unix supported"); -}
\ No newline at end of file diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 0af1bb1..3bf9fc7 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -1,8 +1,8 @@ use std::path::{ Path, PathBuf }; use std::env::temp_dir; use lazy_static::lazy_static; -use std::io::{ Error, ErrorKind, Result }; -use nix::sys::signal::Signal as NixSignal; +use std::io::{ Error, ErrorKind }; +use crate::wait::{ WaitInfo }; pub mod python; pub mod clang; @@ -10,24 +10,21 @@ pub mod clang; pub use python::PythonBackend; pub use clang::ClangBackend; +pub mod run_error; +pub use run_error::RunError; + + lazy_static! { static ref EVR_TMP_DIR: PathBuf = temp_dir().join("evr-tmp"); } -pub enum RunStatus { - Success, - ErrorCode(i32), - TimedOut(std::time::Duration), - Signal(NixSignal, bool), -} - pub trait Backend { fn get_template(&self) -> Option<&str>; - fn run(&self, fname: &Path) -> Result<RunStatus>; + fn run(&self, fname: &Path) -> Result<WaitInfo, RunError>; } -fn mk_tmp_dir() -> Result<&'static std::path::PathBuf> { +fn mk_tmp_dir() -> std::io::Result<&'static std::path::PathBuf> { if !EVR_TMP_DIR.exists() { std::fs::create_dir(&*EVR_TMP_DIR)?; } else { diff --git a/src/backends/python.rs b/src/backends/python.rs index 66402b9..e3569b1 100644 --- a/src/backends/python.rs +++ b/src/backends/python.rs @@ -1,9 +1,8 @@ use serde_derive::{ Serialize, Deserialize }; -use crate::backends::{ Backend, RunStatus }; +use crate::backends::{ Backend, RunError }; use std::process::{ Command }; use std::path::Path; -use log::error; -use wait_timeout::ChildExt; +use crate::wait::{ wait_child, WaitInfo }; #[derive(Debug, Serialize, Deserialize, Default)] pub struct PythonBackend { @@ -33,35 +32,14 @@ impl Backend for PythonBackend { } } - fn run(&self, fname: &Path) -> std::io::Result<RunStatus> { - let timer = std::time::SystemTime::now(); + fn run(&self, fname: &Path) -> Result<WaitInfo, RunError> { + let timer = std::time::Instant::now(); - let mut child = Command::new(self.get_interpreter()) + let child = Command::new(self.get_interpreter()) .arg(fname.as_os_str()) .spawn()?; let timeout = std::time::Duration::from_secs_f32(self.timeout.unwrap_or(1.0)); - match child.wait_timeout(timeout) { - Ok(maybe_status) => match maybe_status { - Some(status) => { - if !status.success() { - return Ok(RunStatus::ErrorCode(status.code().unwrap_or(0))); - } - Ok(RunStatus::Success) - }, - None => { - let elapsed = timer.elapsed().unwrap_or(Default::default()); - child.kill()?; - child.wait()?; - Ok(RunStatus::TimedOut(elapsed)) - } - }, - Err(err) => { - error!("could not wait for child: {}", err); - child.kill()?; - child.wait()?; // Wait defunct - Err(err) - } - } + Ok(wait_child(child, timeout, timer)?) } }
\ No newline at end of file diff --git a/src/backends/run_error.rs b/src/backends/run_error.rs new file mode 100644 index 0000000..68f0495 --- /dev/null +++ b/src/backends/run_error.rs @@ -0,0 +1,38 @@ +use crate::wait::WaitError; +use std::io::Error as IoError; + +#[derive(Debug)] +pub enum RunError { + IoError(std::io::Error), + WaitError(WaitError) +} + +impl From<IoError> for RunError { + fn from(err: IoError) -> Self { + RunError::IoError(err) + } +} + +impl From<WaitError> for RunError { + fn from(err: WaitError) -> Self { + RunError::WaitError(err) + } +} + +impl std::fmt::Display for RunError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match &*self { + RunError::IoError(e) => e.fmt(f), + RunError::WaitError(e) => e.fmt(f) + } + } +} + +impl std::error::Error for RunError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + RunError::IoError(ref e) => Some(e), + RunError::WaitError(ref e) => Some(e) + } + } +}
\ No newline at end of file diff --git a/src/conf.rs b/src/conf.rs index dec461f..febfef5 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -3,11 +3,12 @@ use std::path::{ PathBuf, Path }; use toml::de; use log::{ error, trace }; use std::io::prelude::*; +use nix::sys::wait::WaitStatus; type Error = std::io::Error; use std::io::ErrorKind; -use crate::backends::{ Backend, PythonBackend, ClangBackend, RunStatus }; +use crate::backends::{ Backend, PythonBackend, ClangBackend, RunError }; #[derive(Debug, Serialize, Deserialize, Default)] @@ -42,24 +43,42 @@ impl Conf { } } - pub fn run(&self, fname: &Path) -> std::io::Result<()> { + pub fn run( + &self, + fname: &Path, + show_time: bool, + show_mem: bool + ) -> Result<(), RunError> { match self.get_backend(fname) { - Some(backend) => backend.run(fname).map(|status| { - match status { - RunStatus::Success => {}, - RunStatus::ErrorCode(code) => - { error!("process exited with code: {}", code); }, - RunStatus::TimedOut(duration) => - { error!("process timed out at {:.3}s", duration.as_secs_f32()); } - RunStatus::Signal(sig, coredump) => - { error!("process killed by {} {}", sig, if coredump { "(core dumped)" } else { "" }); } - }; + Some(backend) => backend.run(fname).map(|info| { + match info.status { + WaitStatus::Exited(_pid, ret) => match ret { + 0 => { + if show_time { + println!("wall time: {:?}", info.wall_time); + } + if show_mem { + println!("rss: {}K", info.usage.ru_maxrss); + } + }, + _ => error!("process exited with {}", ret) + }, + WaitStatus::Signaled(pid, sig, coredump) => { + error!( + "process killed by {} {}. was {}", + sig, + if coredump {"(core dumped)"} else {""}, + pid + ); + }, + _ => error!("process signaled, but not exited") + } }), - None => Err(Error::new(ErrorKind::InvalidData, "Backend not found")) + None => Err(Error::new(ErrorKind::InvalidData, "Backend not found").into()) } } - pub fn make(&self, fname: &Path) -> std::io::Result<()> { + pub fn make(&self, fname: &Path) -> Result<(), RunError> { trace!("Template: {:?}", self.get_template(&fname)); std::fs::File::create(fname)? diff --git a/src/main.rs b/src/main.rs index 57f8529..1621e1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,11 @@ extern crate lazy_static; use clap::{ AppSettings, App, Arg }; use env_logger; -use log::{ trace, error }; +use log::{ error }; mod conf; mod backends; +mod wait; fn main() { let matches = App::new("evr") @@ -42,13 +43,17 @@ fn main() { let result = if src_path.exists() { - config.run(&src_path) + config.run( + &src_path, + matches.is_present("time"), + matches.is_present("mem") + ) } else { config.make(&src_path) }; match result { - Ok(_) => trace!("ok"), + Ok(_) => {}, Err(err) => error!("{}", err) } } diff --git a/src/wait/error.rs b/src/wait/error.rs new file mode 100644 index 0000000..bb95e81 --- /dev/null +++ b/src/wait/error.rs @@ -0,0 +1,32 @@ +use nix; +use std::time::Duration; + +#[derive(Debug)] +pub enum WaitError { + TimedOut(Duration), + OsError(nix::Error) +} + +impl From<nix::Error> for WaitError { + fn from(err: nix::Error) -> Self { + WaitError::OsError(err) + } +} + +impl std::fmt::Display for WaitError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + WaitError::TimedOut(dur) => write!(f, "process timed out in {:?}", dur), + WaitError::OsError(err) => err.fmt(f) + } + } +} + +impl std::error::Error for WaitError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + WaitError::TimedOut(_) => None, + WaitError::OsError(ref e) => Some(e) + } + } +}
\ No newline at end of file diff --git a/src/wait/mod.rs b/src/wait/mod.rs new file mode 100644 index 0000000..9761f08 --- /dev/null +++ b/src/wait/mod.rs @@ -0,0 +1,81 @@ +use nix::libc::{self, c_int}; +use nix::{ + errno::Errno, + sys::wait::WaitStatus, + unistd::Pid +}; +use std::time::{ Instant, Duration }; +use std::process::Child; +use std::thread; +use std::sync::mpsc; + +mod error; +mod rusage_ffi; +pub use rusage_ffi::Rusage; + +pub use error::WaitError; + +#[derive(Debug)] +pub struct WaitInfo { + pub status: WaitStatus, + pub usage: Rusage, + pub wall_time: Duration +} + + +fn wait4_pid( + pid: Pid, + chan: mpsc::Sender<std::result::Result<WaitInfo, nix::Error>>, + timer: Instant +) { + let mut status: c_int = 0; + let mut usg: libc::rusage; + let wait_ret; + + unsafe { + usg = std::mem::zeroed(); + wait_ret = libc::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<WaitInfo, WaitError> { + 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(usg)) => Ok(usg), + 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!() + } +} diff --git a/src/wait/rusage_ffi.rs b/src/wait/rusage_ffi.rs new file mode 100644 index 0000000..5948958 --- /dev/null +++ b/src/wait/rusage_ffi.rs @@ -0,0 +1,53 @@ +use nix::libc; +use std::time::Duration; + +#[derive(Debug)] +pub struct Rusage { + pub ru_utime: Duration, + pub ru_stime: Duration, + pub ru_maxrss: i64, + pub ru_ixrss: i64, + pub ru_idrss: i64, + pub ru_isrss: i64, + pub ru_minflt: i64, + pub ru_majflt: i64, + pub ru_nswap: i64, + pub ru_inblock: i64, + pub ru_oublock: i64, + pub ru_msgsnd: i64, + pub ru_msgrcv: i64, + pub ru_nsignals: i64, + pub ru_nvcsw: i64, + pub ru_nivcsw: i64, +} + +impl From<libc::rusage> for Rusage { + fn from(usg: libc::rusage) -> Rusage { + const MICROS_IN_SEC: u64 = 1_000_000; + let convert_timeval = |tv: libc::timeval| { + Duration::from_micros( + tv.tv_sec as u64 * MICROS_IN_SEC + + tv.tv_usec as u64 + ) + }; + + Rusage { + ru_utime: convert_timeval(usg.ru_utime), + ru_stime: convert_timeval(usg.ru_stime), + ru_maxrss: usg.ru_maxrss, + ru_ixrss: usg.ru_ixrss, + ru_idrss: usg.ru_idrss, + ru_isrss: usg.ru_isrss, + ru_minflt: usg.ru_minflt, + ru_majflt: usg.ru_majflt, + ru_nswap: usg.ru_nswap, + ru_inblock: usg.ru_inblock, + ru_oublock: usg.ru_oublock, + ru_msgsnd: usg.ru_msgsnd, + ru_msgrcv: usg.ru_msgrcv, + ru_nsignals: usg.ru_nsignals, + ru_nvcsw: usg.ru_nvcsw, + ru_nivcsw: usg.ru_nivcsw, + } + } +} |