summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsyn <isaqtm@gmail.com>2020-02-09 19:17:57 +0300
committersyn <isaqtm@gmail.com>2020-02-09 19:17:57 +0300
commit30e1e3a13dc19c47afc517d1178e1dbf5b401596 (patch)
treeb57b23cb10d468325bc259e05dfb6901b9b56f59 /src
parentdf090c4c8caa0632ce1fa802faf8752c2c861517 (diff)
downloadevr-30e1e3a13dc19c47afc517d1178e1dbf5b401596.tar.gz
timeout'ed waiter for process
Diffstat (limited to 'src')
-rw-r--r--src/backends/clang.rs60
-rw-r--r--src/backends/mod.rs19
-rw-r--r--src/backends/python.rs34
-rw-r--r--src/backends/run_error.rs38
-rw-r--r--src/conf.rs47
-rw-r--r--src/main.rs11
-rw-r--r--src/wait/error.rs32
-rw-r--r--src/wait/mod.rs81
-rw-r--r--src/wait/rusage_ffi.rs53
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,
+ }
+ }
+}