summaryrefslogtreecommitdiffstats
path: root/vendor/wait-timeout/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/wait-timeout/src')
-rw-r--r--vendor/wait-timeout/src/bin/exit.rs4
-rw-r--r--vendor/wait-timeout/src/bin/reader.rs7
-rw-r--r--vendor/wait-timeout/src/bin/sleep.rs4
-rw-r--r--vendor/wait-timeout/src/lib.rs68
-rw-r--r--vendor/wait-timeout/src/unix.rs305
-rw-r--r--vendor/wait-timeout/src/windows.rs34
6 files changed, 422 insertions, 0 deletions
diff --git a/vendor/wait-timeout/src/bin/exit.rs b/vendor/wait-timeout/src/bin/exit.rs
new file mode 100644
index 000000000..f33c49f34
--- /dev/null
+++ b/vendor/wait-timeout/src/bin/exit.rs
@@ -0,0 +1,4 @@
+fn main() {
+ let code = std::env::args().nth(1).unwrap().parse().unwrap();
+ std::process::exit(code);
+}
diff --git a/vendor/wait-timeout/src/bin/reader.rs b/vendor/wait-timeout/src/bin/reader.rs
new file mode 100644
index 000000000..94b017e64
--- /dev/null
+++ b/vendor/wait-timeout/src/bin/reader.rs
@@ -0,0 +1,7 @@
+use std::io::{Read, stdin};
+
+#[allow(unused_must_use)]
+fn main() {
+ let mut buffer: [u8; 32] = Default::default();
+ stdin().read(&mut buffer);
+}
diff --git a/vendor/wait-timeout/src/bin/sleep.rs b/vendor/wait-timeout/src/bin/sleep.rs
new file mode 100644
index 000000000..48dabdc02
--- /dev/null
+++ b/vendor/wait-timeout/src/bin/sleep.rs
@@ -0,0 +1,4 @@
+fn main() {
+ let amt = std::env::args().nth(1).unwrap().parse().unwrap();
+ std::thread::sleep(std::time::Duration::from_millis(amt));
+}
diff --git a/vendor/wait-timeout/src/lib.rs b/vendor/wait-timeout/src/lib.rs
new file mode 100644
index 000000000..7c19bf30a
--- /dev/null
+++ b/vendor/wait-timeout/src/lib.rs
@@ -0,0 +1,68 @@
+//! A crate to wait on a child process with a particular timeout.
+//!
+//! This crate is an implementation for Unix and Windows of the ability to wait
+//! on a child process with a timeout specified. On Windows the implementation
+//! is fairly trivial as it's just a call to `WaitForSingleObject` with a
+//! timeout argument, but on Unix the implementation is much more involved. The
+//! current implementation registers a `SIGCHLD` handler and initializes some
+//! global state. This handler also works within multi-threaded environments.
+//! If your application is otherwise handling `SIGCHLD` then bugs may arise.
+//!
+//! # Example
+//!
+//! ```no_run
+//! use std::process::Command;
+//! use wait_timeout::ChildExt;
+//! use std::time::Duration;
+//!
+//! let mut child = Command::new("foo").spawn().unwrap();
+//!
+//! let one_sec = Duration::from_secs(1);
+//! let status_code = match child.wait_timeout(one_sec).unwrap() {
+//! Some(status) => status.code(),
+//! None => {
+//! // child hasn't exited yet
+//! child.kill().unwrap();
+//! child.wait().unwrap().code()
+//! }
+//! };
+//! ```
+
+#![deny(missing_docs, warnings)]
+#![doc(html_root_url = "https://docs.rs/wait-timeout/0.1")]
+
+#[cfg(unix)]
+extern crate libc;
+
+use std::io;
+use std::process::{Child, ExitStatus};
+use std::time::Duration;
+
+#[cfg(unix)] #[path = "unix.rs"]
+mod imp;
+#[cfg(windows)] #[path = "windows.rs"]
+mod imp;
+
+/// Extension methods for the standard `std::process::Child` type.
+pub trait ChildExt {
+ /// Deprecated, use `wait_timeout` instead.
+ #[doc(hidden)]
+ fn wait_timeout_ms(&mut self, ms: u32) -> io::Result<Option<ExitStatus>> {
+ self.wait_timeout(Duration::from_millis(ms as u64))
+ }
+
+ /// Wait for this child to exit, timing out after the duration `dur` has
+ /// elapsed.
+ ///
+ /// If `Ok(None)` is returned then the timeout period elapsed without the
+ /// child exiting, and if `Ok(Some(..))` is returned then the child exited
+ /// with the specified exit code.
+ fn wait_timeout(&mut self, dur: Duration) -> io::Result<Option<ExitStatus>>;
+}
+
+impl ChildExt for Child {
+ fn wait_timeout(&mut self, dur: Duration) -> io::Result<Option<ExitStatus>> {
+ drop(self.stdin.take());
+ imp::wait_timeout(self, dur)
+ }
+}
diff --git a/vendor/wait-timeout/src/unix.rs b/vendor/wait-timeout/src/unix.rs
new file mode 100644
index 000000000..535855378
--- /dev/null
+++ b/vendor/wait-timeout/src/unix.rs
@@ -0,0 +1,305 @@
+//! Unix implementation of waiting for children with timeouts
+//!
+//! On unix, wait() and its friends have no timeout parameters, so there is
+//! no way to time out a thread in wait(). From some googling and some
+//! thinking, it appears that there are a few ways to handle timeouts in
+//! wait(), but the only real reasonable one for a multi-threaded program is
+//! to listen for SIGCHLD.
+//!
+//! With this in mind, the waiting mechanism with a timeout only uses
+//! waitpid() with WNOHANG, but otherwise all the necessary blocking is done by
+//! waiting for a SIGCHLD to arrive (and that blocking has a timeout). Note,
+//! however, that waitpid() is still used to actually reap the child.
+//!
+//! Signal handling is super tricky in general, and this is no exception. Due
+//! to the async nature of SIGCHLD, we use the self-pipe trick to transmit
+//! data out of the signal handler to the rest of the application.
+
+#![allow(bad_style)]
+
+use std::cmp;
+use std::collections::HashMap;
+use std::io::{self, Write, Read};
+use std::os::unix::net::UnixStream;
+use std::mem;
+use std::os::unix::prelude::*;
+use std::process::{Child, ExitStatus};
+use std::sync::{Once, ONCE_INIT, Mutex};
+use std::time::{Duration, Instant};
+
+use libc::{self, c_int};
+
+static INIT: Once = ONCE_INIT;
+static mut STATE: *mut State = 0 as *mut _;
+
+struct State {
+ prev: libc::sigaction,
+ write: UnixStream,
+ read: UnixStream,
+ map: Mutex<StateMap>,
+}
+
+type StateMap = HashMap<*mut Child, (UnixStream, Option<ExitStatus>)>;
+
+pub fn wait_timeout(child: &mut Child, dur: Duration)
+ -> io::Result<Option<ExitStatus>> {
+ INIT.call_once(State::init);
+ unsafe {
+ (*STATE).wait_timeout(child, dur)
+ }
+}
+
+// Do $value as type_of($target)
+macro_rules! _as {
+ ($value:expr, $target:expr) => (
+ {
+ let mut x = $target;
+ x = $value as _;
+ x
+ }
+ )
+}
+
+impl State {
+ #[allow(unused_assignments)]
+ fn init() {
+ unsafe {
+ // Create our "self pipe" and then set both ends to nonblocking
+ // mode.
+ let (read, write) = UnixStream::pair().unwrap();
+ read.set_nonblocking(true).unwrap();
+ write.set_nonblocking(true).unwrap();
+
+ let mut state = Box::new(State {
+ prev: mem::zeroed(),
+ write: write,
+ read: read,
+ map: Mutex::new(HashMap::new()),
+ });
+
+ // Register our sigchld handler
+ let mut new: libc::sigaction = mem::zeroed();
+ new.sa_sigaction = sigchld_handler as usize;
+
+ // FIXME: remove this workaround when the PR to libc get merged and released
+ //
+ // This is a workaround for the type mismatch in the definition of SA_*
+ // constants for android. See https://github.com/rust-lang/libc/pull/511
+ //
+ let sa_flags = new.sa_flags;
+ new.sa_flags = _as!(libc::SA_NOCLDSTOP, sa_flags) |
+ _as!(libc::SA_RESTART, sa_flags) |
+ _as!(libc::SA_SIGINFO, sa_flags);
+
+ assert_eq!(libc::sigaction(libc::SIGCHLD, &new, &mut state.prev), 0);
+
+ STATE = mem::transmute(state);
+ }
+ }
+
+ fn wait_timeout(&self, child: &mut Child, dur: Duration)
+ -> io::Result<Option<ExitStatus>> {
+ // First up, prep our notification pipe which will tell us when our
+ // child has been reaped (other threads may signal this pipe).
+ let (read, write) = UnixStream::pair()?;
+ read.set_nonblocking(true)?;
+ write.set_nonblocking(true)?;
+
+ // Next, take a lock on the map of children currently waiting. Right
+ // after this, **before** we add ourselves to the map, we check to see
+ // if our child has actually already exited via a `try_wait`. If the
+ // child has exited then we return immediately as we'll never otherwise
+ // receive a SIGCHLD notification.
+ //
+ // If the wait reports the child is still running, however, we add
+ // ourselves to the map and then block in `select` waiting for something
+ // to happen.
+ let mut map = self.map.lock().unwrap();
+ if let Some(status) = child.try_wait()? {
+ return Ok(Some(status))
+ }
+ assert!(map.insert(child, (write, None)).is_none());
+ drop(map);
+
+ // Make sure that no matter what when we exit our pointer is removed
+ // from the map.
+ struct Remove<'a> {
+ state: &'a State,
+ child: &'a mut Child,
+ }
+ impl<'a> Drop for Remove<'a> {
+ fn drop(&mut self) {
+ let mut map = self.state.map.lock().unwrap();
+ drop(map.remove(&(self.child as *mut Child)));
+ }
+ }
+ let remove = Remove { state: self, child };
+
+
+ // Alright, we're guaranteed that we'll eventually get a SIGCHLD due
+ // to our `try_wait` failing, and we're also guaranteed that we'll
+ // get notified about this because we're in the map. Next up wait
+ // for an event.
+ //
+ // Note that this happens in a loop for two reasons; we could
+ // receive EINTR or we could pick up a SIGCHLD for other threads but not
+ // actually be ready oureslves.
+ let start = Instant::now();
+ let mut fds = [
+ libc::pollfd {
+ fd: self.read.as_raw_fd(),
+ events: libc::POLLIN,
+
+ revents: 0,
+ },
+ libc::pollfd {
+ fd: read.as_raw_fd(),
+ events: libc::POLLIN,
+ revents: 0,
+ },
+ ];
+ loop {
+ let elapsed = start.elapsed();
+ if elapsed >= dur {
+ break
+ }
+ let timeout = dur - elapsed;
+ let timeout = timeout.as_secs().checked_mul(1_000)
+ .and_then(|amt| {
+ amt.checked_add(timeout.subsec_nanos() as u64 / 1_000_000)
+ })
+ .unwrap_or(u64::max_value());
+ let timeout = cmp::min(<c_int>::max_value() as u64, timeout) as c_int;
+ let r = unsafe {
+ libc::poll(fds.as_mut_ptr(), 2, timeout)
+ };
+ let timeout = match r {
+ 0 => true,
+ n if n > 0 => false,
+ n => {
+ let err = io::Error::last_os_error();
+ if err.kind() == io::ErrorKind::Interrupted {
+ continue
+ } else {
+ panic!("error in select = {}: {}", n, err)
+ }
+ }
+ };
+
+ // Now that something has happened, we need to process what actually
+ // happened. There's are three reasons we could have woken up:
+ //
+ // 1. The file descriptor in our SIGCHLD handler was written to.
+ // This means that a SIGCHLD was received and we need to poll the
+ // entire list of waiting processes to figure out which ones
+ // actually exited.
+ // 2. Our file descriptor was written to. This means that another
+ // thread reaped our child and listed the exit status in the
+ // local map.
+ // 3. We timed out. This means we need to remove ourselves from the
+ // map and simply carry on.
+ //
+ // In the case that a SIGCHLD signal was received, we do that
+ // processing and keep going. If our fd was written to or a timeout
+ // was received then we break out of the loop and return from this
+ // call.
+ let mut map = self.map.lock().unwrap();
+ if drain(&self.read) {
+ self.process_sigchlds(&mut map);
+ }
+
+ if drain(&read) || timeout {
+ break
+ }
+ }
+
+ let mut map = self.map.lock().unwrap();
+ let (_write, ret) = map.remove(&(remove.child as *mut Child)).unwrap();
+ drop(map);
+ Ok(ret)
+ }
+
+ fn process_sigchlds(&self, map: &mut StateMap) {
+ for (&k, &mut (ref write, ref mut status)) in map {
+ // Already reaped, nothing to do here
+ if status.is_some() {
+ continue
+ }
+
+ *status = unsafe { (*k).try_wait().unwrap() };
+ if status.is_some() {
+ notify(write);
+ }
+ }
+ }
+}
+
+fn drain(mut file: &UnixStream) -> bool {
+ let mut ret = false;
+ let mut buf = [0u8; 16];
+ loop {
+ match file.read(&mut buf) {
+ Ok(0) => return true, // EOF == something happened
+ Ok(..) => ret = true, // data read, but keep draining
+ Err(e) => {
+ if e.kind() == io::ErrorKind::WouldBlock {
+ return ret
+ } else {
+ panic!("bad read: {}", e)
+ }
+ }
+ }
+ }
+}
+
+fn notify(mut file: &UnixStream) {
+ match file.write(&[1]) {
+ Ok(..) => {}
+ Err(e) => {
+ if e.kind() != io::ErrorKind::WouldBlock {
+ panic!("bad error on write fd: {}", e)
+ }
+ }
+ }
+}
+
+// Signal handler for SIGCHLD signals, must be async-signal-safe!
+//
+// This function will write to the writing half of the "self pipe" to wake
+// up the helper thread if it's waiting. Note that this write must be
+// nonblocking because if it blocks and the reader is the thread we
+// interrupted, then we'll deadlock.
+//
+// When writing, if the write returns EWOULDBLOCK then we choose to ignore
+// it. At that point we're guaranteed that there's something in the pipe
+// which will wake up the other end at some point, so we just allow this
+// signal to be coalesced with the pending signals on the pipe.
+#[allow(unused_assignments)]
+extern fn sigchld_handler(signum: c_int,
+ info: *mut libc::siginfo_t,
+ ptr: *mut libc::c_void) {
+ type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void);
+ type FnHandler = extern fn(c_int);
+
+ unsafe {
+ let state = &*STATE;
+ notify(&state.write);
+
+ let fnptr = state.prev.sa_sigaction;
+ if fnptr == 0 {
+ return
+ }
+ // FIXME: remove this workaround when the PR to libc get merged and released
+ //
+ // This is a workaround for the type mismatch in the definition of SA_*
+ // constants for android. See https://github.com/rust-lang/libc/pull/511
+ //
+ if state.prev.sa_flags & _as!(libc::SA_SIGINFO, state.prev.sa_flags) == 0 {
+ let action = mem::transmute::<usize, FnHandler>(fnptr);
+ action(signum)
+ } else {
+ let action = mem::transmute::<usize, FnSigaction>(fnptr);
+ action(signum, info, ptr)
+ }
+ }
+}
diff --git a/vendor/wait-timeout/src/windows.rs b/vendor/wait-timeout/src/windows.rs
new file mode 100644
index 000000000..a3aa10468
--- /dev/null
+++ b/vendor/wait-timeout/src/windows.rs
@@ -0,0 +1,34 @@
+use std::io;
+use std::os::windows::prelude::*;
+use std::process::{Child, ExitStatus};
+use std::time::Duration;
+
+type DWORD = u32;
+type HANDLE = *mut u8;
+
+const WAIT_OBJECT_0: DWORD = 0x00000000;
+const WAIT_TIMEOUT: DWORD = 258;
+
+extern "system" {
+ fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD;
+}
+
+pub fn wait_timeout(child: &mut Child, dur: Duration)
+ -> io::Result<Option<ExitStatus>> {
+ let ms = dur.as_secs().checked_mul(1000).and_then(|amt| {
+ amt.checked_add((dur.subsec_nanos() / 1_000_000) as u64)
+ }).expect("failed to convert duration to milliseconds");
+ let ms = if ms > (DWORD::max_value() as u64) {
+ DWORD::max_value()
+ } else {
+ ms as DWORD
+ };
+ unsafe {
+ match WaitForSingleObject(child.as_raw_handle() as *mut _, ms) {
+ WAIT_OBJECT_0 => {}
+ WAIT_TIMEOUT => return Ok(None),
+ _ => return Err(io::Error::last_os_error()),
+ }
+ }
+ child.try_wait()
+}