From 97508096fa5671d64eece96b974cd8d989e4a752 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 00:13:11 -0700 Subject: [PATCH 001/110] process: Initial commit --- .gitignore | 2 + Cargo.toml | 12 +++++ src/bin/exit.rs | 3 ++ src/lib.rs | 124 ++++++++++++++++++++++++++++++++++++++++++++++++ src/unix.rs | 115 ++++++++++++++++++++++++++++++++++++++++++++ tests/smoke.rs | 54 +++++++++++++++++++++ 6 files changed, 310 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/bin/exit.rs create mode 100644 src/lib.rs create mode 100644 src/unix.rs create mode 100644 tests/smoke.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..a9d37c560 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..2d3bc940a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tokio-process" +version = "0.1.0" +authors = ["Alex Crichton "] + +[dependencies] +tokio-core = { git = "https://github.com/tokio-rs/tokio-core" } +futures = { git = "https://github.com/alexcrichton/futures-rs" } + +[target.'cfg(unix)'.dependencies] +libc = "0.2" +tokio-signal = { git = "https://github.com/alexcrichton/tokio-signal" } diff --git a/src/bin/exit.rs b/src/bin/exit.rs new file mode 100644 index 000000000..73ce00d17 --- /dev/null +++ b/src/bin/exit.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(std::env::args().nth(1).unwrap().parse().unwrap()); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..6e499b9fa --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,124 @@ +#[macro_use] +extern crate futures; +extern crate tokio_core; + +use std::ffi::OsStr; +use std::io; +use std::path::Path; +use std::process::{self, ExitStatus}; + +use futures::{Future, Poll}; +use tokio_core::LoopHandle; + +#[path = "unix.rs"] +mod imp; + +pub struct Command { + inner: process::Command, + handle: LoopHandle, +} + +pub struct Spawn { + inner: Box>, +} + +pub struct Child { + inner: imp::Child, +} + +impl Command { + pub fn new>(exe: T, handle: &LoopHandle) -> Command { + Command::_new(exe.as_ref(), handle) + } + + fn _new(exe: &OsStr, handle: &LoopHandle) -> Command { + Command { + inner: process::Command::new(exe), + handle: handle.clone(), + } + } + + pub fn arg>(&mut self, arg: S) -> &mut Command { + self._arg(arg.as_ref()) + } + + fn _arg(&mut self, arg: &OsStr) -> &mut Command { + self.inner.arg(arg); + self + } + + pub fn args>(&mut self, args: &[S]) -> &mut Command { + for arg in args { + self._arg(arg.as_ref()); + } + self + } + + pub fn env(&mut self, key: K, val: V) -> &mut Command + where K: AsRef, V: AsRef + { + self._env(key.as_ref(), val.as_ref()) + } + + fn _env(&mut self, key: &OsStr, val: &OsStr) -> &mut Command { + self.inner.env(key, val); + self + } + + pub fn env_remove>(&mut self, key: K) -> &mut Command { + self._env_remove(key.as_ref()) + } + + fn _env_remove(&mut self, key: &OsStr) -> &mut Command { + self.inner.env_remove(key); + self + } + + pub fn env_clear(&mut self) -> &mut Command { + self.inner.env_clear(); + self + } + + pub fn current_dir>(&mut self, dir: P) -> &mut Command { + self._current_dir(dir.as_ref()) + } + + fn _current_dir(&mut self, dir: &Path) -> &mut Command { + self.inner.current_dir(dir); + self + } + + pub fn spawn(self) -> Spawn { + Spawn { + inner: Box::new(imp::spawn(self).map(|c| Child { inner: c })), + } + } +} + +impl Future for Spawn { + type Item = Child; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + self.inner.poll() + } +} + +impl Child { + pub fn id(&self) -> u32 { + self.inner.id() + } + + pub fn kill(&mut self) -> io::Result<()> { + self.inner.kill() + } +} + +impl Future for Child { + type Item = ExitStatus; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + self.inner.poll() + } +} diff --git a/src/unix.rs b/src/unix.rs new file mode 100644 index 000000000..fbc780c7a --- /dev/null +++ b/src/unix.rs @@ -0,0 +1,115 @@ +extern crate libc; +extern crate tokio_signal; + +use std::io; +use std::os::unix::prelude::*; +use std::process::{self, ExitStatus}; + +use futures::stream::Stream; +use futures::{Future, Poll, Async}; +use self::libc::c_int; +use self::tokio_signal::unix::Signal; + +use Command; + +pub struct Child { + child: process::Child, + reaped: bool, + sigchld: Signal, +} + +/// Spawns a new child process. +/// +/// Right now the only "fancy" thing about this is how we implement the +/// `Future` implementation on `Child` to get the exit status. Unix offers +/// no way to register a child with epoll, and the only real way to get a +/// notification when a process exits is the SIGCHLD signal. +/// +/// Signal handling in general is *super* hairy and complicated, and it's even +/// more complicated here with the fact that signals are coalesced, so we may +/// not get a SIGCHLD-per-child. +/// +/// Our best approximation here is to check *all spawned processes* for all +/// SIGCHLD signals received. To do that we create a `Signal`, implemented in +/// the `tokio-signal` crate, which is a stream over signals being received. +/// +/// Later when we poll the process's exit status we simply check to see if a +/// SIGCHLD has happened since we last checked, and while that returns "yes" we +/// keep trying. +/// +/// Note that this means that this isn't really scalable, but then again +/// processes in general aren't scalable (e.g. millions) so it shouldn't be that +/// bad in theory... +pub fn spawn(mut cmd: Command) -> Box> { + Box::new(Signal::new(libc::SIGCHLD, &cmd.handle).and_then(move |sigchld| { + cmd.inner.spawn().map(|c| { + Child { + child: c, + reaped: false, + sigchld: sigchld + } + }) + })) +} + +impl Child { + pub fn id(&self) -> u32 { + self.child.id() + } + + pub fn kill(&mut self) -> io::Result<()> { + if self.reaped { + Ok(()) + } else { + self.child.kill() + } + } +} + +impl Future for Child { + type Item = ExitStatus; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + assert!(!self.reaped); + loop { + // Ensure that once we've successfully waited we won't try to + // `kill` above. + if let Some(e) = try!(try_wait(&self.child)) { + self.reaped = true; + return Ok(e.into()) + } + + // If the child hasn't exited yet, then it's our responsibility to + // ensure the current task gets notified when it might be able to + // make progress. + // + // As described in `spawn` above, we just indicate that we can + // next make progress once a SIGCHLD is received. + if try!(self.sigchld.poll()).is_not_ready() { + return Ok(Async::NotReady) + } + } + } +} + +pub fn try_wait(child: &process::Child) -> io::Result> { + let id = child.id() as c_int; + let mut status = 0; + loop { + match unsafe { libc::waitpid(id, &mut status, libc::WNOHANG) } { + 0 => return Ok(None), + n if n < 0 => { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue + } + return Err(err) + } + n => { + assert_eq!(n, id); + return Ok(Some(ExitStatus::from_raw(status))) + } + } + } +} diff --git a/tests/smoke.rs b/tests/smoke.rs new file mode 100644 index 000000000..4e0a65006 --- /dev/null +++ b/tests/smoke.rs @@ -0,0 +1,54 @@ +extern crate futures; +extern crate tokio_core; +extern crate tokio_process; + +use std::env; +use std::sync::mpsc::channel; +use std::sync::{Once, ONCE_INIT}; +use std::thread; + +use futures::Future; +use futures::stream::Stream; +use tokio_core::{Loop, LoopHandle}; +use tokio_process::Command; + +static INIT: Once = ONCE_INIT; + +fn init() { + INIT.call_once(|| { + let (tx, rx) = channel(); + thread::spawn(move || { + let mut lp = Loop::new().unwrap(); + let cmd = exit(&lp.handle()); + let mut child = lp.run(cmd.spawn()).unwrap(); + drop(child.kill()); + lp.run(child).unwrap(); + tx.send(()).unwrap(); + drop(lp.run(futures::empty::<(), ()>())); + }); + rx.recv().unwrap(); + }); +} + +fn exit(handle: &LoopHandle) -> Command { + let mut me = env::current_exe().unwrap(); + me.pop(); + me.push("exit"); + Command::new(me, handle) +} + +#[test] +fn simple() { + init(); + + let mut lp = Loop::new().unwrap(); + let mut cmd = exit(&lp.handle()); + cmd.arg("2"); + let mut child = lp.run(cmd.spawn()).unwrap(); + let id = child.id(); + assert!(id > 0); + let status = lp.run(&mut child).unwrap(); + assert_eq!(status.code(), Some(2)); + assert_eq!(child.id(), id); + assert!(child.kill().is_ok()); +} From eef655f3b1c97399c9c0b5a89cd5cb0d0b5ded31 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 12:04:46 -0700 Subject: [PATCH 002/110] process: Add a Windows implementation --- Cargo.toml | 4 ++ src/lib.rs | 6 +++ src/windows.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 src/windows.rs diff --git a/Cargo.toml b/Cargo.toml index 2d3bc940a..04932379d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,10 @@ authors = ["Alex Crichton "] tokio-core = { git = "https://github.com/tokio-rs/tokio-core" } futures = { git = "https://github.com/alexcrichton/futures-rs" } +[target.'cfg(windows)'.dependencies] +winapi = "0.2" +kernel32-sys = "0.2" + [target.'cfg(unix)'.dependencies] libc = "0.2" tokio-signal = { git = "https://github.com/alexcrichton/tokio-signal" } diff --git a/src/lib.rs b/src/lib.rs index 6e499b9fa..61523c517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,10 +11,16 @@ use futures::{Future, Poll}; use tokio_core::LoopHandle; #[path = "unix.rs"] +#[cfg(unix)] +mod imp; + +#[path = "windows.rs"] +#[cfg(windows)] mod imp; pub struct Command { inner: process::Command, + #[allow(dead_code)] handle: LoopHandle, } diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 000000000..c20160281 --- /dev/null +++ b/src/windows.rs @@ -0,0 +1,123 @@ +extern crate winapi; +extern crate kernel32; + +use std::io; +use std::os::windows::prelude::*; +use std::os::windows::process::ExitStatusExt; +use std::process::{self, ExitStatus}; + +use futures::{self, Future, Poll, Async, Oneshot, Complete, oneshot, Fuse}; + +use Command; + +pub struct Child { + child: process::Child, + waiting: Option, +} + +struct Waiting { + rx: Fuse>, + wait_object: winapi::HANDLE, + tx: *mut Option>, +} + +unsafe impl Sync for Waiting {} +unsafe impl Send for Waiting {} + +pub fn spawn(mut cmd: Command) -> Box> { + Box::new(futures::done(cmd.inner.spawn().map(|c| { + Child { + child: c, + waiting: None, + } + }))) +} + +impl Child { + pub fn id(&self) -> u32 { + self.child.id() + } + + pub fn kill(&mut self) -> io::Result<()> { + self.child.kill() + } +} + +impl Future for Child { + type Item = ExitStatus; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + loop { + if let Some(ref mut w) = self.waiting { + match w.rx.poll().expect("should not be canceled") { + Async::Ready(()) => {} + Async::NotReady => return Ok(Async::NotReady), + } + let status = try!(try_wait(&self.child)).expect("not ready yet"); + return Ok(status.into()) + } + + if let Some(e) = try!(try_wait(&self.child)) { + return Ok(e.into()) + } + let (tx, rx) = oneshot(); + let ptr = Box::into_raw(Box::new(Some(tx))); + let mut wait_object = 0 as *mut _; + let rc = unsafe { + kernel32::RegisterWaitForSingleObject(&mut wait_object, + self.child.as_raw_handle(), + Some(callback), + ptr as *mut _, + winapi::INFINITE, + winapi::WT_EXECUTEINWAITTHREAD | + winapi::WT_EXECUTEONLYONCE) + }; + if rc == 0 { + drop(unsafe { Box::from_raw(ptr) }); + return Err(io::Error::last_os_error()) + } + self.waiting = Some(Waiting { + rx: rx.fuse(), + wait_object: wait_object, + tx: ptr, + }); + } + } +} + +impl Drop for Waiting { + fn drop(&mut self) { + unsafe { + let rc = kernel32::UnregisterWaitEx(self.wait_object, + winapi::INVALID_HANDLE_VALUE); + if rc == 0 { + panic!("failed to unregister: {}", io::Error::last_os_error()); + } + drop(Box::from_raw(self.tx)); + } + } +} + +unsafe extern "system" fn callback(ptr: winapi::PVOID, + _timer_fired: winapi::BOOLEAN) { + let mut complete = Box::from_raw(ptr as *mut Option>); + complete.take().unwrap().complete(()); +} + +pub fn try_wait(child: &process::Child) -> io::Result> { + unsafe { + match kernel32::WaitForSingleObject(child.as_raw_handle(), 0) { + winapi::WAIT_OBJECT_0 => {} + winapi::WAIT_TIMEOUT => return Ok(None), + _ => return Err(io::Error::last_os_error()), + } + let mut status = 0; + let rc = kernel32::GetExitCodeProcess(child.as_raw_handle(), &mut status); + if rc == winapi::FALSE { + Err(io::Error::last_os_error()) + } else { + Ok(Some(ExitStatus::from_raw(status))) + } + } +} From 649fa13a15a43f07ec51edcd6aed08886a2af9b4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 12:07:00 -0700 Subject: [PATCH 003/110] process: Remove unused imports --- tests/smoke.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/smoke.rs b/tests/smoke.rs index 4e0a65006..e3bce9632 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -7,8 +7,6 @@ use std::sync::mpsc::channel; use std::sync::{Once, ONCE_INIT}; use std::thread; -use futures::Future; -use futures::stream::Stream; use tokio_core::{Loop, LoopHandle}; use tokio_process::Command; From 413e1b78a747861174c6a77f1dde5361abdf3653 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 12:56:43 -0700 Subject: [PATCH 004/110] process: Fix a segfault on windows --- src/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows.rs b/src/windows.rs index c20160281..48667f8b8 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -101,7 +101,7 @@ impl Drop for Waiting { unsafe extern "system" fn callback(ptr: winapi::PVOID, _timer_fired: winapi::BOOLEAN) { - let mut complete = Box::from_raw(ptr as *mut Option>); + let mut complete = &mut *(ptr as *mut Option>); complete.take().unwrap().complete(()); } From f4f7bb232e49ec4b671d4ba18a516b45ea6c5e15 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 12:59:46 -0700 Subject: [PATCH 005/110] process: Fix a test on Windows --- tests/smoke.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/smoke.rs b/tests/smoke.rs index e3bce9632..8d5fd011f 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -48,5 +48,5 @@ fn simple() { let status = lp.run(&mut child).unwrap(); assert_eq!(status.code(), Some(2)); assert_eq!(child.id(), id); - assert!(child.kill().is_ok()); + drop(child.kill()); } From 4bd07ac6aae571bfeeef69f7a2ef399e944d4ebd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 13:05:39 -0700 Subject: [PATCH 006/110] process: Add metadata info --- .travis.yml | 24 ++++++ Cargo.toml | 7 ++ LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 25 ++++++ README.md | 32 ++++++++ appveyor.yml | 15 ++++ 6 files changed, 304 insertions(+) create mode 100644 .travis.yml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 appveyor.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..e98c3c03e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: rust + +rust: + - stable + - beta + - nightly +sudo: false +before_script: + - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH +script: + - cargo build + - cargo test + - cargo doc --no-deps +after_success: + - travis-cargo --only nightly doc-upload +env: + global: + - secure: baOI3mpetAfhZqrqv+dWniOz7cUxrIQ8IdIO2RkPkeyz1RcqGN57br0/lL/AVoG89lmcLoen/RXUw81a0c/uPSS3ybdu9I5HWDx8iVjjfUHBc2hevmYv2FaYlXJANoGMc7TlluGdS8gYqSuMFKlibzCosLnMy7JHiLqLhQqndcCCAca98JwfgUguckXG/Yg8RDp/5ueFJMXe5vjMk3iblSfomMyLRn72hmvOXSb9Oxd8+mgnWZ4DKs2cnO4KC3o8hrvjAHGPEIKBIZ0udsd1rCKQjd8D4D8pe+k7ULFs9WOISJVEIlzw9Vl985Ne/C5lxraDdEEPOHuqOjx32p5wyZNlcMlLXfOxF8WhK/lUhCEqa0W4Bx25cegkz30JhFGn0g8zJd2Ztf6y/U+DM+t7vhE+AdB/XnwrUM100qlsFHmZ+oXxJ9TWenw3bGK4uKIUI4Zhjr+L1AFs57K5OMIX6t2zWkXRhyKuQjWAQ4CJUTaH5LxOIJK7NOtYeG7uVt29iM3s4dE8OuaZpoq2ZqeU+8S0xIzPlndi/QGdYQKwChbBk8bYkLyX3Jpja4A9ASjBrZXTdeBUJz57gYXkvAVL0+l9MAVYCxFEddi2zgt/HoK4Vz4obKHi+TA7/NqZVW2K09XUWI/CpTmmd490dUDz/TQMm+sRFL4wCpwGw2NQbwA= +notifications: + email: + on_success: never +os: + - linux + - osx diff --git a/Cargo.toml b/Cargo.toml index 04932379d..cab0776dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,13 @@ name = "tokio-process" version = "0.1.0" authors = ["Alex Crichton "] +license = "MIT/Apache-2.0" +repository = "https://github.com/alexcrichton/tokio-process" +homepage = "https://github.com/alexcrichton/tokio-process" +documentation = "https://alexcrichton.github.io/tokio-process" +description = """ +An implementation of an asynchronous process management backed futures. +""" [dependencies] tokio-core = { git = "https://github.com/tokio-rs/tokio-core" } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..28e630cf4 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2016 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..1e118ebfe --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# tokio-process + +An implementation of process management for Tokio + +[![Build Status](https://travis-ci.org/alexcrichton/tokio-process.svg?branch=master)](https://travis-ci.org/alexcrichton/tokio-process) + +[Documentation](https://alexcrichton.github.io/tokio-process) + +## Usage + +First, add this to your `Cargo.toml`: + +```toml +[dependencies] +tokio-process = { git = "https://github.com/alexcrichton/tokio-process" } +``` + +Next, add this to your crate: + +```rust +extern crate tokio_process; +``` + +# License + +`tokio-process` is primarily distributed under the terms of both the MIT +license and the Apache License (Version 2.0), with portions covered by various +BSD-like licenses. + +See LICENSE-APACHE, and LICENSE-MIT for details. + + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..75c11da0d --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,15 @@ +environment: + matrix: + - TARGET: x86_64-pc-windows-msvc +install: + - curl -sSf -o rustup-init.exe https://win.rustup.rs/ + - rustup-init.exe -y --default-host %TARGET% + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -V + - cargo -V + +build: false + +test_script: + - cargo build + - cargo test From 5e68b0d51d7a857919468976cea655a379457279 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 13:10:10 -0700 Subject: [PATCH 007/110] process: Don't build on stable, start w/ beta for now --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e98c3c03e..70c2cdc63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: rust rust: - - stable + #- stable - beta - nightly sudo: false diff --git a/appveyor.yml b/appveyor.yml index 75c11da0d..9c96de43c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ environment: - TARGET: x86_64-pc-windows-msvc install: - curl -sSf -o rustup-init.exe https://win.rustup.rs/ - - rustup-init.exe -y --default-host %TARGET% + - rustup-init.exe -y --default-host %TARGET% --default-toolchain beta - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - rustc -V - cargo -V From 31c81faf965ca38e0787cfcbac3dd7ce80cbcb24 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 16:16:30 -0700 Subject: [PATCH 008/110] process: Add appveyor to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1e118ebfe..e279cb6cc 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ An implementation of process management for Tokio [![Build Status](https://travis-ci.org/alexcrichton/tokio-process.svg?branch=master)](https://travis-ci.org/alexcrichton/tokio-process) +[![Build status](https://ci.appveyor.com/api/projects/status/43c8g7fy801e5902?svg=true)](https://ci.appveyor.com/project/alexcrichton/tokio-process) [Documentation](https://alexcrichton.github.io/tokio-process) From 073a1a251ad1e2ddff9b418799306dafee7d417c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 22:15:13 -0700 Subject: [PATCH 009/110] process: Track tokio-core master --- src/lib.rs | 8 ++++---- tests/smoke.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 61523c517..b8b44d46c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ use std::path::Path; use std::process::{self, ExitStatus}; use futures::{Future, Poll}; -use tokio_core::LoopHandle; +use tokio_core::reactor::Handle; #[path = "unix.rs"] #[cfg(unix)] @@ -21,7 +21,7 @@ mod imp; pub struct Command { inner: process::Command, #[allow(dead_code)] - handle: LoopHandle, + handle: Handle, } pub struct Spawn { @@ -33,11 +33,11 @@ pub struct Child { } impl Command { - pub fn new>(exe: T, handle: &LoopHandle) -> Command { + pub fn new>(exe: T, handle: &Handle) -> Command { Command::_new(exe.as_ref(), handle) } - fn _new(exe: &OsStr, handle: &LoopHandle) -> Command { + fn _new(exe: &OsStr, handle: &Handle) -> Command { Command { inner: process::Command::new(exe), handle: handle.clone(), diff --git a/tests/smoke.rs b/tests/smoke.rs index 8d5fd011f..9e6949d74 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -7,7 +7,7 @@ use std::sync::mpsc::channel; use std::sync::{Once, ONCE_INIT}; use std::thread; -use tokio_core::{Loop, LoopHandle}; +use tokio_core::reactor::{Core, Handle}; use tokio_process::Command; static INIT: Once = ONCE_INIT; @@ -16,7 +16,7 @@ fn init() { INIT.call_once(|| { let (tx, rx) = channel(); thread::spawn(move || { - let mut lp = Loop::new().unwrap(); + let mut lp = Core::new().unwrap(); let cmd = exit(&lp.handle()); let mut child = lp.run(cmd.spawn()).unwrap(); drop(child.kill()); @@ -28,7 +28,7 @@ fn init() { }); } -fn exit(handle: &LoopHandle) -> Command { +fn exit(handle: &Handle) -> Command { let mut me = env::current_exe().unwrap(); me.pop(); me.push("exit"); @@ -39,7 +39,7 @@ fn exit(handle: &LoopHandle) -> Command { fn simple() { init(); - let mut lp = Loop::new().unwrap(); + let mut lp = Core::new().unwrap(); let mut cmd = exit(&lp.handle()); cmd.arg("2"); let mut child = lp.run(cmd.spawn()).unwrap(); From 72179d49c5dabf3cb7267d899347a16f6340c9e5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 9 Sep 2016 22:04:51 -0700 Subject: [PATCH 010/110] process: Update to crates.io versions of deps --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cab0776dc..112201de5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,8 @@ An implementation of an asynchronous process management backed futures. """ [dependencies] -tokio-core = { git = "https://github.com/tokio-rs/tokio-core" } -futures = { git = "https://github.com/alexcrichton/futures-rs" } +tokio-core = "0.1" +futures = "0.1" [target.'cfg(windows)'.dependencies] winapi = "0.2" @@ -20,4 +20,4 @@ kernel32-sys = "0.2" [target.'cfg(unix)'.dependencies] libc = "0.2" -tokio-signal = { git = "https://github.com/alexcrichton/tokio-signal" } +tokio-signal = "0.1" From 56222c588b50b9990e61aa652916d362edbaa386 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Oct 2016 16:43:29 -0700 Subject: [PATCH 011/110] process: pass --target on appveyor --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9c96de43c..2f7e9cbc3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,5 +11,5 @@ install: build: false test_script: - - cargo build - - cargo test + - cargo build --target %TARGET% + - cargo test --target %TARGET% From 4416ea07d8dbbff2f6ee2886975531afba9cba07 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 19 Nov 2016 09:11:53 -0800 Subject: [PATCH 012/110] process: Update travis token --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 70c2cdc63..757e02669 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,8 @@ after_success: - travis-cargo --only nightly doc-upload env: global: - - secure: baOI3mpetAfhZqrqv+dWniOz7cUxrIQ8IdIO2RkPkeyz1RcqGN57br0/lL/AVoG89lmcLoen/RXUw81a0c/uPSS3ybdu9I5HWDx8iVjjfUHBc2hevmYv2FaYlXJANoGMc7TlluGdS8gYqSuMFKlibzCosLnMy7JHiLqLhQqndcCCAca98JwfgUguckXG/Yg8RDp/5ueFJMXe5vjMk3iblSfomMyLRn72hmvOXSb9Oxd8+mgnWZ4DKs2cnO4KC3o8hrvjAHGPEIKBIZ0udsd1rCKQjd8D4D8pe+k7ULFs9WOISJVEIlzw9Vl985Ne/C5lxraDdEEPOHuqOjx32p5wyZNlcMlLXfOxF8WhK/lUhCEqa0W4Bx25cegkz30JhFGn0g8zJd2Ztf6y/U+DM+t7vhE+AdB/XnwrUM100qlsFHmZ+oXxJ9TWenw3bGK4uKIUI4Zhjr+L1AFs57K5OMIX6t2zWkXRhyKuQjWAQ4CJUTaH5LxOIJK7NOtYeG7uVt29iM3s4dE8OuaZpoq2ZqeU+8S0xIzPlndi/QGdYQKwChbBk8bYkLyX3Jpja4A9ASjBrZXTdeBUJz57gYXkvAVL0+l9MAVYCxFEddi2zgt/HoK4Vz4obKHi+TA7/NqZVW2K09XUWI/CpTmmd490dUDz/TQMm+sRFL4wCpwGw2NQbwA= + - secure: "mTrrxm6AHgbh+k6/GKhKwQoKmLF4tZQLZ7671jvJ42Yu3U6mH4xGWnQDEQ6E883SvoBi0W5KwsvRqKRqNpPXYbWIMsS46gpMu0jEL6uz7+zwip64847OdbXAbS8NZsnXhS0w5b9dYdQUCoj71TrbWGVS/sqNb2twn+GJGIqfsjUHRnkHLIMmwoILgzYMbd3d1Jy/KlicIGtHq8Sb23EVr7tdkN++k21ZSDbmD+q5Pmf9MZH3yyk2YIpgCooVzqYAtS8Ua6ug1L+u3MBDWtqUFEOxGP5ya1+s312TGsBShaVvtQrH2IFOG+izdVjvRkpeM/FJXlqQAh02VxHK8ST9B6zjYO7Mnn8yT4gAA5PfoMlwZ5UxYQmr5fcjPAWvUkIdb6qRdqEupBbtCGO4EWTi2Gw8/w8tZGMS3JqPSsl8QJCu+fj3FWRwRwKmUaFIgIojbJQ3NCRCu9RGdbOoOOlhjYbw51lN9kxGxHLiiZzEtxv+nay5vNqIVkprRz4gbXOAi27Fglz2rYie88vb7XgrAVnaoahBA7C6l7KXZ/W3EuDDGjlxChj0kAqKz9AbbKAkYGN02hOn2Y26ag/THd4LKNE7QfcaEBKfJx3YEKVBI0fmoyT+iT1RMr9vmEySJmgZXHgd6TTIIpvIN8ouIy6LxzQby0bgP1++NSH05jgf/Sk=" + notifications: email: on_success: never From 5664660156b74eca0c4a34de13051e936948a515 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 19 Nov 2016 10:03:43 -0800 Subject: [PATCH 013/110] process: Fix tests on nightly --- tests/smoke.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/smoke.rs b/tests/smoke.rs index 9e6949d74..b156920a6 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -31,6 +31,9 @@ fn init() { fn exit(handle: &Handle) -> Command { let mut me = env::current_exe().unwrap(); me.pop(); + if me.ends_with("deps") { + me.pop(); + } me.push("exit"); Command::new(me, handle) } From b16a8613b1a6e001f1607d9f7186230e0a878b7b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 19 Nov 2016 10:03:51 -0800 Subject: [PATCH 014/110] process: Test on stable --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 757e02669..57c9e65ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: rust rust: - #- stable + - stable - beta - nightly sudo: false From 849a5ad0b2a2f2e5814d18f04b31610fc36d265a Mon Sep 17 00:00:00 2001 From: Andreas Rottmann Date: Fri, 18 Nov 2016 20:57:31 +0100 Subject: [PATCH 015/110] process: Add support for stdio streams --- Cargo.toml | 1 + src/bin/cat.rs | 18 +++++++++++ src/lib.rs | 30 ++++++++++++++++++ src/unix.rs | 67 ++++++++++++++++++++++++++++++++++++--- tests/stdio.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 src/bin/cat.rs create mode 100644 tests/stdio.rs diff --git a/Cargo.toml b/Cargo.toml index 112201de5..8269fabc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ An implementation of an asynchronous process management backed futures. [dependencies] tokio-core = "0.1" futures = "0.1" +mio = "0.6" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/src/bin/cat.rs b/src/bin/cat.rs new file mode 100644 index 000000000..8118d2bea --- /dev/null +++ b/src/bin/cat.rs @@ -0,0 +1,18 @@ +// A cat-like utility that can be used as a subprocess to test I/O +// stream communication. +use std::io; +use std::io::Write; + +fn main() { + let stdin = io::stdin(); + let mut stdout = io::stdout(); + let mut line = String::new(); + loop { + line.clear(); + stdin.read_line(&mut line).unwrap(); + if line.len() == 0 { + break; + } + stdout.write(line.as_bytes()).unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index b8b44d46c..c72887cd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #[macro_use] extern crate futures; extern crate tokio_core; +extern crate mio; use std::ffi::OsStr; use std::io; @@ -18,6 +19,9 @@ mod imp; #[cfg(windows)] mod imp; +pub use imp::ChildStdin; +pub use imp::ChildStdout; + pub struct Command { inner: process::Command, #[allow(dead_code)] @@ -94,6 +98,20 @@ impl Command { self } + pub fn stdin(&mut self, cfg: process::Stdio) -> &mut Self { + self.inner.stdin(cfg); + self + } + + pub fn stdout(&mut self, cfg: process::Stdio) -> &mut Self { + self.inner.stdout(cfg); + self + } + pub fn stderr(&mut self, cfg: process::Stdio) -> &mut Self { + self.inner.stderr(cfg); + self + } + pub fn spawn(self) -> Spawn { Spawn { inner: Box::new(imp::spawn(self).map(|c| Child { inner: c })), @@ -118,6 +136,18 @@ impl Child { pub fn kill(&mut self) -> io::Result<()> { self.inner.kill() } + + pub fn stdin(&mut self) -> &mut Option { + &mut self.inner.stdin + } + + pub fn stdout(&mut self) -> &mut Option { + &mut self.inner.stdout + } + + pub fn stderr(&mut self) -> &mut Option { + &mut self.inner.stderr + } } impl Future for Child { diff --git a/src/unix.rs b/src/unix.rs index fbc780c7a..aa79141c2 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -1,3 +1,4 @@ +extern crate mio; extern crate libc; extern crate tokio_signal; @@ -7,15 +8,67 @@ use std::process::{self, ExitStatus}; use futures::stream::Stream; use futures::{Future, Poll, Async}; +use tokio_core::reactor::{Handle,PollEvented}; use self::libc::c_int; use self::tokio_signal::unix::Signal; +use mio::{Evented,PollOpt,Ready,Token}; +use mio::unix::EventedFd; + use Command; pub struct Child { child: process::Child, reaped: bool, sigchld: Signal, + pub stdin: Option, + pub stdout: Option, + pub stderr: Option, +} + +struct RawFdWrap(T); + +pub struct StdStream { + io: PollEvented>, +} + +pub type ChildStdin = StdStream; +pub type ChildStdout = StdStream; +pub type ChildStderr = StdStream; + +impl Evented for RawFdWrap where T: AsRawFd { + fn register(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt) -> io::Result<()> { + EventedFd(&self.0.as_raw_fd()).register(poll, token, interest, opts) + } + fn reregister(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt) -> io::Result<()> { + EventedFd(&self.0.as_raw_fd()).reregister(poll, token, interest, opts) + } + fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { + EventedFd(&self.0.as_raw_fd()).deregister(poll) + } +} + +impl io::Read for StdStream where T: io::Read { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.io.get_mut().0.read(buf) + } +} + +impl io::Write for StdStream where T: io::Write { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.io.get_mut().0.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.io.get_mut().0.flush() + } +} + +fn stdio(option: &mut Option, handle: &Handle) -> Result>, io::Error> + where T: AsRawFd { + + option.take().map_or(Ok(None), |stream| { + PollEvented::new(RawFdWrap(stream), handle).map(|io| Some(StdStream { io: io })) + }) } /// Spawns a new child process. @@ -42,12 +95,18 @@ pub struct Child { /// bad in theory... pub fn spawn(mut cmd: Command) -> Box> { Box::new(Signal::new(libc::SIGCHLD, &cmd.handle).and_then(move |sigchld| { - cmd.inner.spawn().map(|c| { - Child { + cmd.inner.spawn().and_then(|mut c| { + let stdin = try!(stdio(&mut c.stdin, &cmd.handle)); + let stdout = try!(stdio(&mut c.stdout, &cmd.handle)); + let stderr = try!(stdio(&mut c.stderr, &cmd.handle)); + Ok(Child { child: c, reaped: false, - sigchld: sigchld - } + sigchld: sigchld, + stdin: stdin, + stdout: stdout, + stderr: stderr, + }) }) })) } diff --git a/tests/stdio.rs b/tests/stdio.rs new file mode 100644 index 000000000..4a551b63e --- /dev/null +++ b/tests/stdio.rs @@ -0,0 +1,85 @@ +extern crate futures; +#[macro_use] +extern crate tokio_core; +extern crate tokio_process; + +use std::env; +use std::io; +use std::process::Stdio; + +use futures::{Future, BoxFuture}; +use futures::stream::{self, Stream}; +use tokio_core::io::{read_until, write_all}; +use tokio_core::reactor::{Core, Handle}; +use tokio_process::{Command, Child}; + +fn cat(handle: &Handle) -> Command { + let mut path = env::current_exe().unwrap(); + path.pop(); + path.push("cat"); + let mut cmd = Command::new(path, handle); + cmd.stdin(Stdio::piped()) + .stdout(Stdio::piped()); + cmd +} + +fn feed_cat(cat: &mut Child, n: usize) -> BoxFuture<(), io::Error> { + let stdin = cat.stdin().take().unwrap(); + let stdout = cat.stdout().take().unwrap(); + + // Produce n lines on the child's stdout. + let numbers = stream::iter((0..n).into_iter().map(Ok)); + let write = numbers.fold(stdin, |stdin, i| { + write_all(stdin, format!("line {}\n", i).into_bytes()).map(|(writer, _)| writer) + }).map(|_| {}); + + // Try to read `n + 1` lines, ensuring the last one is empty + // (i.e. EOF is reached after `n` lines. + let reader = io::BufReader::new(stdout); + let expected_numbers = stream::iter((0..n + 1).into_iter().map(Ok)); + let read = expected_numbers.fold((reader, 0), move |(reader, i), _| { + let done = i >= n; + read_until(reader, b'\n', Vec::new()).and_then(move |(reader, vec)| { + match (done, vec.len()) { + (false, 0) => { + Err(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe")) + }, + (true, n) if n != 0 => { + Err(io::Error::new(io::ErrorKind::Other, "extraneous data")) + }, + _ => { + let s = std::str::from_utf8(&vec).unwrap(); + let expected = format!("line {}\n", i); + if done || s == expected { + Ok((reader, i + 1)) + } else { + Err(io::Error::new(io::ErrorKind::Other, "unexpected data")) + } + } + } + }) + }); + // Compose reading and writing concurrently. + write.join(read).map(|_| {}).boxed() +} + +#[test] +/// Check for the following properties when feeding stdin and +/// consuming stdout of a cat-like process: +/// +/// - A number of lines that amounts to a number of bytes exceeding a +/// typical OS buffer size can be fed to the child without +/// deadlock. This tests that we also consume the stdout +/// concurrently; otherwise this would deadlock. +/// +/// - We read the same lines from the child that we fed it. +// +/// - The child does produce EOF on stdout after the last line. +fn cat_loop() { + let mut lp = Core::new().unwrap(); + let cmd = cat(&lp.handle()); + let mut child = lp.run(cmd.spawn()).unwrap(); + lp.run(feed_cat(&mut child, 10000)).unwrap(); + let status = lp.run(&mut child).unwrap(); + assert_eq!(status.code(), Some(0)); +} From 97ebb2275cb4fd57b8fe7da73505e069cbbf9784 Mon Sep 17 00:00:00 2001 From: Andreas Rottmann Date: Sun, 4 Dec 2016 18:47:48 +0100 Subject: [PATCH 016/110] process: [WIP] Actually be non-blocking --- Cargo.toml | 7 ++++++ src/bin/cat.rs | 1 + src/lib.rs | 2 ++ src/unix.rs | 64 ++++++++++++++++++++++++++++++++++++++++++-------- tests/stdio.rs | 27 ++++++++++++++------- 5 files changed, 83 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8269fabc7..da60131b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ An implementation of an asynchronous process management backed futures. tokio-core = "0.1" futures = "0.1" mio = "0.6" +log = "0.3" +env_logger = "0.3" [target.'cfg(windows)'.dependencies] winapi = "0.2" @@ -21,4 +23,9 @@ kernel32-sys = "0.2" [target.'cfg(unix)'.dependencies] libc = "0.2" +nix = "0.6" tokio-signal = "0.1" + +[replace] +"mio:0.6.1" = { path = "mio" } +"tokio-core:0.1.1" = { path = "tokio-core" } diff --git a/src/bin/cat.rs b/src/bin/cat.rs index 8118d2bea..b982fceaa 100644 --- a/src/bin/cat.rs +++ b/src/bin/cat.rs @@ -15,4 +15,5 @@ fn main() { } stdout.write(line.as_bytes()).unwrap(); } + stdout.flush().unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index c72887cd8..66b186a8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ extern crate futures; extern crate tokio_core; extern crate mio; +#[macro_use] +extern crate log; use std::ffi::OsStr; use std::io; diff --git a/src/unix.rs b/src/unix.rs index aa79141c2..928e0ea7e 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -1,5 +1,5 @@ -extern crate mio; extern crate libc; +extern crate nix; extern crate tokio_signal; use std::io; @@ -10,9 +10,12 @@ use futures::stream::Stream; use futures::{Future, Poll, Async}; use tokio_core::reactor::{Handle,PollEvented}; use self::libc::c_int; +use self::nix::fcntl::FcntlArg::F_SETFL; +use self::nix::fcntl::{fcntl, O_NONBLOCK}; use self::tokio_signal::unix::Signal; -use mio::{Evented,PollOpt,Ready,Token}; +use mio; +use mio::{Evented, PollOpt, Ready, Token}; use mio::unix::EventedFd; use Command; @@ -28,6 +31,40 @@ pub struct Child { struct RawFdWrap(T); +impl RawFdWrap { + fn new(fd: T) -> io::Result + where T: AsRawFd { + + try!(set_nonblock(&fd)); + Ok(RawFdWrap(fd)) + } +} + +impl io::Read for RawFdWrap where T: io::Read { + fn read(&mut self, bytes: &mut [u8]) -> io::Result { + self.0.read(bytes) + } +} + +impl io::Write for RawFdWrap where T: io::Write { + fn write(&mut self, bytes: &[u8]) -> io::Result { + self.0.write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} + +fn from_nix_error(err: nix::Error) -> io::Error { + io::Error::from_raw_os_error(err.errno() as i32) +} + +fn set_nonblock(s: &AsRawFd) -> io::Result<()> { + fcntl(s.as_raw_fd(), F_SETFL(O_NONBLOCK)).map_err(from_nix_error) + .map(|_| ()) +} + pub struct StdStream { io: PollEvented>, } @@ -37,29 +74,34 @@ pub type ChildStdout = StdStream; pub type ChildStderr = StdStream; impl Evented for RawFdWrap where T: AsRawFd { - fn register(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt) -> io::Result<()> { - EventedFd(&self.0.as_raw_fd()).register(poll, token, interest, opts) + fn register(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt) + -> io::Result<()> { + debug!("Evented::register({:?}, {:?}, {:?}", token, interest, opts); + EventedFd(&self.0.as_raw_fd()).register(poll, token, interest | Ready::hup(), opts) } - fn reregister(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt) -> io::Result<()> { - EventedFd(&self.0.as_raw_fd()).reregister(poll, token, interest, opts) + fn reregister(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt) + -> io::Result<()> { + debug!("Evented::reregister({:?}, {:?}, {:?}", token, interest, opts); + EventedFd(&self.0.as_raw_fd()).reregister(poll, token, interest | Ready::hup(), opts) } fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { + debug!("Evented::deregister()"); EventedFd(&self.0.as_raw_fd()).deregister(poll) } } impl io::Read for StdStream where T: io::Read { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.io.get_mut().0.read(buf) + self.io.read(buf) } } impl io::Write for StdStream where T: io::Write { fn write(&mut self, buf: &[u8]) -> io::Result { - self.io.get_mut().0.write(buf) + self.io.write(buf) } fn flush(&mut self) -> io::Result<()> { - self.io.get_mut().0.flush() + self.io.flush() } } @@ -67,7 +109,9 @@ fn stdio(option: &mut Option, handle: &Handle) -> Result Command { cmd } -fn feed_cat(cat: &mut Child, n: usize) -> BoxFuture<(), io::Error> { +fn feed_cat(mut cat: Child, n: usize) -> BoxFuture { let stdin = cat.stdin().take().unwrap(); let stdout = cat.stdout().take().unwrap(); + debug!("starting to feed"); // Produce n lines on the child's stdout. let numbers = stream::iter((0..n).into_iter().map(Ok)); let write = numbers.fold(stdin, |stdin, i| { + debug!("sending line {} to child", i); write_all(stdin, format!("line {}\n", i).into_bytes()).map(|(writer, _)| writer) }).map(|_| {}); @@ -39,7 +44,10 @@ fn feed_cat(cat: &mut Child, n: usize) -> BoxFuture<(), io::Error> { let expected_numbers = stream::iter((0..n + 1).into_iter().map(Ok)); let read = expected_numbers.fold((reader, 0), move |(reader, i), _| { let done = i >= n; + debug!("starting read from child"); read_until(reader, b'\n', Vec::new()).and_then(move |(reader, vec)| { + debug!("read line {} from child ({} bytes, done: {})", i, vec.len(), done); + io::stdout().flush().unwrap(); match (done, vec.len()) { (false, 0) => { Err(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe")) @@ -60,7 +68,7 @@ fn feed_cat(cat: &mut Child, n: usize) -> BoxFuture<(), io::Error> { }) }); // Compose reading and writing concurrently. - write.join(read).map(|_| {}).boxed() + write.join(read).and_then(|_| cat).boxed() } #[test] @@ -75,11 +83,14 @@ fn feed_cat(cat: &mut Child, n: usize) -> BoxFuture<(), io::Error> { /// - We read the same lines from the child that we fed it. // /// - The child does produce EOF on stdout after the last line. -fn cat_loop() { +fn feed_a_lot() { + let _ = ::env_logger::init(); + let mut lp = Core::new().unwrap(); let cmd = cat(&lp.handle()); - let mut child = lp.run(cmd.spawn()).unwrap(); - lp.run(feed_cat(&mut child, 10000)).unwrap(); - let status = lp.run(&mut child).unwrap(); + let child = cmd.spawn().and_then(|child| { + feed_cat(child, 10000) + }); + let status = lp.run(child).unwrap(); assert_eq!(status.code(), Some(0)); } From 7f3f868b665046479c66f5b74175a8deba59a50a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 12 Dec 2016 00:34:07 -0800 Subject: [PATCH 017/110] process: Add Windows support for stdio streams --- Cargo.toml | 10 ++- src/lib.rs | 56 ++++++++++--- src/unix.rs | 207 +++++++++++++++++++++++++------------------------ src/windows.rs | 53 +++++++++++-- tests/stdio.rs | 15 ++-- 5 files changed, 211 insertions(+), 130 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da60131b7..56a75ac42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,17 +15,19 @@ tokio-core = "0.1" futures = "0.1" mio = "0.6" log = "0.3" -env_logger = "0.3" + +[dev-dependencies] +env_logger = { version = "0.3", default-features = false } [target.'cfg(windows)'.dependencies] winapi = "0.2" kernel32-sys = "0.2" +mio-named-pipes = { git = 'https://github.com/alexcrichton/mio-named-pipes' } [target.'cfg(unix)'.dependencies] libc = "0.2" -nix = "0.6" tokio-signal = "0.1" [replace] -"mio:0.6.1" = { path = "mio" } -"tokio-core:0.1.1" = { path = "tokio-core" } +"mio:0.6.1" = { git = "https://github.com/alexcrichton/mio", branch = "custom-iocp" } +"tokio-core:0.1.1" = { git = "https://github.com/tokio-rs/tokio-core" } diff --git a/src/lib.rs b/src/lib.rs index 66b186a8e..137e475ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ extern crate mio; extern crate log; use std::ffi::OsStr; -use std::io; +use std::io::{self, Read, Write}; use std::path::Path; use std::process::{self, ExitStatus}; @@ -21,9 +21,6 @@ mod imp; #[cfg(windows)] mod imp; -pub use imp::ChildStdin; -pub use imp::ChildStdout; - pub struct Command { inner: process::Command, #[allow(dead_code)] @@ -36,6 +33,21 @@ pub struct Spawn { pub struct Child { inner: imp::Child, + stdin: Option, + stdout: Option, + stderr: Option, +} + +pub struct ChildStdin { + inner: imp::ChildStdin, +} + +pub struct ChildStdout { + inner: imp::ChildStdout, +} + +pub struct ChildStderr { + inner: imp::ChildStderr, } impl Command { @@ -116,7 +128,7 @@ impl Command { pub fn spawn(self) -> Spawn { Spawn { - inner: Box::new(imp::spawn(self).map(|c| Child { inner: c })), + inner: Box::new(imp::spawn(self)), } } } @@ -139,16 +151,16 @@ impl Child { self.inner.kill() } - pub fn stdin(&mut self) -> &mut Option { - &mut self.inner.stdin + pub fn stdin(&mut self) -> &mut Option { + &mut self.stdin } - pub fn stdout(&mut self) -> &mut Option { - &mut self.inner.stdout + pub fn stdout(&mut self) -> &mut Option { + &mut self.stdout } - pub fn stderr(&mut self) -> &mut Option { - &mut self.inner.stderr + pub fn stderr(&mut self) -> &mut Option { + &mut self.stderr } } @@ -160,3 +172,25 @@ impl Future for Child { self.inner.poll() } } + +impl Write for ChildStdin { + fn write(&mut self, bytes: &[u8]) -> io::Result { + self.inner.write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl Read for ChildStdout { + fn read(&mut self, bytes: &mut [u8]) -> io::Result { + self.inner.read(bytes) + } +} + +impl Read for ChildStderr { + fn read(&mut self, bytes: &mut [u8]) -> io::Result { + self.inner.read(bytes) + } +} diff --git a/src/unix.rs b/src/unix.rs index 928e0ea7e..9c74f182e 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -1,5 +1,4 @@ extern crate libc; -extern crate nix; extern crate tokio_signal; use std::io; @@ -10,8 +9,6 @@ use futures::stream::Stream; use futures::{Future, Poll, Async}; use tokio_core::reactor::{Handle,PollEvented}; use self::libc::c_int; -use self::nix::fcntl::FcntlArg::F_SETFL; -use self::nix::fcntl::{fcntl, O_NONBLOCK}; use self::tokio_signal::unix::Signal; use mio; @@ -24,95 +21,6 @@ pub struct Child { child: process::Child, reaped: bool, sigchld: Signal, - pub stdin: Option, - pub stdout: Option, - pub stderr: Option, -} - -struct RawFdWrap(T); - -impl RawFdWrap { - fn new(fd: T) -> io::Result - where T: AsRawFd { - - try!(set_nonblock(&fd)); - Ok(RawFdWrap(fd)) - } -} - -impl io::Read for RawFdWrap where T: io::Read { - fn read(&mut self, bytes: &mut [u8]) -> io::Result { - self.0.read(bytes) - } -} - -impl io::Write for RawFdWrap where T: io::Write { - fn write(&mut self, bytes: &[u8]) -> io::Result { - self.0.write(bytes) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.flush() - } -} - -fn from_nix_error(err: nix::Error) -> io::Error { - io::Error::from_raw_os_error(err.errno() as i32) -} - -fn set_nonblock(s: &AsRawFd) -> io::Result<()> { - fcntl(s.as_raw_fd(), F_SETFL(O_NONBLOCK)).map_err(from_nix_error) - .map(|_| ()) -} - -pub struct StdStream { - io: PollEvented>, -} - -pub type ChildStdin = StdStream; -pub type ChildStdout = StdStream; -pub type ChildStderr = StdStream; - -impl Evented for RawFdWrap where T: AsRawFd { - fn register(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt) - -> io::Result<()> { - debug!("Evented::register({:?}, {:?}, {:?}", token, interest, opts); - EventedFd(&self.0.as_raw_fd()).register(poll, token, interest | Ready::hup(), opts) - } - fn reregister(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt) - -> io::Result<()> { - debug!("Evented::reregister({:?}, {:?}, {:?}", token, interest, opts); - EventedFd(&self.0.as_raw_fd()).reregister(poll, token, interest | Ready::hup(), opts) - } - fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { - debug!("Evented::deregister()"); - EventedFd(&self.0.as_raw_fd()).deregister(poll) - } -} - -impl io::Read for StdStream where T: io::Read { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.io.read(buf) - } -} - -impl io::Write for StdStream where T: io::Write { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.io.write(buf) - } - fn flush(&mut self) -> io::Result<()> { - self.io.flush() - } -} - -fn stdio(option: &mut Option, handle: &Handle) -> Result>, io::Error> - where T: AsRawFd { - - option.take().map_or(Ok(None), |stream| { - PollEvented::new(try!(RawFdWrap::new(stream)), handle).map(|io| { - Some(StdStream { io: io }) - }) - }) } /// Spawns a new child process. @@ -137,19 +45,35 @@ fn stdio(option: &mut Option, handle: &Handle) -> Result Box> { +pub fn spawn(mut cmd: Command) -> Box> { + struct KillOnDrop(Option); + + impl Drop for KillOnDrop { + fn drop(&mut self) { + if let Some(mut c) = self.0.take() { + drop(c.kill()); + } + } + } + Box::new(Signal::new(libc::SIGCHLD, &cmd.handle).and_then(move |sigchld| { cmd.inner.spawn().and_then(|mut c| { - let stdin = try!(stdio(&mut c.stdin, &cmd.handle)); - let stdout = try!(stdio(&mut c.stdout, &cmd.handle)); - let stderr = try!(stdio(&mut c.stderr, &cmd.handle)); - Ok(Child { - child: c, - reaped: false, - sigchld: sigchld, - stdin: stdin, - stdout: stdout, - stderr: stderr, + let stdin = c.stdin.take(); + let stdout = c.stdout.take(); + let stderr = c.stderr.take(); + let mut c = KillOnDrop(Some(c)); + let stdin = try!(stdio(stdin, &cmd.handle)); + let stdout = try!(stdio(stdout, &cmd.handle)); + let stderr = try!(stdio(stderr, &cmd.handle)); + Ok(::Child { + inner: Child { + child: c.0.take().unwrap(), + reaped: false, + sigchld: sigchld, + }, + stdin: stdin.map(|io| ::ChildStdin { inner: io }), + stdout: stdout.map(|io| ::ChildStdout { inner: io }), + stderr: stderr.map(|io| ::ChildStderr { inner: io }), }) }) })) @@ -216,3 +140,80 @@ pub fn try_wait(child: &process::Child) -> io::Result> { } } } + +pub struct Fd(T); + +impl io::Read for Fd { + fn read(&mut self, bytes: &mut [u8]) -> io::Result { + self.0.read(bytes) + } +} + +impl io::Write for Fd { + fn write(&mut self, bytes: &[u8]) -> io::Result { + self.0.write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} + +pub type ChildStdin = PollEvented>; +pub type ChildStdout = PollEvented>; +pub type ChildStderr = PollEvented>; + +impl Evented for Fd where T: AsRawFd { + fn register(&self, + poll: &mio::Poll, + token: Token, + interest: Ready, + opts: PollOpt) + -> io::Result<()> { + EventedFd(&self.0.as_raw_fd()).register(poll, + token, + interest | Ready::hup(), + opts) + } + + fn reregister(&self, + poll: &mio::Poll, + token: Token, + interest: Ready, + opts: PollOpt) + -> io::Result<()> { + EventedFd(&self.0.as_raw_fd()).reregister(poll, + token, + interest | Ready::hup(), + opts) + } + + fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { + EventedFd(&self.0.as_raw_fd()).deregister(poll) + } +} + +fn stdio(option: Option, handle: &Handle) + -> io::Result>>> + where T: AsRawFd +{ + let io = match option { + Some(io) => io, + None => return Ok(None), + }; + + // Set the fd to nonblocking before we pass it to the event loop + unsafe { + let fd = io.as_raw_fd(); + let r = libc::fcntl(fd, libc::F_GETFL); + if r == -1 { + return Err(io::Error::last_os_error()) + } + let r = libc::fcntl(fd, libc::F_SETFL, r | libc::O_NONBLOCK); + if r == -1 { + return Err(io::Error::last_os_error()) + } + } + let io = try!(PollEvented::new(Fd(io), handle)); + Ok(Some(io)) +} diff --git a/src/windows.rs b/src/windows.rs index 48667f8b8..ccea91c77 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,12 +1,15 @@ extern crate winapi; extern crate kernel32; +extern crate mio_named_pipes; use std::io; use std::os::windows::prelude::*; use std::os::windows::process::ExitStatusExt; use std::process::{self, ExitStatus}; +use tokio_core::reactor::{PollEvented, Handle}; use futures::{self, Future, Poll, Async, Oneshot, Complete, oneshot, Fuse}; +use self::mio_named_pipes::NamedPipe; use Command; @@ -24,12 +27,35 @@ struct Waiting { unsafe impl Sync for Waiting {} unsafe impl Send for Waiting {} -pub fn spawn(mut cmd: Command) -> Box> { - Box::new(futures::done(cmd.inner.spawn().map(|c| { - Child { - child: c, - waiting: None, +pub fn spawn(mut cmd: Command) -> Box> { + struct KillOnDrop(Option); + + impl Drop for KillOnDrop { + fn drop(&mut self) { + if let Some(mut c) = self.0.take() { + drop(c.kill()); + } } + } + + Box::new(futures::done(cmd.inner.spawn().and_then(|mut c| { + let stdin = c.stdin.take(); + let stdout = c.stdout.take(); + let stderr = c.stderr.take(); + let mut c = KillOnDrop(Some(c)); + let stdin = try!(stdio(stdin, &cmd.handle)); + let stdout = try!(stdio(stdout, &cmd.handle)); + let stderr = try!(stdio(stderr, &cmd.handle)); + + Ok(::Child { + inner: Child { + child: c.0.take().unwrap(), + waiting: None, + }, + stdin: stdin.map(|io| ::ChildStdin { inner: io }), + stdout: stdout.map(|io| ::ChildStdout { inner: io }), + stderr: stderr.map(|io| ::ChildStderr { inner: io }), + }) }))) } @@ -121,3 +147,20 @@ pub fn try_wait(child: &process::Child) -> io::Result> { } } } + +pub type ChildStdin = PollEvented; +pub type ChildStdout = PollEvented; +pub type ChildStderr = PollEvented; + +fn stdio(option: Option, handle: &Handle) + -> io::Result>> + where T: IntoRawHandle, +{ + let io = match option { + Some(io) => io, + None => return Ok(None), + }; + let pipe = unsafe { NamedPipe::from_raw_handle(io.into_raw_handle()) }; + let io = try!(PollEvented::new(pipe, handle)); + Ok(Some(io)) +} diff --git a/tests/stdio.rs b/tests/stdio.rs index 1a8224f45..56b4e46f2 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -7,7 +7,7 @@ extern crate log; extern crate env_logger; use std::env; -use std::io::{self, Write}; +use std::io; use std::process::{Stdio, ExitStatus}; use futures::{Future, BoxFuture}; @@ -22,7 +22,7 @@ fn cat(handle: &Handle) -> Command { path.push("cat"); let mut cmd = Command::new(path, handle); cmd.stdin(Stdio::piped()) - .stdout(Stdio::piped()); + .stdout(Stdio::piped()); cmd } @@ -35,19 +35,19 @@ fn feed_cat(mut cat: Child, n: usize) -> BoxFuture { let numbers = stream::iter((0..n).into_iter().map(Ok)); let write = numbers.fold(stdin, |stdin, i| { debug!("sending line {} to child", i); - write_all(stdin, format!("line {}\n", i).into_bytes()).map(|(writer, _)| writer) - }).map(|_| {}); + write_all(stdin, format!("line {}\n", i).into_bytes()).map(|p| p.0) + }).map(|_| ()); // Try to read `n + 1` lines, ensuring the last one is empty // (i.e. EOF is reached after `n` lines. let reader = io::BufReader::new(stdout); - let expected_numbers = stream::iter((0..n + 1).into_iter().map(Ok)); + let expected_numbers = stream::iter((0..n + 1).map(Ok)); let read = expected_numbers.fold((reader, 0), move |(reader, i), _| { let done = i >= n; debug!("starting read from child"); read_until(reader, b'\n', Vec::new()).and_then(move |(reader, vec)| { - debug!("read line {} from child ({} bytes, done: {})", i, vec.len(), done); - io::stdout().flush().unwrap(); + debug!("read line {} from child ({} bytes, done: {})", + i, vec.len(), done); match (done, vec.len()) { (false, 0) => { Err(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe")) @@ -67,6 +67,7 @@ fn feed_cat(mut cat: Child, n: usize) -> BoxFuture { } }) }); + // Compose reading and writing concurrently. write.join(read).and_then(|_| cat).boxed() } From a0cc60153a40eb16e0c60b4f66f867c381a3c21e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 13 Dec 2016 15:57:32 -0800 Subject: [PATCH 018/110] process: Fix nightly tests --- tests/stdio.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/stdio.rs b/tests/stdio.rs index 56b4e46f2..aac0df345 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -19,6 +19,9 @@ use tokio_process::{Command, Child}; fn cat(handle: &Handle) -> Command { let mut path = env::current_exe().unwrap(); path.pop(); + if path.ends_with("deps") { + path.pop(); + } path.push("cat"); let mut cmd = Command::new(path, handle); cmd.stdin(Stdio::piped()) From 89b97929318edebe119ed14c7d618cad06a7578c Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 18 Dec 2016 16:00:48 -0800 Subject: [PATCH 019/110] process: Update README with crates.io info --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e279cb6cc..828da1a46 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ An implementation of process management for Tokio [![Build Status](https://travis-ci.org/alexcrichton/tokio-process.svg?branch=master)](https://travis-ci.org/alexcrichton/tokio-process) [![Build status](https://ci.appveyor.com/api/projects/status/43c8g7fy801e5902?svg=true)](https://ci.appveyor.com/project/alexcrichton/tokio-process) +[![Crates.io](https://img.shields.io/crates/v/tokio-process.svg?maxAge=2592000)](https://crates.io/crates/tokio-process) [Documentation](https://alexcrichton.github.io/tokio-process) @@ -13,7 +14,7 @@ First, add this to your `Cargo.toml`: ```toml [dependencies] -tokio-process = { git = "https://github.com/alexcrichton/tokio-process" } +tokio-process = "0.1" ``` Next, add this to your crate: From ca9586a089a1b8d06b361f1aba98eaa543d04bf9 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 18 Dec 2016 17:58:46 -0800 Subject: [PATCH 020/110] process: Add documentation to public declarations --- src/bin/exit.rs | 2 ++ src/lib.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/bin/exit.rs b/src/bin/exit.rs index 73ce00d17..8db0a4a65 100644 --- a/src/bin/exit.rs +++ b/src/bin/exit.rs @@ -1,3 +1,5 @@ +#[allow(dead_code)] + fn main() { std::process::exit(std::env::args().nth(1).unwrap().parse().unwrap()); } diff --git a/src/lib.rs b/src/lib.rs index 137e475ad..534197229 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,36 @@ +//! An implementation of process management for Tokio. +//! +//! This crate provides `Future` implementations for spawning and waiting +//! on child processes. These implementations are powered by system APIs on +//! Windows and by signals on Unix systems. +//! +//! # Usage +//! +//! To achieve efficient polling of running child processes, we will need to +//! set up an event loop from `tokio-core`: +// FIXME: add warning that on Unix systems the *first* event loop can't go away? +//! +//! ```no_run +//! extern crate futures; +//! extern crate tokio_core; +//! extern crate tokio_process; +//! +//! use futures::Future; +//! use tokio_core::reactor::Core; +//! use tokio_process::Command; +//! +//! fn main() { +//! let mut event_loop = Core::new().expect("failed to init event loop!"); +//! let mut cmd = Command::new("echo", &event_loop.handle()); +//! cmd.args(&["hello", "world"]); +//! +//! match event_loop.run(cmd.spawn().flatten()) { +//! Ok(status) => println!("exited successfully: {}", status.success()), +//! Err(e) => panic!("failed to run command: {}", e), +//! } +//! } +//! ``` + #[macro_use] extern crate futures; extern crate tokio_core; @@ -27,10 +60,27 @@ pub struct Command { handle: Handle, } +/// A future that represents a spawned child process. +/// +/// This future is created by the `Command::spawn` method. +/// +/// If the caller does not care about the intermediate handle to a spawned +/// child, this future can be `flatten`ed to directly compute the child's +/// exit status. pub struct Spawn { inner: Box>, } +/// A future that represents the exit status of a running or exited child process. +/// +/// This future is created by successfully polling the `Spawn` future. +/// +/// # Note +/// +/// Take note that there is no implementation of `Drop` for this future, +/// so if you do not ensure the `Child` has exited then it will continue to +/// run, even after the `Child` handle to the child process has gone out of +/// scope. pub struct Child { inner: imp::Child, stdin: Option, @@ -143,10 +193,13 @@ impl Future for Spawn { } impl Child { + /// Returns the OS-assigned process identifier associated with this child. pub fn id(&self) -> u32 { self.inner.id() } + /// Forces the child to exit. This is equivalent to sending a + /// SIGKILL on unix platforms. pub fn kill(&mut self) -> io::Result<()> { self.inner.kill() } From 6150be189f4f8487483830667309c51302624aea Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 18 Dec 2016 22:36:21 -0800 Subject: [PATCH 021/110] process: Rewrite the crate with an extension trait --- Cargo.toml | 8 +- src/lib.rs | 492 ++++++++++++++++++++++++++++++++++--------------- src/unix.rs | 167 ++++++++--------- src/windows.rs | 79 ++++---- tests/smoke.rs | 17 +- tests/stdio.rs | 37 ++-- 6 files changed, 494 insertions(+), 306 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56a75ac42..1ab0f7964 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ An implementation of an asynchronous process management backed futures. [dependencies] tokio-core = "0.1" -futures = "0.1" +futures = "0.1.7" mio = "0.6" log = "0.3" @@ -22,12 +22,8 @@ env_logger = { version = "0.3", default-features = false } [target.'cfg(windows)'.dependencies] winapi = "0.2" kernel32-sys = "0.2" -mio-named-pipes = { git = 'https://github.com/alexcrichton/mio-named-pipes' } +mio-named-pipes = "0.1" [target.'cfg(unix)'.dependencies] libc = "0.2" tokio-signal = "0.1" - -[replace] -"mio:0.6.1" = { git = "https://github.com/alexcrichton/mio", branch = "custom-iocp" } -"tokio-core:0.1.1" = { git = "https://github.com/tokio-rs/tokio-core" } diff --git a/src/lib.rs b/src/lib.rs index 534197229..ecaff5b09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,35 +1,96 @@ -//! An implementation of process management for Tokio. +//! An implementation of asynchronous process management for Tokio. //! -//! This crate provides `Future` implementations for spawning and waiting -//! on child processes. These implementations are powered by system APIs on -//! Windows and by signals on Unix systems. +//! This crate provides a `CommandExt` trait to enhance the functionality of the +//! `Command` type in the standard library. The three methods provided by this +//! trait mirror the "spawning" methods in the standard library. The +//! `CommandExt` trait in this crate, though, returns "future aware" types that +//! interoperate with Tokio. The asynchronous process support is provided +//! through signal handling on Unix and system APIs on Windows. //! -//! # Usage +//! # Examples //! -//! To achieve efficient polling of running child processes, we will need to -//! set up an event loop from `tokio-core`: -// FIXME: add warning that on Unix systems the *first* event loop can't go away? +//! Here's an example program which will spawn `echo hello world` and then wait +//! for it using an event loop. //! //! ```no_run //! extern crate futures; //! extern crate tokio_core; //! extern crate tokio_process; //! +//! use std::process::Command; +//! //! use futures::Future; //! use tokio_core::reactor::Core; -//! use tokio_process::Command; +//! use tokio_process::CommandExt; //! //! fn main() { -//! let mut event_loop = Core::new().expect("failed to init event loop!"); -//! let mut cmd = Command::new("echo", &event_loop.handle()); -//! cmd.args(&["hello", "world"]); +//! // Create our own local event loop +//! let mut core = Core::new().unwrap(); //! -//! match event_loop.run(cmd.spawn().flatten()) { -//! Ok(status) => println!("exited successfully: {}", status.success()), -//! Err(e) => panic!("failed to run command: {}", e), +//! // Use the standard library's `Command` type to build a process and +//! // then execute it via the `CommandExt` trait. +//! let child = Command::new("echo").arg("hello").arg("world") +//! .spawn_async(&core.handle()); +//! +//! // Make sure our child succeeded in spawning +//! let child = child.expect("failed to spawn"); +//! +//! match core.run(child) { +//! Ok(status) => println!("exit status: {}", status), +//! Err(e) => panic!("failed to wait for exit: {}", e), //! } //! } //! ``` +//! +//! Next, let's take a look at an example where we not only spawn `echo hello +//! world` but we also capture its output. +//! +//! ```no_run +//! extern crate futures; +//! extern crate tokio_core; +//! extern crate tokio_process; +//! +//! use std::process::Command; +//! +//! use futures::Future; +//! use tokio_core::reactor::Core; +//! use tokio_process::CommandExt; +//! +//! fn main() { +//! let mut core = Core::new().unwrap(); +//! +//! // Like above, but use `output_async` which returns a future instead of +//! // immediately returning the `Child`. +//! let output = Command::new("echo").arg("hello").arg("world") +//! .output_async(&core.handle()); +//! let output = core.run(output).expect("failed to collect output"); +//! +//! assert!(output.status.success()); +//! assert_eq!(output.stdout, b"hello world\n"); +//! } +//! ``` +//! +//! # Caveats +//! +//! While similar to the standard library, this crate's `Child` type differs +//! importantly in the behavior of `drop`. In the standard library, a child +//! process will continue running after the instance of `std::process::Child` +//! is dropped. In this crate, however, because `tokio_process::Child` is a +//! future of the child's `ExitStatus`, a child process is terminated if +//! `tokio_process::Child` is dropped. The behavior of the standard library can +//! be regained with the `Child::forget` method. +//! +//! As a final caveat, currently this crate relies on the `tokio-signal` crate +//! and therefore inherits its current restriction. Namely, once a child has +//! been spawned onto an event loop then *that event loop must stay alive for +//! any spawned child in the future to make progress*. In other words, once +//! you spawn a child onto an event loop, you should ensure that the event loop +//! keeps running for the duration of the program if there are multiple event +//! loops. Unfortunately this makes testing particularly tricky, but you can +//! work around this with an initial event loop that just runs forever in the +//! background. + +#![deny(missing_docs)] #[macro_use] extern crate futures; @@ -38,13 +99,13 @@ extern crate mio; #[macro_use] extern crate log; -use std::ffi::OsStr; use std::io::{self, Read, Write}; -use std::path::Path; -use std::process::{self, ExitStatus}; +use std::process::{ExitStatus, Command, Output, Stdio}; -use futures::{Future, Poll}; +use futures::{Future, Poll, IntoFuture}; +use futures::future::{Flatten, FutureResult, Either, ok}; use tokio_core::reactor::Handle; +use tokio_core::io::{IoFuture, read_to_end}; #[path = "unix.rs"] #[cfg(unix)] @@ -54,167 +115,210 @@ mod imp; #[cfg(windows)] mod imp; -pub struct Command { - inner: process::Command, - #[allow(dead_code)] - handle: Handle, +/// Extensions provided by this crate to the `Command` type in the standard +/// library. +/// +/// This crate primarily enhances the standard library's `Command` type with +/// asynchronous capabilities. The currently three blocking functions in the +/// standard library, `spawn`, `status`, and `output`, all have asynchronous +/// versions through this trait. +/// +/// Note that the `Child` type spawned is specific to this crate, and that the +/// I/O handles created from this crate are all asynchronous as well (differing +/// from their `std` counterparts). +pub trait CommandExt { + /// Executes the command as a child process, returning a handle to it. + /// + /// By default, stdin, stdout and stderr are inherited from the parent. + /// + /// This method will spawn the child process synchronously and return a + /// handle to a future-aware child process. The `Child` returned implements + /// `Future` itself to acquire the `ExitStatus` of the child, and otherwise + /// the `Child` has methods to acquire handles to the stdin, stdout, and + /// stderr streams. + /// + /// The `handle` specified to this method must be a handle to a valid event + /// loop, and all I/O this child does will be associated with the specified + /// event loop. + fn spawn_async(&mut self, handle: &Handle) -> io::Result; + + /// Executes a command as a child process, waiting for it to finish and + /// collecting its exit status. + /// + /// By default, stdin, stdout and stderr are inherited from the parent. + /// + /// The `StatusAsync` future returned will resolve to the `ExitStatus` + /// type in the standard library representing how the process exited. If + /// output handles are set to a pipe then they will be immediately closed + /// after the child is spawned. + /// + /// The `handle` specified must be a handle to a valid event loop, and all + /// I/O this child does will be associated with the specified event loop. + /// + /// If the `OutputAsync` future is dropped before the future resolves, then + /// the child will be killed, if it was spawned. + fn status_async(&mut self, handle: &Handle) -> StatusAsync; + + /// Executes the command as a child process, waiting for it to finish and + /// collecting all of its output. + /// + /// > **Note**: this method, unlike the standard library, will + /// > unconditionally configure the stdout/stderr handles to be pipes, even + /// > if they have been previously configured. If this is not desired then + /// > the `spawn_async` method should be used in combination with the + /// > `wait_with_output` method on child. + /// + /// This method will return a future representing the collection of the + /// child process's stdout/stderr. The `OutputAsync` future will resolve to + /// the `Output` type in the standard library, containing `stdout` and + /// `stderr` as `Vec` along with an `ExitStatus` representing how the + /// process exited. + /// + /// The `handle` specified must be a handle to a valid event loop, and all + /// I/O this child does will be associated with the specified event loop. + /// + /// If the `OutputAsync` future is dropped before the future resolves, then + /// the child will be killed, if it was spawned. + fn output_async(&mut self, handle: &Handle) -> OutputAsync; } -/// A future that represents a spawned child process. -/// -/// This future is created by the `Command::spawn` method. -/// -/// If the caller does not care about the intermediate handle to a spawned -/// child, this future can be `flatten`ed to directly compute the child's -/// exit status. -pub struct Spawn { - inner: Box>, + +impl CommandExt for Command { + fn spawn_async(&mut self, handle: &Handle) -> io::Result { + let mut child = Child { + child: imp::Child::new(try!(self.spawn()), handle), + stdin: None, + stdout: None, + stderr: None, + kill_on_drop: true, + }; + child.stdin = try!(child.child.register_stdin(handle)).map(|io| { + ChildStdin { inner: io } + }); + child.stdout = try!(child.child.register_stdout(handle)).map(|io| { + ChildStdout { inner: io } + }); + child.stderr = try!(child.child.register_stderr(handle)).map(|io| { + ChildStderr { inner: io } + }); + Ok(child) + } + + fn status_async(&mut self, handle: &Handle) -> StatusAsync { + StatusAsync { + inner: self.spawn_async(handle).into_future().flatten(), + } + } + + fn output_async(&mut self, handle: &Handle) -> OutputAsync { + self.stdout(Stdio::piped()); + self.stderr(Stdio::piped()); + OutputAsync { + inner: self.spawn_async(handle).into_future().and_then(|c| { + c.wait_with_output() + }).boxed(), + } + } } -/// A future that represents the exit status of a running or exited child process. +/// Representation of a child process spawned onto an event loop. /// -/// This future is created by successfully polling the `Spawn` future. +/// This type is also a future which will yield the `ExitStatus` of the +/// underlying child process. A `Child` here also provides access to information +/// like the OS-assigned identifier and the stdio streams. /// -/// # Note -/// -/// Take note that there is no implementation of `Drop` for this future, -/// so if you do not ensure the `Child` has exited then it will continue to -/// run, even after the `Child` handle to the child process has gone out of -/// scope. +/// > **Note**: The behavior of `drop` on a child in this crate is *different +/// > than the behavior of the standard library*. If a `tokio_process::Child` is +/// > dropped before the process finishes then the process will be terminated. +/// > In the standard library, however, the process continues executing. This is +/// > done because futures in general take `drop` as a sign of cancellation, and +/// > this `Child` is itself a future. If you'd like to run a process in the +/// > background, though, you may use the `forget` method. pub struct Child { - inner: imp::Child, + child: imp::Child, + kill_on_drop: bool, stdin: Option, stdout: Option, stderr: Option, } -pub struct ChildStdin { - inner: imp::ChildStdin, -} - -pub struct ChildStdout { - inner: imp::ChildStdout, -} - -pub struct ChildStderr { - inner: imp::ChildStderr, -} - -impl Command { - pub fn new>(exe: T, handle: &Handle) -> Command { - Command::_new(exe.as_ref(), handle) - } - - fn _new(exe: &OsStr, handle: &Handle) -> Command { - Command { - inner: process::Command::new(exe), - handle: handle.clone(), - } - } - - pub fn arg>(&mut self, arg: S) -> &mut Command { - self._arg(arg.as_ref()) - } - - fn _arg(&mut self, arg: &OsStr) -> &mut Command { - self.inner.arg(arg); - self - } - - pub fn args>(&mut self, args: &[S]) -> &mut Command { - for arg in args { - self._arg(arg.as_ref()); - } - self - } - - pub fn env(&mut self, key: K, val: V) -> &mut Command - where K: AsRef, V: AsRef - { - self._env(key.as_ref(), val.as_ref()) - } - - fn _env(&mut self, key: &OsStr, val: &OsStr) -> &mut Command { - self.inner.env(key, val); - self - } - - pub fn env_remove>(&mut self, key: K) -> &mut Command { - self._env_remove(key.as_ref()) - } - - fn _env_remove(&mut self, key: &OsStr) -> &mut Command { - self.inner.env_remove(key); - self - } - - pub fn env_clear(&mut self) -> &mut Command { - self.inner.env_clear(); - self - } - - pub fn current_dir>(&mut self, dir: P) -> &mut Command { - self._current_dir(dir.as_ref()) - } - - fn _current_dir(&mut self, dir: &Path) -> &mut Command { - self.inner.current_dir(dir); - self - } - - pub fn stdin(&mut self, cfg: process::Stdio) -> &mut Self { - self.inner.stdin(cfg); - self - } - - pub fn stdout(&mut self, cfg: process::Stdio) -> &mut Self { - self.inner.stdout(cfg); - self - } - pub fn stderr(&mut self, cfg: process::Stdio) -> &mut Self { - self.inner.stderr(cfg); - self - } - - pub fn spawn(self) -> Spawn { - Spawn { - inner: Box::new(imp::spawn(self)), - } - } -} - -impl Future for Spawn { - type Item = Child; - type Error = io::Error; - - fn poll(&mut self) -> Poll { - self.inner.poll() - } -} - impl Child { /// Returns the OS-assigned process identifier associated with this child. pub fn id(&self) -> u32 { - self.inner.id() + self.child.id() } - /// Forces the child to exit. This is equivalent to sending a - /// SIGKILL on unix platforms. + /// Forces the child to exit. + /// + /// This is equivalent to sending a SIGKILL on unix platforms. pub fn kill(&mut self) -> io::Result<()> { - self.inner.kill() + self.child.kill() } + /// Returns a handle for writing to the child's stdin, if it has been + /// captured pub fn stdin(&mut self) -> &mut Option { &mut self.stdin } + /// Returns a handle for writing to the child's stdout, if it has been + /// captured pub fn stdout(&mut self) -> &mut Option { &mut self.stdout } + /// Returns a handle for writing to the child's stderr, if it has been + /// captured pub fn stderr(&mut self) -> &mut Option { &mut self.stderr } + + /// Returns a future that will resolve to an `Output`, containing the exit + /// status, stdout, and stderr of the child process. + /// + /// The returned future will simultaneously waits for the child to exit and + /// collect all remaining output on the stdout/stderr handles, returning an + /// `Output` instance. + /// + /// The stdin handle to the child process, if any, will be closed before + /// waiting. This helps avoid deadlock: it ensures that the child does not + /// block waiting for input from the parent, while the parent waits for the + /// child to exit. + /// + /// By default, stdin, stdout and stderr are inherited from the parent. In + /// order to capture the output into this `Output` it is necessary to create + /// new pipes between parent and child. Use `stdout(Stdio::piped())` or + /// `stderr(Stdio::piped())`, respectively, when creating a `Command`. + pub fn wait_with_output(mut self) -> WaitWithOutput { + drop(self.stdin().take()); + let stdout = match self.stdout().take() { + Some(io) => Either::A(read_to_end(io, Vec::new()).map(|p| p.1)), + None => Either::B(ok(Vec::new())), + }; + let stderr = match self.stderr().take() { + Some(io) => Either::A(read_to_end(io, Vec::new()).map(|p| p.1)), + None => Either::B(ok(Vec::new())), + }; + + WaitWithOutput { + inner: self.join(stdout).join(stderr).map(|((status, stdout), stderr)| { + Output { + status: status, + stdout: stdout, + stderr: stderr, + } + }).boxed() + } + } + + /// Drop this `Child` without killing the underlying process. + /// + /// Normally a `Child` is killed if it's still alive when dropped, but this + /// method will ensure that the child may continue running once the `Child` + /// instance is dropped. + pub fn forget(mut self) { + self.kill_on_drop = false; + } } impl Future for Child { @@ -222,10 +326,100 @@ impl Future for Child { type Error = io::Error; fn poll(&mut self) -> Poll { + self.child.poll_exit() + } +} + +impl Drop for Child { + fn drop(&mut self) { + if self.kill_on_drop { + drop(self.kill()); + } + } +} + +/// Future returned from the `Child::wait_with_output` method. +/// +/// This future will resolve to the standard library's `Output` type which +/// contains the exit status, stdout, and stderr of a child process. +pub struct WaitWithOutput { + inner: IoFuture, +} + +impl Future for WaitWithOutput { + type Item = Output; + type Error = io::Error; + + fn poll(&mut self) -> Poll { self.inner.poll() } } +/// Future returned by the `CommandExt::status_async` method. +/// +/// This future is used to conveniently spawn a child and simply wait for its +/// exit status. This future will resolves to the `ExitStatus` type in the +/// standard library. +pub struct StatusAsync { + inner: Flatten>, +} + +impl Future for StatusAsync { + type Item = ExitStatus; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + self.inner.poll() + } +} + +/// Future returned by the `CommandExt::output_async` method. +/// +/// This future is mostly equivalent to spawning a process and then calling +/// `wait_with_output` on it internally. This can be useful to simply spawn a +/// process, collecting all of its output and its exit status. +pub struct OutputAsync { + inner: IoFuture, +} + +impl Future for OutputAsync { + type Item = Output; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + self.inner.poll() + } +} + +/// The standard input stream for spawned children. +/// +/// This type implements the `Write` trait to pass data to the stdin handle of +/// a child process. Note that this type is also "futures aware" meaning that it +/// is both (a) nonblocking and (b) will panic if used off of a future's task. +pub struct ChildStdin { + inner: imp::ChildStdin, +} + +/// The standard output stream for spawned children. +/// +/// This type implements the `Read` trait to read data from the stdout handle +/// of a child process. Note that this type is also "futures aware" meaning +/// that it is both (a) nonblocking and (b) will panic if used off of a +/// future's task. +pub struct ChildStdout { + inner: imp::ChildStdout, +} + +/// The standard error stream for spawned children. +/// +/// This type implements the `Read` trait to read data from the stderr handle +/// of a child process. Note that this type is also "futures aware" meaning +/// that it is both (a) nonblocking and (b) will panic if used off of a +/// future's task. +pub struct ChildStderr { + inner: imp::ChildStderr, +} + impl Write for ChildStdin { fn write(&mut self, bytes: &[u8]) -> io::Result { self.inner.write(bytes) diff --git a/src/unix.rs b/src/unix.rs index 9c74f182e..b29e9b1f7 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -1,3 +1,26 @@ +//! Unix handling of child processes +//! +//! Right now the only "fancy" thing about this is how we implement the +//! `Future` implementation on `Child` to get the exit status. Unix offers +//! no way to register a child with epoll, and the only real way to get a +//! notification when a process exits is the SIGCHLD signal. +//! +//! Signal handling in general is *super* hairy and complicated, and it's even +//! more complicated here with the fact that signals are coalesced, so we may +//! not get a SIGCHLD-per-child. +//! +//! Our best approximation here is to check *all spawned processes* for all +//! SIGCHLD signals received. To do that we create a `Signal`, implemented in +//! the `tokio-signal` crate, which is a stream over signals being received. +//! +//! Later when we poll the process's exit status we simply check to see if a +//! SIGCHLD has happened since we last checked, and while that returns "yes" we +//! keep trying. +//! +//! Note that this means that this isn't really scalable, but then again +//! processes in general aren't scalable (e.g. millions) so it shouldn't be that +//! bad in theory... + extern crate libc; extern crate tokio_signal; @@ -5,104 +28,64 @@ use std::io; use std::os::unix::prelude::*; use std::process::{self, ExitStatus}; -use futures::stream::Stream; -use futures::{Future, Poll, Async}; -use tokio_core::reactor::{Handle,PollEvented}; +use futures::future::FlattenStream; +use futures::{Future, Poll, Async, Stream}; +use mio::unix::EventedFd; +use mio::{Evented, PollOpt, Ready, Token}; +use mio; use self::libc::c_int; use self::tokio_signal::unix::Signal; - -use mio; -use mio::{Evented, PollOpt, Ready, Token}; -use mio::unix::EventedFd; - -use Command; +use tokio_core::io::IoFuture; +use tokio_core::reactor::{Handle, PollEvented}; pub struct Child { - child: process::Child, + inner: process::Child, reaped: bool, - sigchld: Signal, -} - -/// Spawns a new child process. -/// -/// Right now the only "fancy" thing about this is how we implement the -/// `Future` implementation on `Child` to get the exit status. Unix offers -/// no way to register a child with epoll, and the only real way to get a -/// notification when a process exits is the SIGCHLD signal. -/// -/// Signal handling in general is *super* hairy and complicated, and it's even -/// more complicated here with the fact that signals are coalesced, so we may -/// not get a SIGCHLD-per-child. -/// -/// Our best approximation here is to check *all spawned processes* for all -/// SIGCHLD signals received. To do that we create a `Signal`, implemented in -/// the `tokio-signal` crate, which is a stream over signals being received. -/// -/// Later when we poll the process's exit status we simply check to see if a -/// SIGCHLD has happened since we last checked, and while that returns "yes" we -/// keep trying. -/// -/// Note that this means that this isn't really scalable, but then again -/// processes in general aren't scalable (e.g. millions) so it shouldn't be that -/// bad in theory... -pub fn spawn(mut cmd: Command) -> Box> { - struct KillOnDrop(Option); - - impl Drop for KillOnDrop { - fn drop(&mut self) { - if let Some(mut c) = self.0.take() { - drop(c.kill()); - } - } - } - - Box::new(Signal::new(libc::SIGCHLD, &cmd.handle).and_then(move |sigchld| { - cmd.inner.spawn().and_then(|mut c| { - let stdin = c.stdin.take(); - let stdout = c.stdout.take(); - let stderr = c.stderr.take(); - let mut c = KillOnDrop(Some(c)); - let stdin = try!(stdio(stdin, &cmd.handle)); - let stdout = try!(stdio(stdout, &cmd.handle)); - let stderr = try!(stdio(stderr, &cmd.handle)); - Ok(::Child { - inner: Child { - child: c.0.take().unwrap(), - reaped: false, - sigchld: sigchld, - }, - stdin: stdin.map(|io| ::ChildStdin { inner: io }), - stdout: stdout.map(|io| ::ChildStdout { inner: io }), - stderr: stderr.map(|io| ::ChildStderr { inner: io }), - }) - }) - })) + sigchld: FlattenStream>, } impl Child { + pub fn new(inner: process::Child, handle: &Handle) -> Child { + Child { + inner: inner, + reaped: false, + sigchld: Signal::new(libc::SIGCHLD, handle).flatten_stream(), + } + } + + pub fn register_stdin(&mut self, handle: &Handle) + -> io::Result> { + stdio(self.inner.stdin.take(), handle) + } + + pub fn register_stdout(&mut self, handle: &Handle) + -> io::Result> { + stdio(self.inner.stdout.take(), handle) + } + + pub fn register_stderr(&mut self, handle: &Handle) + -> io::Result> { + stdio(self.inner.stderr.take(), handle) + } + pub fn id(&self) -> u32 { - self.child.id() + self.inner.id() } pub fn kill(&mut self) -> io::Result<()> { if self.reaped { Ok(()) } else { - self.child.kill() + self.inner.kill() } } -} -impl Future for Child { - type Item = ExitStatus; - type Error = io::Error; - - fn poll(&mut self) -> Poll { + pub fn poll_exit(&mut self) -> Poll { assert!(!self.reaped); loop { // Ensure that once we've successfully waited we won't try to // `kill` above. - if let Some(e) = try!(try_wait(&self.child)) { + if let Some(e) = try!(self.try_wait()) { self.reaped = true; return Ok(e.into()) } @@ -118,24 +101,24 @@ impl Future for Child { } } } -} -pub fn try_wait(child: &process::Child) -> io::Result> { - let id = child.id() as c_int; - let mut status = 0; - loop { - match unsafe { libc::waitpid(id, &mut status, libc::WNOHANG) } { - 0 => return Ok(None), - n if n < 0 => { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::Interrupted { - continue + fn try_wait(&self) -> io::Result> { + let id = self.id() as c_int; + let mut status = 0; + loop { + match unsafe { libc::waitpid(id, &mut status, libc::WNOHANG) } { + 0 => return Ok(None), + n if n < 0 => { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue + } + return Err(err) + } + n => { + assert_eq!(n, id); + return Ok(Some(ExitStatus::from_raw(status))) } - return Err(err) - } - n => { - assert_eq!(n, id); - return Ok(Some(ExitStatus::from_raw(status))) } } } diff --git a/src/windows.rs b/src/windows.rs index ccea91c77..7feaa6e05 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,3 +1,20 @@ +//! Windows asynchronous process handling. +//! +//! Like with Unix we don't actually have a way of registering a process with an +//! IOCP object. As a result we similarly need another mechanism for getting a +//! signal when a process has exited. For now this is implemented with the +//! `RegisterWaitForSingleObject` function in the kernel32.dll. +//! +//! This strategy is the same that libuv takes and essentially just queues up a +//! wait for the process in a kernel32-specific thread pool. Once the object is +//! notified (e.g. the process exits) then we have a callback that basically +//! just completes a `Oneshot`. +//! +//! The `poll_exit` implementation will attempt to wait for the process in a +//! nonblocking fashion, but failing that it'll fire off a +//! `RegisterWaitForSingleObject` and then wait on the other end of the oneshot +//! from then on out. + extern crate winapi; extern crate kernel32; extern crate mio_named_pipes; @@ -7,11 +24,9 @@ use std::os::windows::prelude::*; use std::os::windows::process::ExitStatusExt; use std::process::{self, ExitStatus}; -use tokio_core::reactor::{PollEvented, Handle}; -use futures::{self, Future, Poll, Async, Oneshot, Complete, oneshot, Fuse}; +use futures::{Future, Poll, Async, Oneshot, Complete, oneshot, Fuse}; use self::mio_named_pipes::NamedPipe; - -use Command; +use tokio_core::reactor::{PollEvented, Handle}; pub struct Child { child: process::Child, @@ -27,39 +42,29 @@ struct Waiting { unsafe impl Sync for Waiting {} unsafe impl Send for Waiting {} -pub fn spawn(mut cmd: Command) -> Box> { - struct KillOnDrop(Option); - - impl Drop for KillOnDrop { - fn drop(&mut self) { - if let Some(mut c) = self.0.take() { - drop(c.kill()); - } +impl Child { + pub fn new(child: process::Child, _handle: &Handle) -> Child { + Child { + child: child, + waiting: None, } } - Box::new(futures::done(cmd.inner.spawn().and_then(|mut c| { - let stdin = c.stdin.take(); - let stdout = c.stdout.take(); - let stderr = c.stderr.take(); - let mut c = KillOnDrop(Some(c)); - let stdin = try!(stdio(stdin, &cmd.handle)); - let stdout = try!(stdio(stdout, &cmd.handle)); - let stderr = try!(stdio(stderr, &cmd.handle)); + pub fn register_stdin(&mut self, handle: &Handle) + -> io::Result> { + stdio(self.child.stdin.take(), handle) + } - Ok(::Child { - inner: Child { - child: c.0.take().unwrap(), - waiting: None, - }, - stdin: stdin.map(|io| ::ChildStdin { inner: io }), - stdout: stdout.map(|io| ::ChildStdout { inner: io }), - stderr: stderr.map(|io| ::ChildStderr { inner: io }), - }) - }))) -} + pub fn register_stdout(&mut self, handle: &Handle) + -> io::Result> { + stdio(self.child.stdout.take(), handle) + } + + pub fn register_stderr(&mut self, handle: &Handle) + -> io::Result> { + stdio(self.child.stderr.take(), handle) + } -impl Child { pub fn id(&self) -> u32 { self.child.id() } @@ -67,13 +72,8 @@ impl Child { pub fn kill(&mut self) -> io::Result<()> { self.child.kill() } -} -impl Future for Child { - type Item = ExitStatus; - type Error = io::Error; - - fn poll(&mut self) -> Poll { + pub fn poll_exit(&mut self) -> Poll { loop { if let Some(ref mut w) = self.waiting { match w.rx.poll().expect("should not be canceled") { @@ -100,8 +100,9 @@ impl Future for Child { winapi::WT_EXECUTEONLYONCE) }; if rc == 0 { + let err = io::Error::last_os_error(); drop(unsafe { Box::from_raw(ptr) }); - return Err(io::Error::last_os_error()) + return Err(err) } self.waiting = Some(Waiting { rx: rx.fuse(), diff --git a/tests/smoke.rs b/tests/smoke.rs index b156920a6..142e1a444 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -6,9 +6,10 @@ use std::env; use std::sync::mpsc::channel; use std::sync::{Once, ONCE_INIT}; use std::thread; +use std::process::Command; -use tokio_core::reactor::{Core, Handle}; -use tokio_process::Command; +use tokio_core::reactor::Core; +use tokio_process::CommandExt; static INIT: Once = ONCE_INIT; @@ -17,8 +18,8 @@ fn init() { let (tx, rx) = channel(); thread::spawn(move || { let mut lp = Core::new().unwrap(); - let cmd = exit(&lp.handle()); - let mut child = lp.run(cmd.spawn()).unwrap(); + let mut cmd = exit(); + let mut child = cmd.spawn_async(&lp.handle()).unwrap(); drop(child.kill()); lp.run(child).unwrap(); tx.send(()).unwrap(); @@ -28,14 +29,14 @@ fn init() { }); } -fn exit(handle: &Handle) -> Command { +fn exit() -> Command { let mut me = env::current_exe().unwrap(); me.pop(); if me.ends_with("deps") { me.pop(); } me.push("exit"); - Command::new(me, handle) + Command::new(me) } #[test] @@ -43,9 +44,9 @@ fn simple() { init(); let mut lp = Core::new().unwrap(); - let mut cmd = exit(&lp.handle()); + let mut cmd = exit(); cmd.arg("2"); - let mut child = lp.run(cmd.spawn()).unwrap(); + let mut child = cmd.spawn_async(&lp.handle()).unwrap(); let id = child.id(); assert!(id > 0); let status = lp.run(&mut child).unwrap(); diff --git a/tests/stdio.rs b/tests/stdio.rs index aac0df345..71d103861 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -8,22 +8,22 @@ extern crate env_logger; use std::env; use std::io; -use std::process::{Stdio, ExitStatus}; +use std::process::{Stdio, ExitStatus, Command}; use futures::{Future, BoxFuture}; use futures::stream::{self, Stream}; -use tokio_core::io::{read_until, write_all}; -use tokio_core::reactor::{Core, Handle}; -use tokio_process::{Command, Child}; +use tokio_core::io::{read_until, write_all, read_to_end}; +use tokio_core::reactor::Core; +use tokio_process::{CommandExt, Child}; -fn cat(handle: &Handle) -> Command { +fn cat() -> Command { let mut path = env::current_exe().unwrap(); path.pop(); if path.ends_with("deps") { path.pop(); } path.push("cat"); - let mut cmd = Command::new(path, handle); + let mut cmd = Command::new(path); cmd.stdin(Stdio::piped()) .stdout(Stdio::piped()); cmd @@ -85,16 +85,29 @@ fn feed_cat(mut cat: Child, n: usize) -> BoxFuture { /// concurrently; otherwise this would deadlock. /// /// - We read the same lines from the child that we fed it. -// +/// /// - The child does produce EOF on stdout after the last line. fn feed_a_lot() { let _ = ::env_logger::init(); let mut lp = Core::new().unwrap(); - let cmd = cat(&lp.handle()); - let child = cmd.spawn().and_then(|child| { - feed_cat(child, 10000) - }); - let status = lp.run(child).unwrap(); + let child = cat().spawn_async(&lp.handle()).unwrap(); + let status = lp.run(feed_cat(child, 10000)).unwrap(); assert_eq!(status.code(), Some(0)); } + +#[test] +fn drop_kills() { + let _ = ::env_logger::init(); + + let mut lp = Core::new().unwrap(); + let mut child = cat().spawn_async(&lp.handle()).unwrap(); + let stdin = child.stdin().take().unwrap(); + let stdout = child.stdout().take().unwrap(); + drop(child); + + let (_, output) = lp.run(read_to_end(stdout, Vec::new())).unwrap(); + assert_eq!(output.len(), 0); + let err = lp.run(write_all(stdin, b"1234")).err().unwrap(); + assert_eq!(err.kind(), io::ErrorKind::BrokenPipe); +} From 124391e42b0bd6139adbdb182f9ced75ea471bbe Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 18 Dec 2016 22:40:14 -0800 Subject: [PATCH 022/110] process: Add a simple `wait_with_output` test --- tests/stdio.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/stdio.rs b/tests/stdio.rs index 71d103861..c95fcb4fb 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -111,3 +111,21 @@ fn drop_kills() { let err = lp.run(write_all(stdin, b"1234")).err().unwrap(); assert_eq!(err.kind(), io::ErrorKind::BrokenPipe); } + +#[test] +fn wait_with_output_captures() { + let _ = ::env_logger::init(); + + let mut core = Core::new().unwrap(); + + let mut child = cat().spawn_async(&core.handle()).unwrap(); + let stdin = child.stdin().take().unwrap(); + let out = child.wait_with_output(); + + let ret = core.run(write_all(stdin, b"1234").map(|p| p.1).join(out)).unwrap(); + let (written, output) = ret; + + assert!(output.status.success()); + assert_eq!(output.stdout, written); + assert_eq!(output.stderr.len(), 0); +} From 9680ecc109e13b0afa3e7e51168d02912441f7aa Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 18 Dec 2016 22:48:18 -0800 Subject: [PATCH 023/110] process: Share init in tests --- tests/smoke.rs | 39 +++------------------------------------ tests/stdio.rs | 17 ++++++----------- tests/support/mod.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 47 deletions(-) create mode 100644 tests/support/mod.rs diff --git a/tests/smoke.rs b/tests/smoke.rs index 142e1a444..a914e6c7f 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -1,50 +1,17 @@ -extern crate futures; extern crate tokio_core; extern crate tokio_process; -use std::env; -use std::sync::mpsc::channel; -use std::sync::{Once, ONCE_INIT}; -use std::thread; -use std::process::Command; - use tokio_core::reactor::Core; use tokio_process::CommandExt; -static INIT: Once = ONCE_INIT; - -fn init() { - INIT.call_once(|| { - let (tx, rx) = channel(); - thread::spawn(move || { - let mut lp = Core::new().unwrap(); - let mut cmd = exit(); - let mut child = cmd.spawn_async(&lp.handle()).unwrap(); - drop(child.kill()); - lp.run(child).unwrap(); - tx.send(()).unwrap(); - drop(lp.run(futures::empty::<(), ()>())); - }); - rx.recv().unwrap(); - }); -} - -fn exit() -> Command { - let mut me = env::current_exe().unwrap(); - me.pop(); - if me.ends_with("deps") { - me.pop(); - } - me.push("exit"); - Command::new(me) -} +mod support; #[test] fn simple() { - init(); + support::init(); let mut lp = Core::new().unwrap(); - let mut cmd = exit(); + let mut cmd = support::cmd("exit"); cmd.arg("2"); let mut child = cmd.spawn_async(&lp.handle()).unwrap(); let id = child.id(); diff --git a/tests/stdio.rs b/tests/stdio.rs index c95fcb4fb..8109af19c 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -6,7 +6,6 @@ extern crate tokio_process; extern crate log; extern crate env_logger; -use std::env; use std::io; use std::process::{Stdio, ExitStatus, Command}; @@ -16,14 +15,10 @@ use tokio_core::io::{read_until, write_all, read_to_end}; use tokio_core::reactor::Core; use tokio_process::{CommandExt, Child}; +mod support; + fn cat() -> Command { - let mut path = env::current_exe().unwrap(); - path.pop(); - if path.ends_with("deps") { - path.pop(); - } - path.push("cat"); - let mut cmd = Command::new(path); + let mut cmd = support::cmd("cat"); cmd.stdin(Stdio::piped()) .stdout(Stdio::piped()); cmd @@ -88,7 +83,7 @@ fn feed_cat(mut cat: Child, n: usize) -> BoxFuture { /// /// - The child does produce EOF on stdout after the last line. fn feed_a_lot() { - let _ = ::env_logger::init(); + support::init(); let mut lp = Core::new().unwrap(); let child = cat().spawn_async(&lp.handle()).unwrap(); @@ -98,7 +93,7 @@ fn feed_a_lot() { #[test] fn drop_kills() { - let _ = ::env_logger::init(); + support::init(); let mut lp = Core::new().unwrap(); let mut child = cat().spawn_async(&lp.handle()).unwrap(); @@ -114,7 +109,7 @@ fn drop_kills() { #[test] fn wait_with_output_captures() { - let _ = ::env_logger::init(); + support::init(); let mut core = Core::new().unwrap(); diff --git a/tests/support/mod.rs b/tests/support/mod.rs new file mode 100644 index 000000000..79f213d1c --- /dev/null +++ b/tests/support/mod.rs @@ -0,0 +1,43 @@ +extern crate env_logger; +extern crate futures; +extern crate tokio_core; +extern crate tokio_process; + +use std::env; +use std::process::Command; +use std::sync::{Once, ONCE_INIT}; +use std::thread; +use std::sync::mpsc::channel; + +use self::tokio_core::reactor::Core; +use self::futures::future; +use self::tokio_process::CommandExt; + +pub fn init() { + static INIT: Once = ONCE_INIT; + + INIT.call_once(|| { + drop(env_logger::init()); + let (tx, rx) = channel(); + thread::spawn(move || { + let mut lp = Core::new().unwrap(); + let mut cmd = cmd("exit"); + let mut child = cmd.spawn_async(&lp.handle()).unwrap(); + drop(child.kill()); + lp.run(child).unwrap(); + tx.send(()).unwrap(); + drop(lp.run(future::empty::<(), ()>())); + }); + rx.recv().unwrap(); + }); +} + +pub fn cmd(s: &str) -> Command { + let mut me = env::current_exe().unwrap(); + me.pop(); + if me.ends_with("deps") { + me.pop(); + } + me.push(s); + Command::new(me) +} From 4a92c4d4b6f5fb0e774634a1cfcdf98a6512d631 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 18 Dec 2016 23:36:40 -0800 Subject: [PATCH 024/110] process: Tweak drop_kills test --- tests/stdio.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/stdio.rs b/tests/stdio.rs index 8109af19c..1235072c6 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -101,10 +101,9 @@ fn drop_kills() { let stdout = child.stdout().take().unwrap(); drop(child); + drop(lp.run(write_all(stdin, b"1234"))); let (_, output) = lp.run(read_to_end(stdout, Vec::new())).unwrap(); assert_eq!(output.len(), 0); - let err = lp.run(write_all(stdin, b"1234")).err().unwrap(); - assert_eq!(err.kind(), io::ErrorKind::BrokenPipe); } #[test] From f20e7a4d2b04f219bc1a5716952abd999ae9838a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 19 Dec 2016 00:40:00 -0800 Subject: [PATCH 025/110] process: Bump minimum version of tokio-core --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1ab0f7964..f8f540ee0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ An implementation of an asynchronous process management backed futures. """ [dependencies] -tokio-core = "0.1" +tokio-core = "0.1.2" futures = "0.1.7" mio = "0.6" log = "0.3" From f3f99b723f9d378a8774397f3ac1d3088741fe15 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 19 Dec 2016 00:44:22 -0800 Subject: [PATCH 026/110] process: Bump to 0.2.0 --- Cargo.toml | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f8f540ee0..575b35b50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "tokio-process" -version = "0.1.0" +version = "0.2.0" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" homepage = "https://github.com/alexcrichton/tokio-process" -documentation = "https://alexcrichton.github.io/tokio-process" +documentation = "https://docs.rs/tokio-process/0.2" description = """ An implementation of an asynchronous process management backed futures. """ diff --git a/README.md b/README.md index 828da1a46..0d065c0fe 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ An implementation of process management for Tokio [![Build status](https://ci.appveyor.com/api/projects/status/43c8g7fy801e5902?svg=true)](https://ci.appveyor.com/project/alexcrichton/tokio-process) [![Crates.io](https://img.shields.io/crates/v/tokio-process.svg?maxAge=2592000)](https://crates.io/crates/tokio-process) -[Documentation](https://alexcrichton.github.io/tokio-process) +[Documentation](https://docs.rs/tokio-process/0.2) ## Usage @@ -14,7 +14,7 @@ First, add this to your `Cargo.toml`: ```toml [dependencies] -tokio-process = "0.1" +tokio-process = "0.2" ``` Next, add this to your crate: From ca51ae96511beb7ef6b010c013a443c881fbc5c8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 19 Dec 2016 08:14:09 -0800 Subject: [PATCH 027/110] process: Add back in 0.1.0 compatibility layer --- src/lib.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ecaff5b09..6080888df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,8 +99,10 @@ extern crate mio; #[macro_use] extern crate log; +use std::ffi::OsStr; use std::io::{self, Read, Write}; -use std::process::{ExitStatus, Command, Output, Stdio}; +use std::path::Path; +use std::process::{self, ExitStatus, Output, Stdio}; use futures::{Future, Poll, IntoFuture}; use futures::future::{Flatten, FutureResult, Either, ok}; @@ -183,7 +185,7 @@ pub trait CommandExt { } -impl CommandExt for Command { +impl CommandExt for process::Command { fn spawn_async(&mut self, handle: &Handle) -> io::Result { let mut child = Child { child: imp::Child::new(try!(self.spawn()), handle), @@ -441,3 +443,102 @@ impl Read for ChildStderr { self.inner.read(bytes) } } + +// deprecated from 0.1.0 + +#[deprecated(note = "use std::process::Command instead")] +#[allow(deprecated, missing_docs)] +pub struct Command { + inner: process::Command, + #[allow(dead_code)] + handle: Handle, +} + +#[deprecated(note = "use std::process::Command instead")] +#[allow(deprecated, missing_docs)] +pub struct Spawn { + inner: Box>, +} + +#[deprecated(note = "use std::process::Command instead")] +#[allow(deprecated, missing_docs)] +impl Command { + pub fn new>(exe: T, handle: &Handle) -> Command { + Command::_new(exe.as_ref(), handle) + } + + fn _new(exe: &OsStr, handle: &Handle) -> Command { + Command { + inner: process::Command::new(exe), + handle: handle.clone(), + } + } + + pub fn arg>(&mut self, arg: S) -> &mut Command { + self._arg(arg.as_ref()) + } + + fn _arg(&mut self, arg: &OsStr) -> &mut Command { + self.inner.arg(arg); + self + } + + pub fn args>(&mut self, args: &[S]) -> &mut Command { + for arg in args { + self._arg(arg.as_ref()); + } + self + } + + pub fn env(&mut self, key: K, val: V) -> &mut Command + where K: AsRef, V: AsRef + { + self._env(key.as_ref(), val.as_ref()) + } + + fn _env(&mut self, key: &OsStr, val: &OsStr) -> &mut Command { + self.inner.env(key, val); + self + } + + pub fn env_remove>(&mut self, key: K) -> &mut Command { + self._env_remove(key.as_ref()) + } + + fn _env_remove(&mut self, key: &OsStr) -> &mut Command { + self.inner.env_remove(key); + self + } + + pub fn env_clear(&mut self) -> &mut Command { + self.inner.env_clear(); + self + } + + pub fn current_dir>(&mut self, dir: P) -> &mut Command { + self._current_dir(dir.as_ref()) + } + + fn _current_dir(&mut self, dir: &Path) -> &mut Command { + self.inner.current_dir(dir); + self + } + + pub fn spawn(mut self) -> Spawn { + Spawn { + inner: self.inner.spawn_async(&self.handle).into_future().boxed() + } + } +} + +#[deprecated(note = "use std::process::Command instead")] +#[allow(deprecated)] +impl Future for Spawn { + type Item = Child; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + self.inner.poll() + } +} + From a0c162c0ffeb9ad8e8ff51102f8b171925a0b1aa Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 19 Dec 2016 08:14:29 -0800 Subject: [PATCH 028/110] process: Hide compat from docs --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 6080888df..2f56e9d40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -448,6 +448,7 @@ impl Read for ChildStderr { #[deprecated(note = "use std::process::Command instead")] #[allow(deprecated, missing_docs)] +#[doc(hidden)] pub struct Command { inner: process::Command, #[allow(dead_code)] @@ -456,12 +457,14 @@ pub struct Command { #[deprecated(note = "use std::process::Command instead")] #[allow(deprecated, missing_docs)] +#[doc(hidden)] pub struct Spawn { inner: Box>, } #[deprecated(note = "use std::process::Command instead")] #[allow(deprecated, missing_docs)] +#[doc(hidden)] impl Command { pub fn new>(exe: T, handle: &Handle) -> Command { Command::_new(exe.as_ref(), handle) @@ -533,6 +536,7 @@ impl Command { #[deprecated(note = "use std::process::Command instead")] #[allow(deprecated)] +#[doc(hidden)] impl Future for Spawn { type Item = Child; type Error = io::Error; From 22bc5e2738824098cbf2ae2b529e2c52c041d912 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 19 Dec 2016 08:14:40 -0800 Subject: [PATCH 029/110] process: Bump back to 0.1.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 575b35b50..a89c19e1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-process" -version = "0.2.0" +version = "0.1.1" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" From 6638cbc80e310beccc9046d80a22588e231650f2 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 19 Dec 2016 08:15:26 -0800 Subject: [PATCH 030/110] process: Update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0d065c0fe..0e4f61206 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ An implementation of process management for Tokio [![Build status](https://ci.appveyor.com/api/projects/status/43c8g7fy801e5902?svg=true)](https://ci.appveyor.com/project/alexcrichton/tokio-process) [![Crates.io](https://img.shields.io/crates/v/tokio-process.svg?maxAge=2592000)](https://crates.io/crates/tokio-process) -[Documentation](https://docs.rs/tokio-process/0.2) +[Documentation](https://docs.rs/tokio-process/0.1) ## Usage @@ -14,7 +14,7 @@ First, add this to your `Cargo.toml`: ```toml [dependencies] -tokio-process = "0.2" +tokio-process = "0.1.1" ``` Next, add this to your crate: From 01b5bf6761fd53a390b81981992dc6c9c6cf2120 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 27 Dec 2016 12:47:42 -0800 Subject: [PATCH 031/110] process: Use join3 instead of two joins --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2f56e9d40..0f7f825c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -303,7 +303,7 @@ impl Child { }; WaitWithOutput { - inner: self.join(stdout).join(stderr).map(|((status, stdout), stderr)| { + inner: self.join3(stdout, stderr).map(|(status, stdout, stderr)| { Output { status: status, stdout: stdout, From 1aee22505a45dc633cd0cf7009e321761d591306 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 11:07:25 -0800 Subject: [PATCH 032/110] process: Remove caveat about tokio-signal --- Cargo.toml | 2 +- src/lib.rs | 10 ---------- tests/smoke.rs | 2 -- tests/stdio.rs | 6 ------ tests/support/mod.rs | 26 -------------------------- 5 files changed, 1 insertion(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a89c19e1f..5dbb90038 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,4 +26,4 @@ mio-named-pipes = "0.1" [target.'cfg(unix)'.dependencies] libc = "0.2" -tokio-signal = "0.1" +tokio-signal = "0.1.2" diff --git a/src/lib.rs b/src/lib.rs index 0f7f825c5..954d7eb46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,16 +79,6 @@ //! future of the child's `ExitStatus`, a child process is terminated if //! `tokio_process::Child` is dropped. The behavior of the standard library can //! be regained with the `Child::forget` method. -//! -//! As a final caveat, currently this crate relies on the `tokio-signal` crate -//! and therefore inherits its current restriction. Namely, once a child has -//! been spawned onto an event loop then *that event loop must stay alive for -//! any spawned child in the future to make progress*. In other words, once -//! you spawn a child onto an event loop, you should ensure that the event loop -//! keeps running for the duration of the program if there are multiple event -//! loops. Unfortunately this makes testing particularly tricky, but you can -//! work around this with an initial event loop that just runs forever in the -//! background. #![deny(missing_docs)] diff --git a/tests/smoke.rs b/tests/smoke.rs index a914e6c7f..89f3e300b 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -8,8 +8,6 @@ mod support; #[test] fn simple() { - support::init(); - let mut lp = Core::new().unwrap(); let mut cmd = support::cmd("exit"); cmd.arg("2"); diff --git a/tests/stdio.rs b/tests/stdio.rs index 1235072c6..a09426453 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -83,8 +83,6 @@ fn feed_cat(mut cat: Child, n: usize) -> BoxFuture { /// /// - The child does produce EOF on stdout after the last line. fn feed_a_lot() { - support::init(); - let mut lp = Core::new().unwrap(); let child = cat().spawn_async(&lp.handle()).unwrap(); let status = lp.run(feed_cat(child, 10000)).unwrap(); @@ -93,8 +91,6 @@ fn feed_a_lot() { #[test] fn drop_kills() { - support::init(); - let mut lp = Core::new().unwrap(); let mut child = cat().spawn_async(&lp.handle()).unwrap(); let stdin = child.stdin().take().unwrap(); @@ -108,8 +104,6 @@ fn drop_kills() { #[test] fn wait_with_output_captures() { - support::init(); - let mut core = Core::new().unwrap(); let mut child = cat().spawn_async(&core.handle()).unwrap(); diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 79f213d1c..878afb12a 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -5,32 +5,6 @@ extern crate tokio_process; use std::env; use std::process::Command; -use std::sync::{Once, ONCE_INIT}; -use std::thread; -use std::sync::mpsc::channel; - -use self::tokio_core::reactor::Core; -use self::futures::future; -use self::tokio_process::CommandExt; - -pub fn init() { - static INIT: Once = ONCE_INIT; - - INIT.call_once(|| { - drop(env_logger::init()); - let (tx, rx) = channel(); - thread::spawn(move || { - let mut lp = Core::new().unwrap(); - let mut cmd = cmd("exit"); - let mut child = cmd.spawn_async(&lp.handle()).unwrap(); - drop(child.kill()); - lp.run(child).unwrap(); - tx.send(()).unwrap(); - drop(lp.run(future::empty::<(), ()>())); - }); - rx.recv().unwrap(); - }); -} pub fn cmd(s: &str) -> Command { let mut me = env::current_exe().unwrap(); From ed23a06fb1353fb8b1354e037ca15462f899efcd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 11:09:00 -0800 Subject: [PATCH 033/110] process: Update doc urls and metadata --- Cargo.toml | 7 ++++++- README.md | 2 +- src/lib.rs | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5dbb90038..bbe62eafd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,15 @@ authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" homepage = "https://github.com/alexcrichton/tokio-process" -documentation = "https://docs.rs/tokio-process/0.2" +documentation = "https://docs.rs/tokio-process" description = """ An implementation of an asynchronous process management backed futures. """ +categories = ["asynchronous"] + +[badges] +travis-ci = { repository = "alexcrichton/tokio-process" } +appveyor = { repository = "alexcrichton/tokio-process" } [dependencies] tokio-core = "0.1.2" diff --git a/README.md b/README.md index 0e4f61206..9da882340 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ An implementation of process management for Tokio [![Build status](https://ci.appveyor.com/api/projects/status/43c8g7fy801e5902?svg=true)](https://ci.appveyor.com/project/alexcrichton/tokio-process) [![Crates.io](https://img.shields.io/crates/v/tokio-process.svg?maxAge=2592000)](https://crates.io/crates/tokio-process) -[Documentation](https://docs.rs/tokio-process/0.1) +[Documentation](https://docs.rs/tokio-process) ## Usage diff --git a/src/lib.rs b/src/lib.rs index 954d7eb46..f17a36259 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,7 @@ //! be regained with the `Child::forget` method. #![deny(missing_docs)] +#![doc(html_root_url = "https://docs.rs/tokio-process/0.1")] #[macro_use] extern crate futures; From 521dc940215e22e833897cbb2ab36529267c768b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 11:09:44 -0800 Subject: [PATCH 034/110] process: Bump to 0.1.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bbe62eafd..d88accfdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-process" -version = "0.1.1" +version = "0.1.2" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" From 1384b31d60b401c28a8ecb7544ecbe7381faa574 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 9 Mar 2017 09:16:04 -0800 Subject: [PATCH 035/110] process: Update to tokio-io, mio, and tokio-core changes --- Cargo.toml | 9 +++++---- src/lib.rs | 16 +++++++++++++++- src/unix.rs | 11 ++++++----- src/windows.rs | 14 ++++++++------ tests/stdio.rs | 5 +++-- 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d88accfdc..82fae17ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,11 @@ travis-ci = { repository = "alexcrichton/tokio-process" } appveyor = { repository = "alexcrichton/tokio-process" } [dependencies] -tokio-core = "0.1.2" -futures = "0.1.7" -mio = "0.6" +futures = "0.1.11" log = "0.3" +mio = "0.6.5" +tokio-core = "0.1.6" +tokio-io = "0.1" [dev-dependencies] env_logger = { version = "0.3", default-features = false } @@ -31,4 +32,4 @@ mio-named-pipes = "0.1" [target.'cfg(unix)'.dependencies] libc = "0.2" -tokio-signal = "0.1.2" +tokio-signal = "0.1" diff --git a/src/lib.rs b/src/lib.rs index f17a36259..5938ce55a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,7 @@ #[macro_use] extern crate futures; extern crate tokio_core; +extern crate tokio_io; extern crate mio; #[macro_use] extern crate log; @@ -98,7 +99,8 @@ use std::process::{self, ExitStatus, Output, Stdio}; use futures::{Future, Poll, IntoFuture}; use futures::future::{Flatten, FutureResult, Either, ok}; use tokio_core::reactor::Handle; -use tokio_core::io::{IoFuture, read_to_end}; +use tokio_io::io::{read_to_end}; +use tokio_io::{AsyncWrite, AsyncRead, IoFuture}; #[path = "unix.rs"] #[cfg(unix)] @@ -423,18 +425,30 @@ impl Write for ChildStdin { } } +impl AsyncWrite for ChildStdin { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.inner.shutdown() + } +} + impl Read for ChildStdout { fn read(&mut self, bytes: &mut [u8]) -> io::Result { self.inner.read(bytes) } } +impl AsyncRead for ChildStdout { +} + impl Read for ChildStderr { fn read(&mut self, bytes: &mut [u8]) -> io::Result { self.inner.read(bytes) } } +impl AsyncRead for ChildStderr { +} + // deprecated from 0.1.0 #[deprecated(note = "use std::process::Command instead")] diff --git a/src/unix.rs b/src/unix.rs index b29e9b1f7..47cec7b77 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -30,12 +30,13 @@ use std::process::{self, ExitStatus}; use futures::future::FlattenStream; use futures::{Future, Poll, Async, Stream}; -use mio::unix::EventedFd; -use mio::{Evented, PollOpt, Ready, Token}; +use mio::unix::{EventedFd, UnixReady}; +use mio::{PollOpt, Ready, Token}; +use mio::event::Evented; use mio; use self::libc::c_int; use self::tokio_signal::unix::Signal; -use tokio_core::io::IoFuture; +use tokio_io::IoFuture; use tokio_core::reactor::{Handle, PollEvented}; pub struct Child { @@ -155,7 +156,7 @@ impl Evented for Fd where T: AsRawFd { -> io::Result<()> { EventedFd(&self.0.as_raw_fd()).register(poll, token, - interest | Ready::hup(), + interest | UnixReady::hup(), opts) } @@ -167,7 +168,7 @@ impl Evented for Fd where T: AsRawFd { -> io::Result<()> { EventedFd(&self.0.as_raw_fd()).reregister(poll, token, - interest | Ready::hup(), + interest | UnixReady::hup(), opts) } diff --git a/src/windows.rs b/src/windows.rs index 7feaa6e05..4febf57f0 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -24,7 +24,9 @@ use std::os::windows::prelude::*; use std::os::windows::process::ExitStatusExt; use std::process::{self, ExitStatus}; -use futures::{Future, Poll, Async, Oneshot, Complete, oneshot, Fuse}; +use futures::{Future, Poll, Async} ; +use futures::sync::oneshot; +use futures::future::Fuse; use self::mio_named_pipes::NamedPipe; use tokio_core::reactor::{PollEvented, Handle}; @@ -34,9 +36,9 @@ pub struct Child { } struct Waiting { - rx: Fuse>, + rx: Fuse>, wait_object: winapi::HANDLE, - tx: *mut Option>, + tx: *mut Option>, } unsafe impl Sync for Waiting {} @@ -87,7 +89,7 @@ impl Child { if let Some(e) = try!(try_wait(&self.child)) { return Ok(e.into()) } - let (tx, rx) = oneshot(); + let (tx, rx) = oneshot::channel(); let ptr = Box::into_raw(Box::new(Some(tx))); let mut wait_object = 0 as *mut _; let rc = unsafe { @@ -128,8 +130,8 @@ impl Drop for Waiting { unsafe extern "system" fn callback(ptr: winapi::PVOID, _timer_fired: winapi::BOOLEAN) { - let mut complete = &mut *(ptr as *mut Option>); - complete.take().unwrap().complete(()); + let mut complete = &mut *(ptr as *mut Option>); + drop(complete.take().unwrap().send(())); } pub fn try_wait(child: &process::Child) -> io::Result> { diff --git a/tests/stdio.rs b/tests/stdio.rs index a09426453..e1353ec1f 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -1,6 +1,7 @@ extern crate futures; #[macro_use] extern crate tokio_core; +extern crate tokio_io; extern crate tokio_process; #[macro_use] extern crate log; @@ -9,9 +10,9 @@ extern crate env_logger; use std::io; use std::process::{Stdio, ExitStatus, Command}; -use futures::{Future, BoxFuture}; +use futures::future::{BoxFuture, Future}; use futures::stream::{self, Stream}; -use tokio_core::io::{read_until, write_all, read_to_end}; +use tokio_io::io::{read_until, write_all, read_to_end}; use tokio_core::reactor::Core; use tokio_process::{CommandExt, Child}; From 5c5f793ef008323cff2e20ae20da6243fa2d99ee Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 15 Mar 2017 10:41:42 -0700 Subject: [PATCH 036/110] process: Bump to 0.1.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 82fae17ea..56e222408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-process" -version = "0.1.2" +version = "0.1.3" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" From c101e9e11de145d278a02db3207033a96f697dce Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 27 Mar 2017 06:48:25 -0700 Subject: [PATCH 037/110] process: Use appveyor to download rustup --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2f7e9cbc3..84ade714f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ environment: matrix: - TARGET: x86_64-pc-windows-msvc install: - - curl -sSf -o rustup-init.exe https://win.rustup.rs/ + - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - rustup-init.exe -y --default-host %TARGET% --default-toolchain beta - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - rustc -V From 50cabae181b6dd16420c2de39361a79796383e7e Mon Sep 17 00:00:00 2001 From: Michael Pankov Date: Mon, 24 Apr 2017 23:47:56 +0300 Subject: [PATCH 038/110] process: Add an example with reading input line-by-line --- src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5938ce55a..ace0dbe13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,42 @@ //! } //! ``` //! +//! We can also read input line by line. +//! +//! ```no_run +//! extern crate futures; +//! extern crate tokio_core; +//! extern crate tokio_process; +//! extern crate tokio_io; +//! +//! use std::io; +//! use std::process::{Command, Stdio, Output}; +//! +//! use futures::{BoxFuture, Future, Stream}; +//! use tokio_core::reactor::Core; +//! use tokio_process::{CommandExt, Child}; +//! +//! fn get_lines(mut cat: Child) -> BoxFuture<((), Output), io::Error> { +//! let stdout = cat.stdout().take().unwrap(); +//! let reader = io::BufReader::new(stdout); +//! let lines = tokio_io::io::lines(reader); +//! let cycle = lines.for_each(|l| { +//! println!("Line: {}", l); +//! Ok(()) +//! }); +//! cycle.join(cat.wait_with_output()).boxed() +//! } +//! +//! fn main() { +//! let mut cmd = Command::new("cat"); +//! let mut cat = cmd.stdout(Stdio::piped()); +//! let mut core = Core::new().unwrap(); +//! let child = cat.spawn_async(&core.handle()).unwrap(); +//! core.run(get_lines(child)).unwrap(); +//! } +//! +//! ``` +//! //! # Caveats //! //! While similar to the standard library, this crate's `Child` type differs From 4d11784b0177cce17c2341ce0a27be6ce2788b35 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 24 Apr 2017 14:02:51 -0700 Subject: [PATCH 039/110] process: Tweak docs and macro imports --- Cargo.toml | 2 +- src/lib.rs | 14 +++++--------- tests/stdio.rs | 1 - 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56e222408..82e8df1af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,13 @@ appveyor = { repository = "alexcrichton/tokio-process" } [dependencies] futures = "0.1.11" -log = "0.3" mio = "0.6.5" tokio-core = "0.1.6" tokio-io = "0.1" [dev-dependencies] env_logger = { version = "0.3", default-features = false } +log = "0.3" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/src/lib.rs b/src/lib.rs index ace0dbe13..aa15624df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,13 +79,13 @@ //! extern crate tokio_io; //! //! use std::io; -//! use std::process::{Command, Stdio, Output}; +//! use std::process::{Command, Stdio, ExitStatus}; //! //! use futures::{BoxFuture, Future, Stream}; //! use tokio_core::reactor::Core; //! use tokio_process::{CommandExt, Child}; //! -//! fn get_lines(mut cat: Child) -> BoxFuture<((), Output), io::Error> { +//! fn print_lines(mut cat: Child) -> BoxFuture { //! let stdout = cat.stdout().take().unwrap(); //! let reader = io::BufReader::new(stdout); //! let lines = tokio_io::io::lines(reader); @@ -93,17 +93,16 @@ //! println!("Line: {}", l); //! Ok(()) //! }); -//! cycle.join(cat.wait_with_output()).boxed() +//! cycle.join(cat).map(|((), s)| s).boxed() //! } //! //! fn main() { +//! let mut core = Core::new().unwrap(); //! let mut cmd = Command::new("cat"); //! let mut cat = cmd.stdout(Stdio::piped()); -//! let mut core = Core::new().unwrap(); //! let child = cat.spawn_async(&core.handle()).unwrap(); -//! core.run(get_lines(child)).unwrap(); +//! core.run(print_lines(child)).unwrap(); //! } -//! //! ``` //! //! # Caveats @@ -119,13 +118,10 @@ #![deny(missing_docs)] #![doc(html_root_url = "https://docs.rs/tokio-process/0.1")] -#[macro_use] extern crate futures; extern crate tokio_core; extern crate tokio_io; extern crate mio; -#[macro_use] -extern crate log; use std::ffi::OsStr; use std::io::{self, Read, Write}; diff --git a/tests/stdio.rs b/tests/stdio.rs index e1353ec1f..bd0cdee29 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -1,5 +1,4 @@ extern crate futures; -#[macro_use] extern crate tokio_core; extern crate tokio_io; extern crate tokio_process; From 914b803429d8e83bdd5212b26049fe0bbbc6fc5e Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 17 Jun 2017 16:40:21 -0700 Subject: [PATCH 040/110] process: Add `Debug` impls for nondeprecated structs --- src/lib.rs | 26 +++++++++++++++++++++++++- src/unix.rs | 13 +++++++++++++ src/windows.rs | 11 +++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index aa15624df..64798fca8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,7 @@ //! `tokio_process::Child` is dropped. The behavior of the standard library can //! be regained with the `Child::forget` method. +#![warn(missing_debug_implementations)] #![deny(missing_docs)] #![doc(html_root_url = "https://docs.rs/tokio-process/0.1")] @@ -130,6 +131,7 @@ use std::process::{self, ExitStatus, Output, Stdio}; use futures::{Future, Poll, IntoFuture}; use futures::future::{Flatten, FutureResult, Either, ok}; +use std::fmt; use tokio_core::reactor::Handle; use tokio_io::io::{read_to_end}; use tokio_io::{AsyncWrite, AsyncRead, IoFuture}; @@ -261,6 +263,7 @@ impl CommandExt for process::Command { /// > done because futures in general take `drop` as a sign of cancellation, and /// > this `Child` is itself a future. If you'd like to run a process in the /// > background, though, you may use the `forget` method. +#[derive(Debug)] pub struct Child { child: imp::Child, kill_on_drop: bool, @@ -373,6 +376,14 @@ pub struct WaitWithOutput { inner: IoFuture, } +impl fmt::Debug for WaitWithOutput { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("WaitWithOutput") + .field("inner", &"..") + .finish() + } +} + impl Future for WaitWithOutput { type Item = Output; type Error = io::Error; @@ -387,6 +398,7 @@ impl Future for WaitWithOutput { /// This future is used to conveniently spawn a child and simply wait for its /// exit status. This future will resolves to the `ExitStatus` type in the /// standard library. +#[derive(Debug)] pub struct StatusAsync { inner: Flatten>, } @@ -409,6 +421,14 @@ pub struct OutputAsync { inner: IoFuture, } +impl fmt::Debug for OutputAsync { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("OutputAsync") + .field("inner", &"..") + .finish() + } +} + impl Future for OutputAsync { type Item = Output; type Error = io::Error; @@ -423,6 +443,7 @@ impl Future for OutputAsync { /// This type implements the `Write` trait to pass data to the stdin handle of /// a child process. Note that this type is also "futures aware" meaning that it /// is both (a) nonblocking and (b) will panic if used off of a future's task. +#[derive(Debug)] pub struct ChildStdin { inner: imp::ChildStdin, } @@ -433,6 +454,7 @@ pub struct ChildStdin { /// of a child process. Note that this type is also "futures aware" meaning /// that it is both (a) nonblocking and (b) will panic if used off of a /// future's task. +#[derive(Debug)] pub struct ChildStdout { inner: imp::ChildStdout, } @@ -443,6 +465,7 @@ pub struct ChildStdout { /// of a child process. Note that this type is also "futures aware" meaning /// that it is both (a) nonblocking and (b) will panic if used off of a /// future's task. +#[derive(Debug)] pub struct ChildStderr { inner: imp::ChildStderr, } @@ -485,6 +508,7 @@ impl AsyncRead for ChildStderr { #[deprecated(note = "use std::process::Command instead")] #[allow(deprecated, missing_docs)] +#[allow(deprecated, missing_debug_implementations, missing_docs)] #[doc(hidden)] pub struct Command { inner: process::Command, @@ -493,7 +517,7 @@ pub struct Command { } #[deprecated(note = "use std::process::Command instead")] -#[allow(deprecated, missing_docs)] +#[allow(deprecated, missing_debug_implementations, missing_docs)] #[doc(hidden)] pub struct Spawn { inner: Box>, diff --git a/src/unix.rs b/src/unix.rs index 47cec7b77..7e85a6017 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -36,6 +36,7 @@ use mio::event::Evented; use mio; use self::libc::c_int; use self::tokio_signal::unix::Signal; +use std::fmt; use tokio_io::IoFuture; use tokio_core::reactor::{Handle, PollEvented}; @@ -45,6 +46,17 @@ pub struct Child { sigchld: FlattenStream>, } +impl fmt::Debug for Child { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("Child") + .field("pid", &self.inner.id()) + .field("inner", &self.inner) + .field("reaped", &self.reaped) + .field("sigchld", &"..") + .finish() + } +} + impl Child { pub fn new(inner: process::Child, handle: &Handle) -> Child { Child { @@ -125,6 +137,7 @@ impl Child { } } +#[derive(Debug)] pub struct Fd(T); impl io::Read for Fd { diff --git a/src/windows.rs b/src/windows.rs index 4febf57f0..e87ece162 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -19,6 +19,7 @@ extern crate winapi; extern crate kernel32; extern crate mio_named_pipes; +use std::fmt; use std::io; use std::os::windows::prelude::*; use std::os::windows::process::ExitStatusExt; @@ -35,6 +36,16 @@ pub struct Child { waiting: Option, } +impl fmt::Debug for Child { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("Child") + .field("pid", &self.id()) + .field("child", &self.child) + .field("waiting", &"..") + .finish() + } +} + struct Waiting { rx: Fuse>, wait_object: winapi::HANDLE, From 34e71fa71acf09d85460cf987012067d51b162a5 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 17 Jun 2017 16:46:36 -0700 Subject: [PATCH 041/110] process: Add `must_use` annotations to all futures --- src/lib.rs | 4 ++++ src/unix.rs | 1 + src/windows.rs | 1 + 3 files changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 64798fca8..bcb835d2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -263,6 +263,7 @@ impl CommandExt for process::Command { /// > done because futures in general take `drop` as a sign of cancellation, and /// > this `Child` is itself a future. If you'd like to run a process in the /// > background, though, you may use the `forget` method. +#[must_use = "futures do nothing unless polled"] #[derive(Debug)] pub struct Child { child: imp::Child, @@ -372,6 +373,7 @@ impl Drop for Child { /// /// This future will resolve to the standard library's `Output` type which /// contains the exit status, stdout, and stderr of a child process. +#[must_use = "futures do nothing unless polled"] pub struct WaitWithOutput { inner: IoFuture, } @@ -398,6 +400,7 @@ impl Future for WaitWithOutput { /// This future is used to conveniently spawn a child and simply wait for its /// exit status. This future will resolves to the `ExitStatus` type in the /// standard library. +#[must_use = "futures do nothing unless polled"] #[derive(Debug)] pub struct StatusAsync { inner: Flatten>, @@ -417,6 +420,7 @@ impl Future for StatusAsync { /// This future is mostly equivalent to spawning a process and then calling /// `wait_with_output` on it internally. This can be useful to simply spawn a /// process, collecting all of its output and its exit status. +#[must_use = "futures do nothing unless polled"] pub struct OutputAsync { inner: IoFuture, } diff --git a/src/unix.rs b/src/unix.rs index 7e85a6017..fed6deea2 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -40,6 +40,7 @@ use std::fmt; use tokio_io::IoFuture; use tokio_core::reactor::{Handle, PollEvented}; +#[must_use = "futures do nothing unless polled"] pub struct Child { inner: process::Child, reaped: bool, diff --git a/src/windows.rs b/src/windows.rs index e87ece162..8ed1781fc 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -31,6 +31,7 @@ use futures::future::Fuse; use self::mio_named_pipes::NamedPipe; use tokio_core::reactor::{PollEvented, Handle}; +#[must_use = "futures do nothing unless polled"] pub struct Child { child: process::Child, waiting: Option, From 56d3914675a48e22f1cdad66c56b1a399629069c Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 24 Jun 2017 18:11:39 -0700 Subject: [PATCH 042/110] process: Bugfix: ensure `status_async` closes child's stdio handles after spawning --- src/lib.rs | 18 ++++++++++++++---- tests/stdio.rs | 21 ++++++++++++++++++++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bcb835d2e..2b91ca69b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,13 +178,13 @@ pub trait CommandExt { /// /// The `StatusAsync` future returned will resolve to the `ExitStatus` /// type in the standard library representing how the process exited. If - /// output handles are set to a pipe then they will be immediately closed - /// after the child is spawned. + /// any input/output handles are set to a pipe then they will be immediately + /// closed after the child is spawned. /// /// The `handle` specified must be a handle to a valid event loop, and all /// I/O this child does will be associated with the specified event loop. /// - /// If the `OutputAsync` future is dropped before the future resolves, then + /// If the `StatusAsync` future is dropped before the future resolves, then /// the child will be killed, if it was spawned. fn status_async(&mut self, handle: &Handle) -> StatusAsync; @@ -234,8 +234,18 @@ impl CommandExt for process::Command { } fn status_async(&mut self, handle: &Handle) -> StatusAsync { + let mut inner = self.spawn_async(handle); + if let Ok(child) = inner.as_mut() { + // Ensure we close any stdio handles so we can't deadlock + // waiting on the child which may be waiting to read/write + // to a pipe we're holding. + child.stdin.take(); + child.stdout.take(); + child.stderr.take(); + } + StatusAsync { - inner: self.spawn_async(handle).into_future().flatten(), + inner: inner.into_future().flatten(), } } diff --git a/tests/stdio.rs b/tests/stdio.rs index bd0cdee29..dfef91991 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -8,11 +8,12 @@ extern crate env_logger; use std::io; use std::process::{Stdio, ExitStatus, Command}; +use std::time::Duration; use futures::future::{BoxFuture, Future}; use futures::stream::{self, Stream}; use tokio_io::io::{read_until, write_all, read_to_end}; -use tokio_core::reactor::Core; +use tokio_core::reactor::{Core, Timeout}; use tokio_process::{CommandExt, Child}; mod support; @@ -117,3 +118,21 @@ fn wait_with_output_captures() { assert_eq!(output.stdout, written); assert_eq!(output.stderr.len(), 0); } + +#[test] +fn status_closes_any_pipes() { + let mut core = Core::new().unwrap(); + + // Cat will open a pipe between the parent and child. + // If `status_async` doesn't ensure the handles are closed, + // we would end up blocking forever (and time out). + let child = cat().status_async(&core.handle()); + let timeout = Timeout::new(Duration::from_secs(1), &core.handle()) + .expect("timeout registration failed") + .map(|()| panic!("time out exceeded! did we get stuck waiting on the child?")); + + match core.run(child.select(timeout)) { + Ok((status, _)) => assert!(status.success()), + Err(_) => panic!("failed to run futures"), + } +} From b9c6eb309c6cf63c9423db8c7d668db24bea79e5 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 24 Jun 2017 17:59:41 -0700 Subject: [PATCH 043/110] process: Add `status_async2` as a closer analog to `spawn_async` --- src/lib.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/stdio.rs | 19 +++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2b91ca69b..90c25a555 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,8 +186,27 @@ pub trait CommandExt { /// /// If the `StatusAsync` future is dropped before the future resolves, then /// the child will be killed, if it was spawned. + #[deprecated(note = "use the more flexible `spawn_async2` method instead")] + #[allow(deprecated)] fn status_async(&mut self, handle: &Handle) -> StatusAsync; + /// Executes a command as a child process, waiting for it to finish and + /// collecting its exit status. + /// + /// By default, stdin, stdout and stderr are inherited from the parent. + /// + /// The `StatusAsync` future returned will resolve to the `ExitStatus` + /// type in the standard library representing how the process exited. If + /// any input/output handles are set to a pipe then they will be immediately + /// closed after the child is spawned. + /// + /// The `handle` specified must be a handle to a valid event loop, and all + /// I/O this child does will be associated with the specified event loop. + /// + /// If the `StatusAsync` future is dropped before the future resolves, then + /// the child will be killed, if it was spawned. + fn status_async2(&mut self, handle: &Handle) -> io::Result; + /// Executes the command as a child process, waiting for it to finish and /// collecting all of its output. /// @@ -233,6 +252,7 @@ impl CommandExt for process::Command { Ok(child) } + #[allow(deprecated)] fn status_async(&mut self, handle: &Handle) -> StatusAsync { let mut inner = self.spawn_async(handle); if let Ok(child) = inner.as_mut() { @@ -249,6 +269,21 @@ impl CommandExt for process::Command { } } + fn status_async2(&mut self, handle: &Handle) -> io::Result { + self.spawn_async(handle).map(|mut child| { + // Ensure we close any stdio handles so we can't deadlock + // waiting on the child which may be waiting to read/write + // to a pipe we're holding. + child.stdin.take(); + child.stdout.take(); + child.stderr.take(); + + StatusAsync2 { + inner: child, + } + }) + } + fn output_async(&mut self, handle: &Handle) -> OutputAsync { self.stdout(Stdio::piped()); self.stderr(Stdio::piped()); @@ -411,11 +446,14 @@ impl Future for WaitWithOutput { /// exit status. This future will resolves to the `ExitStatus` type in the /// standard library. #[must_use = "futures do nothing unless polled"] +#[deprecated(note = "use the more flexible `SpawnAsync2` adapter instead")] +#[allow(deprecated)] #[derive(Debug)] pub struct StatusAsync { inner: Flatten>, } +#[allow(deprecated)] impl Future for StatusAsync { type Item = ExitStatus; type Error = io::Error; @@ -425,6 +463,26 @@ impl Future for StatusAsync { } } +/// Future returned by the `CommandExt::status_async2` method. +/// +/// This future is used to conveniently spawn a child and simply wait for its +/// exit status. This future will resolves to the `ExitStatus` type in the +/// standard library. +#[must_use = "futures do nothing unless polled"] +#[derive(Debug)] +pub struct StatusAsync2 { + inner: Child, +} + +impl Future for StatusAsync2 { + type Item = ExitStatus; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + self.inner.poll() + } +} + /// Future returned by the `CommandExt::output_async` method. /// /// This future is mostly equivalent to spawning a process and then calling diff --git a/tests/stdio.rs b/tests/stdio.rs index dfef91991..79177131c 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -119,6 +119,7 @@ fn wait_with_output_captures() { assert_eq!(output.stderr.len(), 0); } +#[allow(deprecated)] #[test] fn status_closes_any_pipes() { let mut core = Core::new().unwrap(); @@ -136,3 +137,21 @@ fn status_closes_any_pipes() { Err(_) => panic!("failed to run futures"), } } + +#[test] +fn status2_closes_any_pipes() { + let mut core = Core::new().unwrap(); + + // Cat will open a pipe between the parent and child. + // If `status_async2` doesn't ensure the handles are closed, + // we would end up blocking forever (and time out). + let child = cat().status_async2(&core.handle()).unwrap(); + let timeout = Timeout::new(Duration::from_secs(1), &core.handle()) + .expect("timeout registration failed") + .map(|()| panic!("time out exceeded! did we get stuck waiting on the child?")); + + match core.run(child.select(timeout)) { + Ok((status, _)) => assert!(status.success()), + Err(_) => panic!("failed to run futures"), + } +} From 69295fac1e9205266e59979591ff5005cf4fe02f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 25 Jun 2017 10:36:54 -0700 Subject: [PATCH 044/110] process: Add an `Errors` section to `status_async2` --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 90c25a555..3962b54e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -205,6 +205,12 @@ pub trait CommandExt { /// /// If the `StatusAsync` future is dropped before the future resolves, then /// the child will be killed, if it was spawned. + /// + /// # Errors + /// + /// This function will return an error immediately if the child process + /// cannot be spawned. Otherwise errors obtained while waiting for the child + /// are returned through the `StatusAsync2` future. fn status_async2(&mut self, handle: &Handle) -> io::Result; /// Executes the command as a child process, waiting for it to finish and From c11eec39080df2a5477e298e944ae4f1c47fc3c8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 24 Jul 2017 21:38:59 -0700 Subject: [PATCH 045/110] process: Bump to 0.1.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 82e8df1af..c2666098c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-process" -version = "0.1.3" +version = "0.1.4" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" From acec6356eea2ad9c249b038703c6a17c3e0ec0a8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 30 Oct 2017 14:06:47 -0700 Subject: [PATCH 046/110] process: Clarify wording of license information in README. --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9da882340..869b9a611 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,20 @@ Next, add this to your crate: extern crate tokio_process; ``` + # License -`tokio-process` is primarily distributed under the terms of both the MIT -license and the Apache License (Version 2.0), with portions covered by various -BSD-like licenses. +Serde is licensed under either of -See LICENSE-APACHE, and LICENSE-MIT for details. + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) +at your option. +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Serde by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. From c205e2c358abceb6e25bc7021005ac75e71b1ea1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 30 Oct 2017 14:09:06 -0700 Subject: [PATCH 047/110] process: Fix copy/paste --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 869b9a611..f99fa99b0 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ extern crate tokio_process; # License -Serde is licensed under either of +This project is licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) From dbc185cd3ab1f27fe746c56d9863bca0eff3475b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 2 Dec 2017 07:16:16 -0800 Subject: [PATCH 048/110] process: Tweak travis config --- .travis.yml | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 57c9e65ad..304d54f72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,29 @@ language: rust - -rust: - - stable - - beta - - nightly sudo: false -before_script: - - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH + +matrix: + include: + - rust: 1.21.0 + - rust: stable + - os: osx + - rust: beta + - rust: nightly + + - rust: nightly + before_script: + - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH + script: + - cargo doc --no-deps --all-features + after_success: + - travis-cargo --only nightly doc-upload + script: - - cargo build - cargo test - - cargo doc --no-deps -after_success: - - travis-cargo --only nightly doc-upload + env: global: - - secure: "mTrrxm6AHgbh+k6/GKhKwQoKmLF4tZQLZ7671jvJ42Yu3U6mH4xGWnQDEQ6E883SvoBi0W5KwsvRqKRqNpPXYbWIMsS46gpMu0jEL6uz7+zwip64847OdbXAbS8NZsnXhS0w5b9dYdQUCoj71TrbWGVS/sqNb2twn+GJGIqfsjUHRnkHLIMmwoILgzYMbd3d1Jy/KlicIGtHq8Sb23EVr7tdkN++k21ZSDbmD+q5Pmf9MZH3yyk2YIpgCooVzqYAtS8Ua6ug1L+u3MBDWtqUFEOxGP5ya1+s312TGsBShaVvtQrH2IFOG+izdVjvRkpeM/FJXlqQAh02VxHK8ST9B6zjYO7Mnn8yT4gAA5PfoMlwZ5UxYQmr5fcjPAWvUkIdb6qRdqEupBbtCGO4EWTi2Gw8/w8tZGMS3JqPSsl8QJCu+fj3FWRwRwKmUaFIgIojbJQ3NCRCu9RGdbOoOOlhjYbw51lN9kxGxHLiiZzEtxv+nay5vNqIVkprRz4gbXOAi27Fglz2rYie88vb7XgrAVnaoahBA7C6l7KXZ/W3EuDDGjlxChj0kAqKz9AbbKAkYGN02hOn2Y26ag/THd4LKNE7QfcaEBKfJx3YEKVBI0fmoyT+iT1RMr9vmEySJmgZXHgd6TTIIpvIN8ouIy6LxzQby0bgP1++NSH05jgf/Sk=" + secure: "mTrrxm6AHgbh+k6/GKhKwQoKmLF4tZQLZ7671jvJ42Yu3U6mH4xGWnQDEQ6E883SvoBi0W5KwsvRqKRqNpPXYbWIMsS46gpMu0jEL6uz7+zwip64847OdbXAbS8NZsnXhS0w5b9dYdQUCoj71TrbWGVS/sqNb2twn+GJGIqfsjUHRnkHLIMmwoILgzYMbd3d1Jy/KlicIGtHq8Sb23EVr7tdkN++k21ZSDbmD+q5Pmf9MZH3yyk2YIpgCooVzqYAtS8Ua6ug1L+u3MBDWtqUFEOxGP5ya1+s312TGsBShaVvtQrH2IFOG+izdVjvRkpeM/FJXlqQAh02VxHK8ST9B6zjYO7Mnn8yT4gAA5PfoMlwZ5UxYQmr5fcjPAWvUkIdb6qRdqEupBbtCGO4EWTi2Gw8/w8tZGMS3JqPSsl8QJCu+fj3FWRwRwKmUaFIgIojbJQ3NCRCu9RGdbOoOOlhjYbw51lN9kxGxHLiiZzEtxv+nay5vNqIVkprRz4gbXOAi27Fglz2rYie88vb7XgrAVnaoahBA7C6l7KXZ/W3EuDDGjlxChj0kAqKz9AbbKAkYGN02hOn2Y26ag/THd4LKNE7QfcaEBKfJx3YEKVBI0fmoyT+iT1RMr9vmEySJmgZXHgd6TTIIpvIN8ouIy6LxzQby0bgP1++NSH05jgf/Sk=" notifications: email: on_success: never -os: - - linux - - osx From f0680617ee8f71c4c1df37a243739d30d9b7fc84 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 31 Dec 2017 15:00:02 -0800 Subject: [PATCH 049/110] process: Fix project name typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f99fa99b0..9ea45c549 100644 --- a/README.md +++ b/README.md @@ -38,5 +38,5 @@ at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Serde by you, as defined in the Apache-2.0 license, shall be +for inclusion in tokio-process by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. From f48944c1fbd3a02f3018462e306b88a4ccb3f435 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 3 Jan 2018 09:46:41 -0800 Subject: [PATCH 050/110] process: Update winapi to 0.3 --- Cargo.toml | 15 +++++++++++++-- src/lib.rs | 10 +++++----- src/windows.rs | 48 +++++++++++++++++++++++++++--------------------- tests/stdio.rs | 10 +++++----- 4 files changed, 50 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2666098c..f95e0a8b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,10 +26,21 @@ env_logger = { version = "0.3", default-features = false } log = "0.3" [target.'cfg(windows)'.dependencies] -winapi = "0.2" -kernel32-sys = "0.2" mio-named-pipes = "0.1" +[target.'cfg(windows)'.dependencies.winapi] +version = "0.3" +features = [ + "handleapi", + "winerror", + "minwindef", + "processthreadsapi", + "synchapi", + "threadpoollegacyapiset", + "winbase", + "winnt", +] + [target.'cfg(unix)'.dependencies] libc = "0.2" tokio-signal = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 3962b54e6..d6205d09f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,9 +294,9 @@ impl CommandExt for process::Command { self.stdout(Stdio::piped()); self.stderr(Stdio::piped()); OutputAsync { - inner: self.spawn_async(handle).into_future().and_then(|c| { + inner: Box::new(self.spawn_async(handle).into_future().and_then(|c| { c.wait_with_output() - }).boxed(), + })), } } } @@ -383,13 +383,13 @@ impl Child { }; WaitWithOutput { - inner: self.join3(stdout, stderr).map(|(status, stdout, stderr)| { + inner: Box::new(self.join3(stdout, stderr).map(|(status, stdout, stderr)| { Output { status: status, stdout: stdout, stderr: stderr, } - }).boxed() + })) } } @@ -668,7 +668,7 @@ impl Command { pub fn spawn(mut self) -> Spawn { Spawn { - inner: self.inner.spawn_async(&self.handle).into_future().boxed() + inner: Box::new(self.inner.spawn_async(&self.handle).into_future()), } } } diff --git a/src/windows.rs b/src/windows.rs index 8ed1781fc..d60d7b8de 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -16,7 +16,6 @@ //! from then on out. extern crate winapi; -extern crate kernel32; extern crate mio_named_pipes; use std::fmt; @@ -25,10 +24,18 @@ use std::os::windows::prelude::*; use std::os::windows::process::ExitStatusExt; use std::process::{self, ExitStatus}; -use futures::{Future, Poll, Async} ; -use futures::sync::oneshot; use futures::future::Fuse; +use futures::sync::oneshot; +use futures::{Future, Poll, Async} ; use self::mio_named_pipes::NamedPipe; +use self::winapi::shared::minwindef::*; +use self::winapi::shared::winerror::*; +use self::winapi::um::handleapi::*; +use self::winapi::um::processthreadsapi::*; +use self::winapi::um::synchapi::*; +use self::winapi::um::threadpoollegacyapiset::*; +use self::winapi::um::winbase::*; +use self::winapi::um::winnt::*; use tokio_core::reactor::{PollEvented, Handle}; #[must_use = "futures do nothing unless polled"] @@ -49,7 +56,7 @@ impl fmt::Debug for Child { struct Waiting { rx: Fuse>, - wait_object: winapi::HANDLE, + wait_object: HANDLE, tx: *mut Option>, } @@ -105,13 +112,13 @@ impl Child { let ptr = Box::into_raw(Box::new(Some(tx))); let mut wait_object = 0 as *mut _; let rc = unsafe { - kernel32::RegisterWaitForSingleObject(&mut wait_object, - self.child.as_raw_handle(), - Some(callback), - ptr as *mut _, - winapi::INFINITE, - winapi::WT_EXECUTEINWAITTHREAD | - winapi::WT_EXECUTEONLYONCE) + RegisterWaitForSingleObject(&mut wait_object, + self.child.as_raw_handle(), + Some(callback), + ptr as *mut _, + INFINITE, + WT_EXECUTEINWAITTHREAD | + WT_EXECUTEONLYONCE) }; if rc == 0 { let err = io::Error::last_os_error(); @@ -130,8 +137,7 @@ impl Child { impl Drop for Waiting { fn drop(&mut self) { unsafe { - let rc = kernel32::UnregisterWaitEx(self.wait_object, - winapi::INVALID_HANDLE_VALUE); + let rc = UnregisterWaitEx(self.wait_object, INVALID_HANDLE_VALUE); if rc == 0 { panic!("failed to unregister: {}", io::Error::last_os_error()); } @@ -140,22 +146,22 @@ impl Drop for Waiting { } } -unsafe extern "system" fn callback(ptr: winapi::PVOID, - _timer_fired: winapi::BOOLEAN) { - let mut complete = &mut *(ptr as *mut Option>); +unsafe extern "system" fn callback(ptr: PVOID, + _timer_fired: BOOLEAN) { + let complete = &mut *(ptr as *mut Option>); drop(complete.take().unwrap().send(())); } pub fn try_wait(child: &process::Child) -> io::Result> { unsafe { - match kernel32::WaitForSingleObject(child.as_raw_handle(), 0) { - winapi::WAIT_OBJECT_0 => {} - winapi::WAIT_TIMEOUT => return Ok(None), + match WaitForSingleObject(child.as_raw_handle(), 0) { + WAIT_OBJECT_0 => {} + WAIT_TIMEOUT => return Ok(None), _ => return Err(io::Error::last_os_error()), } let mut status = 0; - let rc = kernel32::GetExitCodeProcess(child.as_raw_handle(), &mut status); - if rc == winapi::FALSE { + let rc = GetExitCodeProcess(child.as_raw_handle(), &mut status); + if rc == FALSE { Err(io::Error::last_os_error()) } else { Ok(Some(ExitStatus::from_raw(status))) diff --git a/tests/stdio.rs b/tests/stdio.rs index 79177131c..401e8082a 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -10,7 +10,7 @@ use std::io; use std::process::{Stdio, ExitStatus, Command}; use std::time::Duration; -use futures::future::{BoxFuture, Future}; +use futures::future::Future; use futures::stream::{self, Stream}; use tokio_io::io::{read_until, write_all, read_to_end}; use tokio_core::reactor::{Core, Timeout}; @@ -25,13 +25,13 @@ fn cat() -> Command { cmd } -fn feed_cat(mut cat: Child, n: usize) -> BoxFuture { +fn feed_cat(mut cat: Child, n: usize) -> Box> { let stdin = cat.stdin().take().unwrap(); let stdout = cat.stdout().take().unwrap(); debug!("starting to feed"); // Produce n lines on the child's stdout. - let numbers = stream::iter((0..n).into_iter().map(Ok)); + let numbers = stream::iter_ok(0..n); let write = numbers.fold(stdin, |stdin, i| { debug!("sending line {} to child", i); write_all(stdin, format!("line {}\n", i).into_bytes()).map(|p| p.0) @@ -40,7 +40,7 @@ fn feed_cat(mut cat: Child, n: usize) -> BoxFuture { // Try to read `n + 1` lines, ensuring the last one is empty // (i.e. EOF is reached after `n` lines. let reader = io::BufReader::new(stdout); - let expected_numbers = stream::iter((0..n + 1).map(Ok)); + let expected_numbers = stream::iter_ok(0..n + 1); let read = expected_numbers.fold((reader, 0), move |(reader, i), _| { let done = i >= n; debug!("starting read from child"); @@ -68,7 +68,7 @@ fn feed_cat(mut cat: Child, n: usize) -> BoxFuture { }); // Compose reading and writing concurrently. - write.join(read).and_then(|_| cat).boxed() + Box::new(write.join(read).and_then(|_| cat)) } #[test] From 82aeae147d246537673e803587ae5831fa4d5ff3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 3 Jan 2018 09:48:42 -0800 Subject: [PATCH 051/110] process: Update dev-dependencies --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f95e0a8b6..681be2d7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,8 @@ tokio-core = "0.1.6" tokio-io = "0.1" [dev-dependencies] -env_logger = { version = "0.3", default-features = false } -log = "0.3" +env_logger = { version = "0.4", default-features = false } +log = "0.4" [target.'cfg(windows)'.dependencies] mio-named-pipes = "0.1" From 32c928b6073cab68fb6e4cb170cf248b4000f387 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 3 Jan 2018 09:48:53 -0800 Subject: [PATCH 052/110] process: Bump to 0.1.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 681be2d7e..cfc24965e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-process" -version = "0.1.4" +version = "0.1.5" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" From 7987b644459e5bc4072e69c86efc18e01fe8e9e5 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Tue, 24 Apr 2018 20:04:36 -0700 Subject: [PATCH 053/110] process: Clarify that `Child::forget` docs that it can leak OS resources --- src/lib.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index d6205d09f..bc812729a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -398,6 +398,36 @@ impl Child { /// Normally a `Child` is killed if it's still alive when dropped, but this /// method will ensure that the child may continue running once the `Child` /// instance is dropped. + /// + /// > **Note**: this method may leak OS resources depending on your platform. + /// > To ensure resources are eventually cleaned up, consider sending the + /// > `Child` instance into an event loop as an alternative to this method. + /// + /// ```no_run + /// # extern crate futures; + /// # extern crate tokio_core; + /// # extern crate tokio_process; + /// # + /// # use std::process::Command; + /// # + /// # use futures::Future; + /// # use tokio_core::reactor::Core; + /// # use tokio_process::CommandExt; + /// # + /// # fn main() { + /// let core = Core::new().unwrap(); + /// let handle = core.handle(); + /// + /// let child = Command::new("echo").arg("hello").arg("world") + /// .spawn_async(&handle) + /// .expect("failed to spawn"); + /// + /// let do_cleanup = child.map(|_| ()) // Ignore result + /// .map_err(|_| ()); // Ignore errors + /// + /// handle.spawn(do_cleanup); + /// # } + /// ``` pub fn forget(mut self) { self.kill_on_drop = false; } From bdc87856f28e4b9a7c00581b86b8a8cde7e7efe8 Mon Sep 17 00:00:00 2001 From: "Arvid E. Picciani" Date: Sun, 22 Apr 2018 12:57:14 +0200 Subject: [PATCH 054/110] process: fix zombification on Drop on unix --- src/unix.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/unix.rs b/src/unix.rs index fed6deea2..a231d4e6e 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -90,7 +90,11 @@ impl Child { if self.reaped { Ok(()) } else { - self.inner.kill() + self.inner.kill()?; + let mut status = 0; + let id = self.id() as c_int; + unsafe { libc::waitpid(id, &mut status, 0) }; + Ok(()) } } From 09e21eceeaa1a47340652149fab9c634718bdcc9 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 9 May 2018 19:24:45 -0700 Subject: [PATCH 055/110] process: Unix: mark child as reaped on kill --- src/unix.rs | 62 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index a231d4e6e..123e68602 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -34,7 +34,6 @@ use mio::unix::{EventedFd, UnixReady}; use mio::{PollOpt, Ready, Token}; use mio::event::Evented; use mio; -use self::libc::c_int; use self::tokio_signal::unix::Signal; use std::fmt; use tokio_io::IoFuture; @@ -87,24 +86,21 @@ impl Child { } pub fn kill(&mut self) -> io::Result<()> { - if self.reaped { - Ok(()) - } else { + if !self.reaped { + // NB: SIGKILL cannnot be caught, so the process will definitely exit immediately. + // We're not waiting for the process itself but for the kernel to execute the kill. self.inner.kill()?; - let mut status = 0; - let id = self.id() as c_int; - unsafe { libc::waitpid(id, &mut status, 0) }; - Ok(()) + let _ = self.try_wait(true); } + + Ok(()) } pub fn poll_exit(&mut self) -> Poll { - assert!(!self.reaped); loop { // Ensure that once we've successfully waited we won't try to // `kill` above. - if let Some(e) = try!(self.try_wait()) { - self.reaped = true; + if let Some(e) = try!(self.try_wait(false)) { return Ok(e.into()) } @@ -120,23 +116,35 @@ impl Child { } } - fn try_wait(&self) -> io::Result> { - let id = self.id() as c_int; - let mut status = 0; - loop { - match unsafe { libc::waitpid(id, &mut status, libc::WNOHANG) } { - 0 => return Ok(None), - n if n < 0 => { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::Interrupted { - continue - } - return Err(err) - } - n => { - assert_eq!(n, id); - return Ok(Some(ExitStatus::from_raw(status))) + fn try_wait(&mut self, block_on_wait: bool) -> io::Result> { + assert!(!self.reaped); + let exit = try!(try_wait_process(self.id() as libc::pid_t, block_on_wait)); + + if let Some(_) = exit { + self.reaped = true; + } + + Ok(exit) + } +} + +fn try_wait_process(id: libc::pid_t, block_on_wait: bool) -> io::Result> { + let wait_flags = if block_on_wait { 0 } else { libc::WNOHANG }; + let mut status = 0; + + loop { + match unsafe { libc::waitpid(id, &mut status, wait_flags) } { + 0 => return Ok(None), + n if n < 0 => { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue } + return Err(err) + } + n => { + assert_eq!(n, id); + return Ok(Some(ExitStatus::from_raw(status))) } } } From 0aceba21bd5217910a75b53f7ae26ac33604eb4a Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 9 May 2018 19:38:07 -0700 Subject: [PATCH 056/110] process: Bump to 0.1.6 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cfc24965e..6ed88797e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-process" -version = "0.1.5" +version = "0.1.6" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" From ad5179b2d5997171ff2c56e3ff431dd9248082db Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 8 Apr 2018 15:43:53 -0700 Subject: [PATCH 057/110] process: Remove all items deprecated in 0.1 --- src/lib.rs | 167 +------------------------------------------------ tests/stdio.rs | 19 ------ 2 files changed, 1 insertion(+), 185 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bc812729a..774a9fe0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,13 +124,11 @@ extern crate tokio_core; extern crate tokio_io; extern crate mio; -use std::ffi::OsStr; use std::io::{self, Read, Write}; -use std::path::Path; use std::process::{self, ExitStatus, Output, Stdio}; use futures::{Future, Poll, IntoFuture}; -use futures::future::{Flatten, FutureResult, Either, ok}; +use futures::future::{Either, ok}; use std::fmt; use tokio_core::reactor::Handle; use tokio_io::io::{read_to_end}; @@ -171,25 +169,6 @@ pub trait CommandExt { /// event loop. fn spawn_async(&mut self, handle: &Handle) -> io::Result; - /// Executes a command as a child process, waiting for it to finish and - /// collecting its exit status. - /// - /// By default, stdin, stdout and stderr are inherited from the parent. - /// - /// The `StatusAsync` future returned will resolve to the `ExitStatus` - /// type in the standard library representing how the process exited. If - /// any input/output handles are set to a pipe then they will be immediately - /// closed after the child is spawned. - /// - /// The `handle` specified must be a handle to a valid event loop, and all - /// I/O this child does will be associated with the specified event loop. - /// - /// If the `StatusAsync` future is dropped before the future resolves, then - /// the child will be killed, if it was spawned. - #[deprecated(note = "use the more flexible `spawn_async2` method instead")] - #[allow(deprecated)] - fn status_async(&mut self, handle: &Handle) -> StatusAsync; - /// Executes a command as a child process, waiting for it to finish and /// collecting its exit status. /// @@ -258,23 +237,6 @@ impl CommandExt for process::Command { Ok(child) } - #[allow(deprecated)] - fn status_async(&mut self, handle: &Handle) -> StatusAsync { - let mut inner = self.spawn_async(handle); - if let Ok(child) = inner.as_mut() { - // Ensure we close any stdio handles so we can't deadlock - // waiting on the child which may be waiting to read/write - // to a pipe we're holding. - child.stdin.take(); - child.stdout.take(); - child.stderr.take(); - } - - StatusAsync { - inner: inner.into_future().flatten(), - } - } - fn status_async2(&mut self, handle: &Handle) -> io::Result { self.spawn_async(handle).map(|mut child| { // Ensure we close any stdio handles so we can't deadlock @@ -476,29 +438,6 @@ impl Future for WaitWithOutput { } } -/// Future returned by the `CommandExt::status_async` method. -/// -/// This future is used to conveniently spawn a child and simply wait for its -/// exit status. This future will resolves to the `ExitStatus` type in the -/// standard library. -#[must_use = "futures do nothing unless polled"] -#[deprecated(note = "use the more flexible `SpawnAsync2` adapter instead")] -#[allow(deprecated)] -#[derive(Debug)] -pub struct StatusAsync { - inner: Flatten>, -} - -#[allow(deprecated)] -impl Future for StatusAsync { - type Item = ExitStatus; - type Error = io::Error; - - fn poll(&mut self) -> Poll { - self.inner.poll() - } -} - /// Future returned by the `CommandExt::status_async2` method. /// /// This future is used to conveniently spawn a child and simply wait for its @@ -611,107 +550,3 @@ impl Read for ChildStderr { impl AsyncRead for ChildStderr { } - -// deprecated from 0.1.0 - -#[deprecated(note = "use std::process::Command instead")] -#[allow(deprecated, missing_docs)] -#[allow(deprecated, missing_debug_implementations, missing_docs)] -#[doc(hidden)] -pub struct Command { - inner: process::Command, - #[allow(dead_code)] - handle: Handle, -} - -#[deprecated(note = "use std::process::Command instead")] -#[allow(deprecated, missing_debug_implementations, missing_docs)] -#[doc(hidden)] -pub struct Spawn { - inner: Box>, -} - -#[deprecated(note = "use std::process::Command instead")] -#[allow(deprecated, missing_docs)] -#[doc(hidden)] -impl Command { - pub fn new>(exe: T, handle: &Handle) -> Command { - Command::_new(exe.as_ref(), handle) - } - - fn _new(exe: &OsStr, handle: &Handle) -> Command { - Command { - inner: process::Command::new(exe), - handle: handle.clone(), - } - } - - pub fn arg>(&mut self, arg: S) -> &mut Command { - self._arg(arg.as_ref()) - } - - fn _arg(&mut self, arg: &OsStr) -> &mut Command { - self.inner.arg(arg); - self - } - - pub fn args>(&mut self, args: &[S]) -> &mut Command { - for arg in args { - self._arg(arg.as_ref()); - } - self - } - - pub fn env(&mut self, key: K, val: V) -> &mut Command - where K: AsRef, V: AsRef - { - self._env(key.as_ref(), val.as_ref()) - } - - fn _env(&mut self, key: &OsStr, val: &OsStr) -> &mut Command { - self.inner.env(key, val); - self - } - - pub fn env_remove>(&mut self, key: K) -> &mut Command { - self._env_remove(key.as_ref()) - } - - fn _env_remove(&mut self, key: &OsStr) -> &mut Command { - self.inner.env_remove(key); - self - } - - pub fn env_clear(&mut self) -> &mut Command { - self.inner.env_clear(); - self - } - - pub fn current_dir>(&mut self, dir: P) -> &mut Command { - self._current_dir(dir.as_ref()) - } - - fn _current_dir(&mut self, dir: &Path) -> &mut Command { - self.inner.current_dir(dir); - self - } - - pub fn spawn(mut self) -> Spawn { - Spawn { - inner: Box::new(self.inner.spawn_async(&self.handle).into_future()), - } - } -} - -#[deprecated(note = "use std::process::Command instead")] -#[allow(deprecated)] -#[doc(hidden)] -impl Future for Spawn { - type Item = Child; - type Error = io::Error; - - fn poll(&mut self) -> Poll { - self.inner.poll() - } -} - diff --git a/tests/stdio.rs b/tests/stdio.rs index 401e8082a..25e9fcd34 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -119,25 +119,6 @@ fn wait_with_output_captures() { assert_eq!(output.stderr.len(), 0); } -#[allow(deprecated)] -#[test] -fn status_closes_any_pipes() { - let mut core = Core::new().unwrap(); - - // Cat will open a pipe between the parent and child. - // If `status_async` doesn't ensure the handles are closed, - // we would end up blocking forever (and time out). - let child = cat().status_async(&core.handle()); - let timeout = Timeout::new(Duration::from_secs(1), &core.handle()) - .expect("timeout registration failed") - .map(|()| panic!("time out exceeded! did we get stuck waiting on the child?")); - - match core.run(child.select(timeout)) { - Ok((status, _)) => assert!(status.success()), - Err(_) => panic!("failed to run futures"), - } -} - #[test] fn status2_closes_any_pipes() { let mut core = Core::new().unwrap(); From de9b4014574c5be477d7566c03147165cb84e2f5 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 8 Apr 2018 15:55:26 -0700 Subject: [PATCH 058/110] process: Mark status_async2/StatusAsync2 as deprecated --- src/lib.rs | 43 +++++++++++++++++++++++++++++++++++++------ tests/stdio.rs | 6 +++--- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 774a9fe0e..8b84abfa8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,6 +169,29 @@ pub trait CommandExt { /// event loop. fn spawn_async(&mut self, handle: &Handle) -> io::Result; + /// Executes a command as a child process, waiting for it to finish and + /// collecting its exit status. + /// + /// By default, stdin, stdout and stderr are inherited from the parent. + /// + /// The `StatusAsync` future returned will resolve to the `ExitStatus` + /// type in the standard library representing how the process exited. If + /// any input/output handles are set to a pipe then they will be immediately + /// closed after the child is spawned. + /// + /// The `handle` specified must be a handle to a valid event loop, and all + /// I/O this child does will be associated with the specified event loop. + /// + /// If the `StatusAsync` future is dropped before the future resolves, then + /// the child will be killed, if it was spawned. + /// + /// # Errors + /// + /// This function will return an error immediately if the child process + /// cannot be spawned. Otherwise errors obtained while waiting for the child + /// are returned through the `StatusAsync` future. + fn status_async(&mut self, handle: &Handle) -> io::Result; + /// Executes a command as a child process, waiting for it to finish and /// collecting its exit status. /// @@ -190,7 +213,11 @@ pub trait CommandExt { /// This function will return an error immediately if the child process /// cannot be spawned. Otherwise errors obtained while waiting for the child /// are returned through the `StatusAsync2` future. - fn status_async2(&mut self, handle: &Handle) -> io::Result; + #[doc(hidden)] + #[deprecated(note = "renamed to `status_async`", since = "0.2.1")] + fn status_async2(&mut self, handle: &Handle) -> io::Result { + self.status_async(handle) + } /// Executes the command as a child process, waiting for it to finish and /// collecting all of its output. @@ -237,7 +264,7 @@ impl CommandExt for process::Command { Ok(child) } - fn status_async2(&mut self, handle: &Handle) -> io::Result { + fn status_async(&mut self, handle: &Handle) -> io::Result { self.spawn_async(handle).map(|mut child| { // Ensure we close any stdio handles so we can't deadlock // waiting on the child which may be waiting to read/write @@ -246,7 +273,7 @@ impl CommandExt for process::Command { child.stdout.take(); child.stderr.take(); - StatusAsync2 { + StatusAsync { inner: child, } }) @@ -438,18 +465,22 @@ impl Future for WaitWithOutput { } } -/// Future returned by the `CommandExt::status_async2` method. +#[doc(hidden)] +#[deprecated(note = "renamed to `StatusAsync`", since = "0.2.1")] +pub type StatusAsync2 = StatusAsync; + +/// Future returned by the `CommandExt::status_async` method. /// /// This future is used to conveniently spawn a child and simply wait for its /// exit status. This future will resolves to the `ExitStatus` type in the /// standard library. #[must_use = "futures do nothing unless polled"] #[derive(Debug)] -pub struct StatusAsync2 { +pub struct StatusAsync { inner: Child, } -impl Future for StatusAsync2 { +impl Future for StatusAsync { type Item = ExitStatus; type Error = io::Error; diff --git a/tests/stdio.rs b/tests/stdio.rs index 25e9fcd34..dd8a96adf 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -120,13 +120,13 @@ fn wait_with_output_captures() { } #[test] -fn status2_closes_any_pipes() { +fn status_closes_any_pipes() { let mut core = Core::new().unwrap(); // Cat will open a pipe between the parent and child. - // If `status_async2` doesn't ensure the handles are closed, + // If `status_async` doesn't ensure the handles are closed, // we would end up blocking forever (and time out). - let child = cat().status_async2(&core.handle()).unwrap(); + let child = cat().status_async(&core.handle()).unwrap(); let timeout = Timeout::new(Duration::from_secs(1), &core.handle()) .expect("timeout registration failed") .map(|()| panic!("time out exceeded! did we get stuck waiting on the child?")); From e6b044a8200cb46d18b078e5fcc787ae46f23882 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 13 May 2018 14:50:42 -0700 Subject: [PATCH 059/110] process: Bump tokio-signal version to 0.2 --- Cargo.toml | 3 +- src/lib.rs | 110 ++++++++++++++++++++++++++++++++++--------------- src/unix.rs | 6 +-- src/windows.rs | 4 +- tests/smoke.rs | 2 +- tests/stdio.rs | 8 ++-- 6 files changed, 88 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ed88797e..705290815 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ futures = "0.1.11" mio = "0.6.5" tokio-core = "0.1.6" tokio-io = "0.1" +tokio-reactor = "0.1" [dev-dependencies] env_logger = { version = "0.4", default-features = false } @@ -43,4 +44,4 @@ features = [ [target.'cfg(unix)'.dependencies] libc = "0.2" -tokio-signal = "0.1" +tokio-signal = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 8b84abfa8..0256ecd69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ //! // Use the standard library's `Command` type to build a process and //! // then execute it via the `CommandExt` trait. //! let child = Command::new("echo").arg("hello").arg("world") -//! .spawn_async(&core.handle()); +//! .spawn_async_with_handle(core.handle().new_tokio_handle()); //! //! // Make sure our child succeeded in spawning //! let child = child.expect("failed to spawn"); @@ -62,7 +62,7 @@ //! // Like above, but use `output_async` which returns a future instead of //! // immediately returning the `Child`. //! let output = Command::new("echo").arg("hello").arg("world") -//! .output_async(&core.handle()); +//! .output_async_with_handle(core.handle().new_tokio_handle()); //! let output = core.run(output).expect("failed to collect output"); //! //! assert!(output.status.success()); @@ -100,7 +100,7 @@ //! let mut core = Core::new().unwrap(); //! let mut cmd = Command::new("cat"); //! let mut cat = cmd.stdout(Stdio::piped()); -//! let child = cat.spawn_async(&core.handle()).unwrap(); +//! let child = cat.spawn_async_with_handle(core.handle().new_tokio_handle()).unwrap(); //! core.run(print_lines(child)).unwrap(); //! } //! ``` @@ -122,6 +122,7 @@ extern crate futures; extern crate tokio_core; extern crate tokio_io; +extern crate tokio_reactor; extern crate mio; use std::io::{self, Read, Write}; @@ -130,9 +131,9 @@ use std::process::{self, ExitStatus, Output, Stdio}; use futures::{Future, Poll, IntoFuture}; use futures::future::{Either, ok}; use std::fmt; -use tokio_core::reactor::Handle; use tokio_io::io::{read_to_end}; use tokio_io::{AsyncWrite, AsyncRead, IoFuture}; +use tokio_reactor::Handle; #[path = "unix.rs"] #[cfg(unix)] @@ -154,6 +155,22 @@ mod imp; /// I/O handles created from this crate are all asynchronous as well (differing /// from their `std` counterparts). pub trait CommandExt { + /// Executes the command as a child process, returning a handle to it. + /// + /// By default, stdin, stdout and stderr are inherited from the parent. + /// + /// This method will spawn the child process synchronously and return a + /// handle to a future-aware child process. The `Child` returned implements + /// `Future` itself to acquire the `ExitStatus` of the child, and otherwise + /// the `Child` has methods to acquire handles to the stdin, stdout, and + /// stderr streams. + /// + /// All I/O this child does will be associated with the current default + /// event loop. + fn spawn_async(&mut self) -> io::Result { + self.spawn_async_with_handle(&Handle::default()) + } + /// Executes the command as a child process, returning a handle to it. /// /// By default, stdin, stdout and stderr are inherited from the parent. @@ -167,7 +184,32 @@ pub trait CommandExt { /// The `handle` specified to this method must be a handle to a valid event /// loop, and all I/O this child does will be associated with the specified /// event loop. - fn spawn_async(&mut self, handle: &Handle) -> io::Result; + fn spawn_async_with_handle(&mut self, handle: &Handle) -> io::Result; + + /// Executes a command as a child process, waiting for it to finish and + /// collecting its exit status. + /// + /// By default, stdin, stdout and stderr are inherited from the parent. + /// + /// The `StatusAsync` future returned will resolve to the `ExitStatus` + /// type in the standard library representing how the process exited. If + /// any input/output handles are set to a pipe then they will be immediately + /// closed after the child is spawned. + /// + /// All I/O this child does will be associated with the current default + /// event loop. + /// + /// If the `StatusAsync` future is dropped before the future resolves, then + /// the child will be killed, if it was spawned. + /// + /// # Errors + /// + /// This function will return an error immediately if the child process + /// cannot be spawned. Otherwise errors obtained while waiting for the child + /// are returned through the `StatusAsync` future. + fn status_async(&mut self) -> io::Result { + self.status_async_with_handle(&Handle::default()) + } /// Executes a command as a child process, waiting for it to finish and /// collecting its exit status. @@ -190,33 +232,30 @@ pub trait CommandExt { /// This function will return an error immediately if the child process /// cannot be spawned. Otherwise errors obtained while waiting for the child /// are returned through the `StatusAsync` future. - fn status_async(&mut self, handle: &Handle) -> io::Result; + fn status_async_with_handle(&mut self, handle: &Handle) -> io::Result; - /// Executes a command as a child process, waiting for it to finish and - /// collecting its exit status. + /// Executes the command as a child process, waiting for it to finish and + /// collecting all of its output. /// - /// By default, stdin, stdout and stderr are inherited from the parent. + /// > **Note**: this method, unlike the standard library, will + /// > unconditionally configure the stdout/stderr handles to be pipes, even + /// > if they have been previously configured. If this is not desired then + /// > the `spawn_async` method should be used in combination with the + /// > `wait_with_output` method on child. /// - /// The `StatusAsync` future returned will resolve to the `ExitStatus` - /// type in the standard library representing how the process exited. If - /// any input/output handles are set to a pipe then they will be immediately - /// closed after the child is spawned. + /// This method will return a future representing the collection of the + /// child process's stdout/stderr. The `OutputAsync` future will resolve to + /// the `Output` type in the standard library, containing `stdout` and + /// `stderr` as `Vec` along with an `ExitStatus` representing how the + /// process exited. /// - /// The `handle` specified must be a handle to a valid event loop, and all - /// I/O this child does will be associated with the specified event loop. + /// All I/O this child does will be associated with the current default + /// event loop. /// - /// If the `StatusAsync` future is dropped before the future resolves, then + /// If the `OutputAsync` future is dropped before the future resolves, then /// the child will be killed, if it was spawned. - /// - /// # Errors - /// - /// This function will return an error immediately if the child process - /// cannot be spawned. Otherwise errors obtained while waiting for the child - /// are returned through the `StatusAsync2` future. - #[doc(hidden)] - #[deprecated(note = "renamed to `status_async`", since = "0.2.1")] - fn status_async2(&mut self, handle: &Handle) -> io::Result { - self.status_async(handle) + fn output_async(&mut self) -> OutputAsync { + self.output_async_with_handle(&Handle::default()) } /// Executes the command as a child process, waiting for it to finish and @@ -239,12 +278,12 @@ pub trait CommandExt { /// /// If the `OutputAsync` future is dropped before the future resolves, then /// the child will be killed, if it was spawned. - fn output_async(&mut self, handle: &Handle) -> OutputAsync; + fn output_async_with_handle(&mut self, handle: &Handle) -> OutputAsync; } impl CommandExt for process::Command { - fn spawn_async(&mut self, handle: &Handle) -> io::Result { + fn spawn_async_with_handle(&mut self, handle: &Handle) -> io::Result { let mut child = Child { child: imp::Child::new(try!(self.spawn()), handle), stdin: None, @@ -264,8 +303,8 @@ impl CommandExt for process::Command { Ok(child) } - fn status_async(&mut self, handle: &Handle) -> io::Result { - self.spawn_async(handle).map(|mut child| { + fn status_async_with_handle(&mut self, handle: &Handle) -> io::Result { + self.spawn_async_with_handle(handle).map(|mut child| { // Ensure we close any stdio handles so we can't deadlock // waiting on the child which may be waiting to read/write // to a pipe we're holding. @@ -279,13 +318,16 @@ impl CommandExt for process::Command { }) } - fn output_async(&mut self, handle: &Handle) -> OutputAsync { + fn output_async_with_handle(&mut self, handle: &Handle) -> OutputAsync { self.stdout(Stdio::piped()); self.stderr(Stdio::piped()); + + let inner = self.spawn_async_with_handle(handle) + .into_future() + .and_then(|c| c.wait_with_output()); + OutputAsync { - inner: Box::new(self.spawn_async(handle).into_future().and_then(|c| { - c.wait_with_output() - })), + inner: Box::new(inner), } } } diff --git a/src/unix.rs b/src/unix.rs index 123e68602..7dcbe2ab0 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -37,7 +37,7 @@ use mio; use self::tokio_signal::unix::Signal; use std::fmt; use tokio_io::IoFuture; -use tokio_core::reactor::{Handle, PollEvented}; +use tokio_reactor::{Handle, PollEvented}; #[must_use = "futures do nothing unless polled"] pub struct Child { @@ -62,7 +62,7 @@ impl Child { Child { inner: inner, reaped: false, - sigchld: Signal::new(libc::SIGCHLD, handle).flatten_stream(), + sigchld: Signal::with_handle(libc::SIGCHLD, handle).flatten_stream(), } } @@ -224,6 +224,6 @@ fn stdio(option: Option, handle: &Handle) return Err(io::Error::last_os_error()) } } - let io = try!(PollEvented::new(Fd(io), handle)); + let io = try!(PollEvented::new_with_handle(Fd(io), handle)); Ok(Some(io)) } diff --git a/src/windows.rs b/src/windows.rs index d60d7b8de..83e57f457 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -36,7 +36,7 @@ use self::winapi::um::synchapi::*; use self::winapi::um::threadpoollegacyapiset::*; use self::winapi::um::winbase::*; use self::winapi::um::winnt::*; -use tokio_core::reactor::{PollEvented, Handle}; +use tokio_reactor::{Handle, PollEvented}; #[must_use = "futures do nothing unless polled"] pub struct Child { @@ -182,6 +182,6 @@ fn stdio(option: Option, handle: &Handle) None => return Ok(None), }; let pipe = unsafe { NamedPipe::from_raw_handle(io.into_raw_handle()) }; - let io = try!(PollEvented::new(pipe, handle)); + let io = try!(PollEvented::new_with_handle(pipe, handle)); Ok(Some(io)) } diff --git a/tests/smoke.rs b/tests/smoke.rs index 89f3e300b..69185bab9 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -11,7 +11,7 @@ fn simple() { let mut lp = Core::new().unwrap(); let mut cmd = support::cmd("exit"); cmd.arg("2"); - let mut child = cmd.spawn_async(&lp.handle()).unwrap(); + let mut child = cmd.spawn_async_with_handle(lp.handle().new_tokio_handle()).unwrap(); let id = child.id(); assert!(id > 0); let status = lp.run(&mut child).unwrap(); diff --git a/tests/stdio.rs b/tests/stdio.rs index dd8a96adf..6546cb44f 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -85,7 +85,7 @@ fn feed_cat(mut cat: Child, n: usize) -> Box Date: Sun, 13 May 2018 14:56:30 -0700 Subject: [PATCH 060/110] process: Remove dependency on `tokio-core` --- Cargo.toml | 2 +- src/lib.rs | 62 ++++++++++++++++++++++---------------------- tests/smoke.rs | 13 ++++++---- tests/stdio.rs | 46 ++++++++++++++++---------------- tests/support/mod.rs | 1 - 5 files changed, 63 insertions(+), 61 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 705290815..94953679c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,13 +18,13 @@ appveyor = { repository = "alexcrichton/tokio-process" } [dependencies] futures = "0.1.11" mio = "0.6.5" -tokio-core = "0.1.6" tokio-io = "0.1" tokio-reactor = "0.1" [dev-dependencies] env_logger = { version = "0.4", default-features = false } log = "0.4" +tokio = "0.1" [target.'cfg(windows)'.dependencies] mio-named-pipes = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 0256ecd69..611cd88a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,31 +14,27 @@ //! //! ```no_run //! extern crate futures; -//! extern crate tokio_core; +//! extern crate tokio; //! extern crate tokio_process; //! //! use std::process::Command; //! //! use futures::Future; -//! use tokio_core::reactor::Core; //! use tokio_process::CommandExt; //! //! fn main() { -//! // Create our own local event loop -//! let mut core = Core::new().unwrap(); -//! //! // Use the standard library's `Command` type to build a process and //! // then execute it via the `CommandExt` trait. //! let child = Command::new("echo").arg("hello").arg("world") -//! .spawn_async_with_handle(core.handle().new_tokio_handle()); +//! .spawn_async(); //! -//! // Make sure our child succeeded in spawning -//! let child = child.expect("failed to spawn"); +//! // Make sure our child succeeded in spawning and process the result +//! let future = child.expect("failed to spawn") +//! .map(|status| println!("exit status: {}", status)) +//! .map_err(|e| panic!("failed to wait for exit: {}", e)); //! -//! match core.run(child) { -//! Ok(status) => println!("exit status: {}", status), -//! Err(e) => panic!("failed to wait for exit: {}", e), -//! } +//! // Send the future to the tokio runtime for execution +//! tokio::run(future) //! } //! ``` //! @@ -47,26 +43,27 @@ //! //! ```no_run //! extern crate futures; -//! extern crate tokio_core; +//! extern crate tokio; //! extern crate tokio_process; //! //! use std::process::Command; //! //! use futures::Future; -//! use tokio_core::reactor::Core; //! use tokio_process::CommandExt; //! //! fn main() { -//! let mut core = Core::new().unwrap(); -//! //! // Like above, but use `output_async` which returns a future instead of //! // immediately returning the `Child`. //! let output = Command::new("echo").arg("hello").arg("world") -//! .output_async_with_handle(core.handle().new_tokio_handle()); -//! let output = core.run(output).expect("failed to collect output"); +//! .output_async(); //! -//! assert!(output.status.success()); -//! assert_eq!(output.stdout, b"hello world\n"); +//! let future = output.map_err(|e| panic!("failed to collect output: {}", e)) +//! .map(|output| { +//! assert!(output.status.success()); +//! assert_eq!(output.stdout, b"hello world\n"); +//! }); +//! +//! tokio::run(future); //! } //! ``` //! @@ -74,18 +71,17 @@ //! //! ```no_run //! extern crate futures; -//! extern crate tokio_core; +//! extern crate tokio; //! extern crate tokio_process; //! extern crate tokio_io; //! //! use std::io; -//! use std::process::{Command, Stdio, ExitStatus}; +//! use std::process::{Command, Stdio}; //! -//! use futures::{BoxFuture, Future, Stream}; -//! use tokio_core::reactor::Core; +//! use futures::{Future, Stream}; //! use tokio_process::{CommandExt, Child}; //! -//! fn print_lines(mut cat: Child) -> BoxFuture { +//! fn print_lines(mut cat: Child) -> Box + Send + 'static> { //! let stdout = cat.stdout().take().unwrap(); //! let reader = io::BufReader::new(stdout); //! let lines = tokio_io::io::lines(reader); @@ -93,15 +89,20 @@ //! println!("Line: {}", l); //! Ok(()) //! }); -//! cycle.join(cat).map(|((), s)| s).boxed() +//! +//! let future = cycle.join(cat) +//! .map(|_| ()) +//! .map_err(|e| panic!("{}", e)); +//! +//! Box::new(future) //! } //! //! fn main() { -//! let mut core = Core::new().unwrap(); //! let mut cmd = Command::new("cat"); -//! let mut cat = cmd.stdout(Stdio::piped()); -//! let child = cat.spawn_async_with_handle(core.handle().new_tokio_handle()).unwrap(); -//! core.run(print_lines(child)).unwrap(); +//! cmd.stdout(Stdio::piped()); +//! +//! let future = print_lines(cmd.spawn_async().expect("failed to spawn command")); +//! tokio::run(future); //! } //! ``` //! @@ -120,7 +121,6 @@ #![doc(html_root_url = "https://docs.rs/tokio-process/0.1")] extern crate futures; -extern crate tokio_core; extern crate tokio_io; extern crate tokio_reactor; extern crate mio; diff --git a/tests/smoke.rs b/tests/smoke.rs index 69185bab9..8aff3877a 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -1,21 +1,24 @@ -extern crate tokio_core; +extern crate tokio; extern crate tokio_process; -use tokio_core::reactor::Core; +use tokio::executor::current_thread; use tokio_process::CommandExt; mod support; #[test] fn simple() { - let mut lp = Core::new().unwrap(); let mut cmd = support::cmd("exit"); cmd.arg("2"); - let mut child = cmd.spawn_async_with_handle(lp.handle().new_tokio_handle()).unwrap(); + + let mut child = cmd.spawn_async().unwrap(); + let id = child.id(); assert!(id > 0); - let status = lp.run(&mut child).unwrap(); + + let status = current_thread::block_on_all(&mut child).unwrap(); assert_eq!(status.code(), Some(2)); + assert_eq!(child.id(), id); drop(child.kill()); } diff --git a/tests/stdio.rs b/tests/stdio.rs index 6546cb44f..ea39bceb5 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -1,5 +1,5 @@ extern crate futures; -extern crate tokio_core; +extern crate tokio; extern crate tokio_io; extern crate tokio_process; #[macro_use] @@ -9,12 +9,14 @@ extern crate env_logger; use std::io; use std::process::{Stdio, ExitStatus, Command}; use std::time::Duration; +use std::time::Instant; use futures::future::Future; use futures::stream::{self, Stream}; +use tokio::executor::current_thread; use tokio_io::io::{read_until, write_all, read_to_end}; -use tokio_core::reactor::{Core, Timeout}; use tokio_process::{CommandExt, Child}; +use tokio::timer::Deadline; mod support; @@ -84,34 +86,35 @@ fn feed_cat(mut cat: Child, n: usize) -> Box assert!(status.success()), - Err(_) => panic!("failed to run futures"), - } + // NB: Deadline requires a timer registration which is provided by + // tokio's `current_thread::Runtime`, but isn't available by just using + // tokio's default CurrentThread executor which powers `current_thread::block_on_all`. + let mut rt = tokio::runtime::current_thread::Runtime::new().unwrap(); + rt.block_on(Deadline::new(child, Instant::now() + Duration::from_secs(1))) + .expect("time out exceeded! did we get stuck waiting on the child?"); } diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 878afb12a..0c2014628 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -1,6 +1,5 @@ extern crate env_logger; extern crate futures; -extern crate tokio_core; extern crate tokio_process; use std::env; From 5e9d60e834c657da9d355f9bf852c2416bd4e2df Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 13 May 2018 17:25:40 -0700 Subject: [PATCH 061/110] process: Add a CHANGELOG --- CHANGELOG.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 ++++ 2 files changed, 68 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..438dce5f0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,64 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Changed +- **Breaking**: asynchronous spawning of a child process now requires using a +reactor handle from the `tokio` crate instead of the `tokio-core` crate +- Child processes may be spawned without specifying a `tokio` handle at all +(the current/default reactor handle will be used) +### Removed +- **Breaking**: removed all previously deprecated items + +## [0.1.6] - 2018-05-09 +### Fixed +- On Unix systems, any child processes that are `kill`ed (or implicitly killed +via dropping the child without calling `forget`) are no longer left in a zombie +state, which allows the OS to reclaim the process. + +## [0.1.5] - 2018-01-03 +### Changed +- Minimum required version of `winapi` has been bumped to `0.3`. + +## [0.1.4] - 2017-06-25 +### Fixed +- Added missing `Debug` impls on all types. +- Added missing `must_use` annotations on all futures. +- Ensure `status_async` closes child's stdio handles after spawning in order +to prevent potential deadlocks when attempting to interact with any pipes held +by the parent process. + +## [0.1.3] - 2017-03-15 +### Changed +- Minimum required version of `futures` has been bumped to `0.1.11`. +- Minimum required version of `mio` has been bumped to `0.6.5`. +- Minimum required version of `tokio-core` has been bumped to `0.1.6`. + +## [0.1.2] - 2017-01-24 +### Changed +- Minimum required version of `tokio-signal` has been bumped to `0.1.2`. +### Fixed +- The event loop which spawns the first async child no longer needs to be kept +alive for subsequent child spawns to make progress. + +## [0.1.1] - 2016-12-19 +### Added +- Support performing async I/O operations on the child's stdio handles. +### Changed +- Functionality has been reimplemented as the `CommandExt` extension trait +(implemented directly on `std::process::Command`) instead of going through +the locally vendored `Command` type. + +## 0.1.0 - 2016-09-10 +- First release! + +[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.1.6...HEAD +[0.1.6]: https://github.com/alexcrichton/tokio-process/compare/0.1.5...0.1.6 +[0.1.5]: https://github.com/alexcrichton/tokio-process/compare/0.1.4...0.1.5 +[0.1.4]: https://github.com/alexcrichton/tokio-process/compare/0.1.3...0.1.4 +[0.1.3]: https://github.com/alexcrichton/tokio-process/compare/0.1.2...0.1.3 +[0.1.2]: https://github.com/alexcrichton/tokio-process/compare/0.1.1...0.1.2 +[0.1.1]: https://github.com/alexcrichton/tokio-process/compare/0.1.0...0.1.1 diff --git a/Cargo.toml b/Cargo.toml index 94953679c..a60f4389e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,9 @@ [package] name = "tokio-process" +# When releasing to crates.io: +# - Update html_root_url. +# - Update CHANGELOG.md. +# - Create "vX.Y.Z" git tag. version = "0.1.6" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" From 7b3e4b98ac1f11dd28524edb0179152578b642f6 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 14 May 2018 19:54:38 -0700 Subject: [PATCH 062/110] process: Update Child::forget example to use the tokio runtime --- src/lib.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 611cd88a3..8d5f1722f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -436,27 +436,23 @@ impl Child { /// /// ```no_run /// # extern crate futures; - /// # extern crate tokio_core; + /// # extern crate tokio; /// # extern crate tokio_process; /// # /// # use std::process::Command; /// # /// # use futures::Future; - /// # use tokio_core::reactor::Core; /// # use tokio_process::CommandExt; /// # /// # fn main() { - /// let core = Core::new().unwrap(); - /// let handle = core.handle(); - /// /// let child = Command::new("echo").arg("hello").arg("world") - /// .spawn_async(&handle) + /// .spawn_async() /// .expect("failed to spawn"); /// /// let do_cleanup = child.map(|_| ()) // Ignore result /// .map_err(|_| ()); // Ignore errors /// - /// handle.spawn(do_cleanup); + /// tokio::spawn(do_cleanup); /// # } /// ``` pub fn forget(mut self) { From 827e77e71e19366d07a623df72857919a1276271 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 16 May 2018 18:12:17 -0700 Subject: [PATCH 063/110] process: Bump to 0.2.1 --- CHANGELOG.md | 5 ++++- Cargo.toml | 4 ++-- README.md | 2 +- src/lib.rs | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 438dce5f0..d8972982a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.2.1] - 2018-05-18 ### Changed - **Breaking**: asynchronous spawning of a child process now requires using a reactor handle from the `tokio` crate instead of the `tokio-core` crate @@ -55,7 +57,8 @@ the locally vendored `Command` type. ## 0.1.0 - 2016-09-10 - First release! -[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.1.6...HEAD +[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.1...HEAD +[0.2.1]: https://github.com/alexcrichton/tokio-process/compare/0.1.6...0.2.1 [0.1.6]: https://github.com/alexcrichton/tokio-process/compare/0.1.5...0.1.6 [0.1.5]: https://github.com/alexcrichton/tokio-process/compare/0.1.4...0.1.5 [0.1.4]: https://github.com/alexcrichton/tokio-process/compare/0.1.3...0.1.4 diff --git a/Cargo.toml b/Cargo.toml index a60f4389e..0fdd10ad8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,8 @@ name = "tokio-process" # When releasing to crates.io: # - Update html_root_url. # - Update CHANGELOG.md. -# - Create "vX.Y.Z" git tag. -version = "0.1.6" +# - Create "X.Y.Z" git tag. +version = "0.2.1" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" diff --git a/README.md b/README.md index 9ea45c549..07ac39f20 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ First, add this to your `Cargo.toml`: ```toml [dependencies] -tokio-process = "0.1.1" +tokio-process = "0.2" ``` Next, add this to your crate: diff --git a/src/lib.rs b/src/lib.rs index 8d5f1722f..ff5c5077b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,7 +118,7 @@ #![warn(missing_debug_implementations)] #![deny(missing_docs)] -#![doc(html_root_url = "https://docs.rs/tokio-process/0.1")] +#![doc(html_root_url = "https://docs.rs/tokio-process/0.2")] extern crate futures; extern crate tokio_io; From 9290602815c5f5c63817d9b246565445f94bd233 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 20 May 2018 20:37:21 -0700 Subject: [PATCH 064/110] process: Unix: preregister for signal notifications before polling child --- src/unix.rs | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 7dcbe2ab0..db41a361a 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -98,10 +98,10 @@ impl Child { pub fn poll_exit(&mut self) -> Poll { loop { - // Ensure that once we've successfully waited we won't try to - // `kill` above. - if let Some(e) = try!(self.try_wait(false)) { - return Ok(e.into()) + // Ensure we don't register for additional notifications + // if the child has already finished. + if self.reaped { + return Ok(Async::NotReady); } // If the child hasn't exited yet, then it's our responsibility to @@ -110,8 +110,34 @@ impl Child { // // As described in `spawn` above, we just indicate that we can // next make progress once a SIGCHLD is received. - if try!(self.sigchld.poll()).is_not_ready() { - return Ok(Async::NotReady) + // + // However, we will register for a notification on the next signal + // BEFORE we poll the child. Otherwise it is possible that the child + // can exit and the signal can arrive after we last polled the child, + // but before we've registered for a notification on the next signal + // (this can cause a deadlock if there are no more spawned children + // which can generate a different signal for us). A side effect of + // pre-registering for signal notifications is that when the child + // exits, we will have already registered for an additional + // notification we don't need to consume. If another signal arrives, + // this future's task will be notified/woken up again. Since the + // futures model allows for spurious wake ups this extra wakeup + // should not cause significant issues with parent futures. + let registered_interest = try!(self.sigchld.poll()).is_not_ready(); + + if let Some(e) = try!(self.try_wait(false)) { + return Ok(e.into()); + } + + // If our attempt to poll for the next signal was not ready, then + // we've arranged for our task to get notified and we can bail out. + if registered_interest { + return Ok(Async::NotReady); + } else { + // Otherwise, if the signal stream delivered a signal to us, we + // won't get notified at the next signal, so we'll loop and try + // again. + continue; } } } From 2b6695d25aeed4052e40ede67eab314bf71ef0e0 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 21 May 2018 19:12:38 -0700 Subject: [PATCH 065/110] process: Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8972982a..85eb78192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- Fixed a pathological situation where a signal could be missed if it arrived +after polling the child but before registering for a new notification ## [0.2.1] - 2018-05-18 ### Changed From 329ad3324cca44ce82d98971c60a2b8fcff6a00d Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 27 May 2018 15:02:36 -0700 Subject: [PATCH 066/110] process: Bump to 0.2.2 --- CHANGELOG.md | 5 ++++- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85eb78192..8f1b2a3c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.2.2] - 2018-05-27 ### Fixed - Fixed a pathological situation where a signal could be missed if it arrived after polling the child but before registering for a new notification @@ -60,7 +62,8 @@ the locally vendored `Command` type. ## 0.1.0 - 2016-09-10 - First release! -[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.1...HEAD +[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.2...HEAD +[0.2.2]: https://github.com/alexcrichton/tokio-process/compare/0.2.1...0.2.2 [0.2.1]: https://github.com/alexcrichton/tokio-process/compare/0.1.6...0.2.1 [0.1.6]: https://github.com/alexcrichton/tokio-process/compare/0.1.5...0.1.6 [0.1.5]: https://github.com/alexcrichton/tokio-process/compare/0.1.4...0.1.5 diff --git a/Cargo.toml b/Cargo.toml index 0fdd10ad8..985b3e09b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "tokio-process" # - Update html_root_url. # - Update CHANGELOG.md. # - Create "X.Y.Z" git tag. -version = "0.2.1" +version = "0.2.2" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" From f7c4e3cd84a6ca96b2468d31ed4fc826952e06c0 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Fri, 24 Aug 2018 18:16:31 -0700 Subject: [PATCH 067/110] process: Bump min supported rustc version to 1.25 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 304d54f72..046dd06a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ sudo: false matrix: include: - - rust: 1.21.0 + - rust: 1.25.0 - rust: stable - os: osx - rust: beta From d3b2efc815619a14c0b927d4e58c58db3014b46f Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Tue, 28 Aug 2018 16:17:18 -0700 Subject: [PATCH 068/110] process: Add regression test for signal starvation --- tests/issue_42.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/issue_42.rs diff --git a/tests/issue_42.rs b/tests/issue_42.rs new file mode 100644 index 000000000..0bc55b621 --- /dev/null +++ b/tests/issue_42.rs @@ -0,0 +1,47 @@ +#![cfg(unix)] + +extern crate futures; +extern crate tokio_process; + +use futures::{Future, IntoFuture, Stream, stream}; +use std::process::{Command, Stdio}; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::time::Duration; +use tokio_process::CommandExt; + +fn run_test() { + let finished = Arc::new(AtomicBool::new(false)); + let finished_clone = finished.clone(); + + thread::spawn(move || { + let _ = stream::iter_ok((0..2).into_iter()) + .map(|i| Command::new("echo") + .arg(format!("I am spawned process #{}", i)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn_async() + .into_future() + .flatten() + ) + .buffered(2) + .collect() + .wait(); + + finished_clone.store(true, Ordering::SeqCst); + }); + + thread::sleep(Duration::from_millis(100)); + assert!(finished.load(Ordering::SeqCst), "FINISHED flag not set, maybe we deadlocked?"); +} + +#[test] +fn issue_42() { + let max = 10; + for i in 0..max { + println!("running {}/{}", i, max); + run_test() + } +} From 1581c8b475d3008f0e53a42d3213bcbdd43f753e Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 29 Aug 2018 19:41:26 -0700 Subject: [PATCH 069/110] process: Bump minimum required version of tokio-signal to 0.2.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 985b3e09b..b77bf9ba4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,4 +48,4 @@ features = [ [target.'cfg(unix)'.dependencies] libc = "0.2" -tokio-signal = "0.2" +tokio-signal = "0.2.5" From 5f18bf669ff1650098d8c0ee3887b54765f9ba27 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 29 Aug 2018 19:53:51 -0700 Subject: [PATCH 070/110] process: Bump minimum supported rustc version to 1.26 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 046dd06a7..83cbd34f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ sudo: false matrix: include: - - rust: 1.25.0 + - rust: 1.26.0 - rust: stable - os: osx - rust: beta From 3b43262a10acfd991d6cb0261c513ebb1e952087 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Wed, 26 Sep 2018 23:47:17 +0900 Subject: [PATCH 071/110] process: Implement AsRawFd for inner Fd wrappers and use it instead of self.0 --- src/unix.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index db41a361a..1df1dd161 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -195,6 +195,12 @@ impl io::Write for Fd { } } +impl AsRawFd for Fd where T: AsRawFd { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + pub type ChildStdin = PollEvented>; pub type ChildStdout = PollEvented>; pub type ChildStderr = PollEvented>; @@ -206,10 +212,10 @@ impl Evented for Fd where T: AsRawFd { interest: Ready, opts: PollOpt) -> io::Result<()> { - EventedFd(&self.0.as_raw_fd()).register(poll, - token, - interest | UnixReady::hup(), - opts) + EventedFd(&self.as_raw_fd()).register(poll, + token, + interest | UnixReady::hup(), + opts) } fn reregister(&self, @@ -218,14 +224,14 @@ impl Evented for Fd where T: AsRawFd { interest: Ready, opts: PollOpt) -> io::Result<()> { - EventedFd(&self.0.as_raw_fd()).reregister(poll, - token, - interest | UnixReady::hup(), - opts) + EventedFd(&self.as_raw_fd()).reregister(poll, + token, + interest | UnixReady::hup(), + opts) } fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { - EventedFd(&self.0.as_raw_fd()).deregister(poll) + EventedFd(&self.as_raw_fd()).deregister(poll) } } From e0e9594f710aacf33eee9be45c6bdfcb977cc07f Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Thu, 27 Sep 2018 21:49:53 +0900 Subject: [PATCH 072/110] process: Implement AsRawFd for ChildStd* structs --- src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ff5c5077b..e6618a747 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -619,3 +619,27 @@ impl Read for ChildStderr { impl AsyncRead for ChildStderr { } + +#[cfg(unix)] +mod sys { + use std::os::unix::io::{AsRawFd, RawFd}; + use super::{ChildStdin, ChildStdout, ChildStderr}; + + impl AsRawFd for ChildStdin { + fn as_raw_fd(&self) -> RawFd { + self.inner.get_ref().as_raw_fd() + } + } + + impl AsRawFd for ChildStdout { + fn as_raw_fd(&self) -> RawFd { + self.inner.get_ref().as_raw_fd() + } + } + + impl AsRawFd for ChildStderr { + fn as_raw_fd(&self) -> RawFd { + self.inner.get_ref().as_raw_fd() + } + } +} From 76438c9e7035937a2a837828b211d07681cad95f Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 28 Oct 2018 20:31:27 -0700 Subject: [PATCH 073/110] process: Implement AsRawHandle for ChildStd{in, out, err} for parity --- CHANGELOG.md | 3 +++ src/lib.rs | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1b2a3c2..f6cd03971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +* `ChildStd{in, out, err}` now implement `AsRawFd`/`AsRawHandle` on Unix/Windows +systems, respectively. ## [0.2.2] - 2018-05-27 ### Fixed diff --git a/src/lib.rs b/src/lib.rs index e6618a747..5a2a055d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -643,3 +643,27 @@ mod sys { } } } + +#[cfg(windows)] +mod sys { + use std::os::windows::io::{AsRawHandle, RawHandle}; + use super::{ChildStdin, ChildStdout, ChildStderr}; + + impl AsRawHandle for ChildStdin { + fn as_raw_handle(&self) -> RawHandle { + self.inner.get_ref().as_raw_handle() + } + } + + impl AsRawHandle for ChildStdout { + fn as_raw_handle(&self) -> RawHandle { + self.inner.get_ref().as_raw_handle() + } + } + + impl AsRawHandle for ChildStderr { + fn as_raw_handle(&self) -> RawHandle { + self.inner.get_ref().as_raw_handle() + } + } +} From c94f607f1b09ce28b5ee69f05294fae1e4a72961 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Thu, 1 Nov 2018 20:43:50 -0700 Subject: [PATCH 074/110] process: Fix some test case deprecation warnings --- Cargo.toml | 1 + tests/smoke.rs | 5 ++--- tests/stdio.rs | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b77bf9ba4..9592e5cea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ tokio-reactor = "0.1" env_logger = { version = "0.4", default-features = false } log = "0.4" tokio = "0.1" +tokio-current-thread = "0.1" [target.'cfg(windows)'.dependencies] mio-named-pipes = "0.1" diff --git a/tests/smoke.rs b/tests/smoke.rs index 8aff3877a..e14a59429 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -1,7 +1,6 @@ -extern crate tokio; +extern crate tokio_current_thread; extern crate tokio_process; -use tokio::executor::current_thread; use tokio_process::CommandExt; mod support; @@ -16,7 +15,7 @@ fn simple() { let id = child.id(); assert!(id > 0); - let status = current_thread::block_on_all(&mut child).unwrap(); + let status = tokio_current_thread::block_on_all(&mut child).unwrap(); assert_eq!(status.code(), Some(2)); assert_eq!(child.id(), id); diff --git a/tests/stdio.rs b/tests/stdio.rs index ea39bceb5..045d127b3 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -1,5 +1,6 @@ extern crate futures; extern crate tokio; +extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_process; #[macro_use] @@ -9,14 +10,12 @@ extern crate env_logger; use std::io; use std::process::{Stdio, ExitStatus, Command}; use std::time::Duration; -use std::time::Instant; use futures::future::Future; use futures::stream::{self, Stream}; -use tokio::executor::current_thread; use tokio_io::io::{read_until, write_all, read_to_end}; use tokio_process::{CommandExt, Child}; -use tokio::timer::Deadline; +use tokio::timer::Timeout; mod support; @@ -87,7 +86,7 @@ fn feed_cat(mut cat: Child, n: usize) -> Box Date: Thu, 1 Nov 2018 20:47:40 -0700 Subject: [PATCH 075/110] process: Bump version to 0.2.3 --- CHANGELOG.md | 5 ++++- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6cd03971..9c16b601f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.2.2] - 2018-11-01 ### Added * `ChildStd{in, out, err}` now implement `AsRawFd`/`AsRawHandle` on Unix/Windows systems, respectively. @@ -65,7 +67,8 @@ the locally vendored `Command` type. ## 0.1.0 - 2016-09-10 - First release! -[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.2...HEAD +[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.3...HEAD +[0.2.3]: https://github.com/alexcrichton/tokio-process/compare/0.2.2...0.2.3 [0.2.2]: https://github.com/alexcrichton/tokio-process/compare/0.2.1...0.2.2 [0.2.1]: https://github.com/alexcrichton/tokio-process/compare/0.1.6...0.2.1 [0.1.6]: https://github.com/alexcrichton/tokio-process/compare/0.1.5...0.1.6 diff --git a/Cargo.toml b/Cargo.toml index 9592e5cea..101fda5b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "tokio-process" # - Update html_root_url. # - Update CHANGELOG.md. # - Create "X.Y.Z" git tag. -version = "0.2.2" +version = "0.2.3" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" From ecdfe4c4747f8253b0937291cafab4d607923d15 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 12 May 2019 23:53:19 -0700 Subject: [PATCH 076/110] process: ci: collect code coverage info via cargo-tarpaulin --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 83cbd34f0..1115e2a9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,15 +7,22 @@ matrix: - rust: stable - os: osx - rust: beta - - rust: nightly - rust: nightly + sudo: required + addons: + apt: + packages: + - libssl-dev before_script: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH + - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin script: + - cargo test - cargo doc --no-deps --all-features after_success: - travis-cargo --only nightly doc-upload + - cargo tarpaulin -v --forward --out Xml script: - cargo test From e7dfcf90fe69f28d8fae2a76f470ad534b493528 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 13 May 2019 00:01:15 -0700 Subject: [PATCH 077/110] process: ci: Enable code coverage tracking via codecov.io --- .travis.yml | 1 + Cargo.toml | 1 + README.md | 1 + codecov.yml | 6 ++++++ 4 files changed, 9 insertions(+) create mode 100644 codecov.yml diff --git a/.travis.yml b/.travis.yml index 1115e2a9f..a30d39ba5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ matrix: after_success: - travis-cargo --only nightly doc-upload - cargo tarpaulin -v --forward --out Xml + - bash <(curl -s https://codecov.io/bash) script: - cargo test diff --git a/Cargo.toml b/Cargo.toml index 101fda5b2..a5f641d1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ categories = ["asynchronous"] [badges] travis-ci = { repository = "alexcrichton/tokio-process" } appveyor = { repository = "alexcrichton/tokio-process" } +codecov = { repository = "alexcrichton/tokio-process" } [dependencies] futures = "0.1.11" diff --git a/README.md b/README.md index 07ac39f20..0d3e35bc8 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ An implementation of process management for Tokio [![Build Status](https://travis-ci.org/alexcrichton/tokio-process.svg?branch=master)](https://travis-ci.org/alexcrichton/tokio-process) [![Build status](https://ci.appveyor.com/api/projects/status/43c8g7fy801e5902?svg=true)](https://ci.appveyor.com/project/alexcrichton/tokio-process) [![Crates.io](https://img.shields.io/crates/v/tokio-process.svg?maxAge=2592000)](https://crates.io/crates/tokio-process) +[![Coverage](https://img.shields.io/codecov/c/github/alexcrichton/tokio-process/master.svg)](https://codecov.io/gh/alexcrichton/tokio-process) [Documentation](https://docs.rs/tokio-process) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..04cde1cdc --- /dev/null +++ b/codecov.yml @@ -0,0 +1,6 @@ +ignore: + - "src/bin" + - "tests" + +comment: + behavior: new From 025474dfbb740f135288dc4615facfb6fcceca2c Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 13 May 2019 00:30:15 -0700 Subject: [PATCH 078/110] process: ci: Install cargo-tarpaulin *after* initial tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a30d39ba5..b8fa99b63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,12 +16,12 @@ matrix: - libssl-dev before_script: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH - - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin script: - cargo test - cargo doc --no-deps --all-features after_success: - travis-cargo --only nightly doc-upload + - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin - cargo tarpaulin -v --forward --out Xml - bash <(curl -s https://codecov.io/bash) From c78fd6d6c5da728309bbab6aa2c95e122b43efeb Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 27 May 2019 14:35:16 -0700 Subject: [PATCH 079/110] process: Update Travis link from .org to .com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d3e35bc8..8755b1823 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An implementation of process management for Tokio -[![Build Status](https://travis-ci.org/alexcrichton/tokio-process.svg?branch=master)](https://travis-ci.org/alexcrichton/tokio-process) +[![Build Status](https://travis-ci.com/alexcrichton/tokio-process.svg?branch=master)](https://travis-ci.com/alexcrichton/tokio-process) [![Build status](https://ci.appveyor.com/api/projects/status/43c8g7fy801e5902?svg=true)](https://ci.appveyor.com/project/alexcrichton/tokio-process) [![Crates.io](https://img.shields.io/crates/v/tokio-process.svg?maxAge=2592000)](https://crates.io/crates/tokio-process) [![Coverage](https://img.shields.io/codecov/c/github/alexcrichton/tokio-process/master.svg)](https://codecov.io/gh/alexcrichton/tokio-process) From 91dbf24cf4aaf590b517ab14b90c22061713f57e Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 27 May 2019 17:50:13 -0700 Subject: [PATCH 080/110] process: Update min supported rust version as per the Tokio project policy --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b8fa99b63..d726d628c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,14 @@ sudo: false matrix: include: - - rust: 1.26.0 + # This represents the minimum Rust version supported by + # Tokio. Updating this should be done in a dedicated PR and + # cannot be greater than two 0.x releases prior to the + # current stable. + # + # Tests are not run as tests may require newer versions of + # rust. + - rust: 1.33.0 - rust: stable - os: osx - rust: beta From b37120f61c8e1e1aa9b7496952e04b713e05e913 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 27 May 2019 17:52:48 -0700 Subject: [PATCH 081/110] process: Update line-by-line doc example to be more flexible --- Cargo.toml | 1 + src/lib.rs | 80 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5f641d1f..ab8baac95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ tokio-reactor = "0.1" [dev-dependencies] env_logger = { version = "0.4", default-features = false } +failure = "0.1" log = "0.4" tokio = "0.1" tokio-current-thread = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 5a2a055d5..b89a1ce78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,39 +70,77 @@ //! We can also read input line by line. //! //! ```no_run +//! extern crate failure; //! extern crate futures; //! extern crate tokio; //! extern crate tokio_process; //! extern crate tokio_io; //! -//! use std::io; -//! use std::process::{Command, Stdio}; -//! +//! use failure::Error; //! use futures::{Future, Stream}; -//! use tokio_process::{CommandExt, Child}; +//! use std::io::BufReader; +//! use std::process::{Command, Stdio}; +//! use tokio_process::{Child, ChildStdout, CommandExt}; //! -//! fn print_lines(mut cat: Child) -> Box + Send + 'static> { -//! let stdout = cat.stdout().take().unwrap(); -//! let reader = io::BufReader::new(stdout); -//! let lines = tokio_io::io::lines(reader); -//! let cycle = lines.for_each(|l| { -//! println!("Line: {}", l); -//! Ok(()) -//! }); +//! fn lines_stream(child: &mut Child) -> impl Stream + Send + 'static { +//! let stdout = child.stdout().take() +//! .expect("child did not have a handle to stdout"); //! -//! let future = cycle.join(cat) -//! .map(|_| ()) -//! .map_err(|e| panic!("{}", e)); -//! -//! Box::new(future) +//! tokio_io::io::lines(BufReader::new(stdout)) +//! // Convert any io::Error into a failure::Error for better flexibility +//! .map_err(|e| Error::from(e)) +//! // We print each line we've received here as an example of a way we can +//! // do something with the data. This can be changed to map the data to +//! // something else, or to consume it differently. +//! .inspect(|line| println!("Line: {}", line)) //! } //! //! fn main() { -//! let mut cmd = Command::new("cat"); -//! cmd.stdout(Stdio::piped()); +//! // Lazily invoke any code so it can run directly within the tokio runtime +//! tokio::run(futures::lazy(|| { +//! let mut cmd = Command::new("cat"); //! -//! let future = print_lines(cmd.spawn_async().expect("failed to spawn command")); -//! tokio::run(future); +//! // Specify that we want the command's standard output piped back to us. +//! // By default, standard input/output/error will be inherited from the +//! // current process (for example, this means that standard input will +//! // come from the keyboard and standard output/error will go directly to +//! // the terminal if this process is invoked from the command line). +//! cmd.stdout(Stdio::piped()); +//! +//! let mut child = cmd.spawn_async() +//! .expect("failed to spawn command"); +//! +//! let lines = lines_stream(&mut child); +//! +//! // Spawning into the tokio runtime requires that the future's Item and +//! // Error are both `()`. This is because tokio doesn't know what to do +//! // with any results or errors, so it requires that we've handled them! +//! // +//! // We can replace these sample usages of the child's exit status (or +//! // an encountered error) perform some different actions if needed! +//! // For example, log the error, or send a message on a channel, etc. +//! let child_future = child +//! .map(|status| println!("child status was: {}", status)) +//! .map_err(|e| panic!("error while running child: {}", e)); +//! +//! // Ensure the child process can live on within the runtime, otherwise +//! // the process will get killed if this handle is dropped +//! tokio::spawn(child_future); +//! +//! // Return a future to tokio. This is the same as calling using +//! // `tokio::spawn` above, but without having to return a dummy future +//! // here. +//! lines +//! // Convert the stream of values into a future which will resolve +//! // once the entire stream has been consumed. In this example we +//! // don't need to do anything with the data within the `for_each` +//! // call, but you can extend this to do something else (keep in mind +//! // that the stream will not produce items until the future returned +//! // from the closure resolves). +//! .for_each(|_| Ok(())) +//! // Similarly we "handle" any errors that arise, as required by tokio. +//! .map_err(|e| panic!("error while processing lines: {}", e)) +//! })); //! } //! ``` //! From 83a55601ef2fca5334b4c21e216e61ad8c7690df Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Thu, 23 May 2019 22:31:19 -0700 Subject: [PATCH 082/110] process: Move src/unix.rs to src/unix/mod.rs --- src/lib.rs | 2 +- src/{unix.rs => unix/mod.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{unix.rs => unix/mod.rs} (100%) diff --git a/src/lib.rs b/src/lib.rs index b89a1ce78..16efa49d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,7 +173,7 @@ use tokio_io::io::{read_to_end}; use tokio_io::{AsyncWrite, AsyncRead, IoFuture}; use tokio_reactor::Handle; -#[path = "unix.rs"] +#[path = "unix/mod.rs"] #[cfg(unix)] mod imp; diff --git a/src/unix.rs b/src/unix/mod.rs similarity index 100% rename from src/unix.rs rename to src/unix/mod.rs From 10fd2afd186265a5dd87feb5d409bb38708af221 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 May 2019 13:46:56 -0700 Subject: [PATCH 083/110] process: Simplify child IO registration --- src/lib.rs | 29 ++++++++++------------------- src/unix/mod.rs | 27 ++++++++++----------------- src/windows.rs | 29 +++++++++++------------------ 3 files changed, 31 insertions(+), 54 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 16efa49d4..eced66ad6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,7 +164,7 @@ extern crate tokio_reactor; extern crate mio; use std::io::{self, Read, Write}; -use std::process::{self, ExitStatus, Output, Stdio}; +use std::process::{Command, ExitStatus, Output, Stdio}; use futures::{Future, Poll, IntoFuture}; use futures::future::{Either, ok}; @@ -320,25 +320,16 @@ pub trait CommandExt { } -impl CommandExt for process::Command { +impl CommandExt for Command { fn spawn_async_with_handle(&mut self, handle: &Handle) -> io::Result { - let mut child = Child { - child: imp::Child::new(try!(self.spawn()), handle), - stdin: None, - stdout: None, - stderr: None, - kill_on_drop: true, - }; - child.stdin = try!(child.child.register_stdin(handle)).map(|io| { - ChildStdin { inner: io } - }); - child.stdout = try!(child.child.register_stdout(handle)).map(|io| { - ChildStdout { inner: io } - }); - child.stderr = try!(child.child.register_stderr(handle)).map(|io| { - ChildStderr { inner: io } - }); - Ok(child) + imp::Child::new(self.spawn()?, handle) + .map(|(child, stdin, stdout, stderr)| Child { + child, + stdin: stdin.map(|inner| ChildStdin { inner }), + stdout: stdout.map(|inner| ChildStdout { inner }), + stderr: stderr.map(|inner| ChildStderr { inner }), + kill_on_drop: true, + }) } fn status_async_with_handle(&mut self, handle: &Handle) -> io::Result { diff --git a/src/unix/mod.rs b/src/unix/mod.rs index 1df1dd161..61980a531 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -58,27 +58,20 @@ impl fmt::Debug for Child { } impl Child { - pub fn new(inner: process::Child, handle: &Handle) -> Child { - Child { + pub fn new(mut inner: process::Child, handle: &Handle) + -> io::Result<(Child, Option, Option, Option)> + { + let stdin = stdio(inner.stdin.take(), handle)?; + let stdout = stdio(inner.stdout.take(), handle)?; + let stderr = stdio(inner.stderr.take(), handle)?; + + let child = Child { inner: inner, reaped: false, sigchld: Signal::with_handle(libc::SIGCHLD, handle).flatten_stream(), - } - } + }; - pub fn register_stdin(&mut self, handle: &Handle) - -> io::Result> { - stdio(self.inner.stdin.take(), handle) - } - - pub fn register_stdout(&mut self, handle: &Handle) - -> io::Result> { - stdio(self.inner.stdout.take(), handle) - } - - pub fn register_stderr(&mut self, handle: &Handle) - -> io::Result> { - stdio(self.inner.stderr.take(), handle) + Ok((child, stdin, stdout, stderr)) } pub fn id(&self) -> u32 { diff --git a/src/windows.rs b/src/windows.rs index 83e57f457..9fa5bdfe1 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -64,26 +64,19 @@ unsafe impl Sync for Waiting {} unsafe impl Send for Waiting {} impl Child { - pub fn new(child: process::Child, _handle: &Handle) -> Child { - Child { - child: child, + pub fn new(mut inner: process::Child, handle: &Handle) + -> io::Result<(Child, Option, Option, Option)> + { + let stdin = stdio(inner.stdin.take(), handle)?; + let stdout = stdio(inner.stdout.take(), handle)?; + let stderr = stdio(inner.stderr.take(), handle)?; + + let child = Child { + child: inner, waiting: None, - } - } + }; - pub fn register_stdin(&mut self, handle: &Handle) - -> io::Result> { - stdio(self.child.stdin.take(), handle) - } - - pub fn register_stdout(&mut self, handle: &Handle) - -> io::Result> { - stdio(self.child.stdout.take(), handle) - } - - pub fn register_stderr(&mut self, handle: &Handle) - -> io::Result> { - stdio(self.child.stderr.take(), handle) + Ok((child, stdin, stdout, stderr)) } pub fn id(&self) -> u32 { From db0c4147c8cd6dc75c968410466d0eef68760149 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 May 2019 15:19:36 -0700 Subject: [PATCH 084/110] process: Refactor Unix process handling --- src/unix/mod.rs | 120 ++++-------------- src/unix/reap.rs | 313 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 336 insertions(+), 97 deletions(-) create mode 100644 src/unix/reap.rs diff --git a/src/unix/mod.rs b/src/unix/mod.rs index 61980a531..2ac0a128b 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -24,35 +24,44 @@ extern crate libc; extern crate tokio_signal; -use std::io; -use std::os::unix::prelude::*; -use std::process::{self, ExitStatus}; +mod reap; use futures::future::FlattenStream; -use futures::{Future, Poll, Async, Stream}; +use futures::{Future, Poll}; use mio::unix::{EventedFd, UnixReady}; use mio::{PollOpt, Ready, Token}; use mio::event::Evented; use mio; +use self::reap::{EventedReaper, Kill, Wait}; use self::tokio_signal::unix::Signal; use std::fmt; +use std::io; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::process::{self, ExitStatus}; use tokio_io::IoFuture; use tokio_reactor::{Handle, PollEvented}; +impl Wait for process::Child { + fn try_wait(&mut self) -> io::Result> { + self.try_wait() + } +} + +impl Kill for process::Child { + fn kill(&mut self) -> io::Result<()> { + self.kill() + } +} + #[must_use = "futures do nothing unless polled"] pub struct Child { - inner: process::Child, - reaped: bool, - sigchld: FlattenStream>, + inner: EventedReaper>>, } impl fmt::Debug for Child { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("Child") .field("pid", &self.inner.id()) - .field("inner", &self.inner) - .field("reaped", &self.reaped) - .field("sigchld", &"..") .finish() } } @@ -65,10 +74,9 @@ impl Child { let stdout = stdio(inner.stdout.take(), handle)?; let stderr = stdio(inner.stderr.take(), handle)?; + let signal = Signal::with_handle(libc::SIGCHLD, handle).flatten_stream(); let child = Child { - inner: inner, - reaped: false, - sigchld: Signal::with_handle(libc::SIGCHLD, handle).flatten_stream(), + inner: EventedReaper::new(inner, signal), }; Ok((child, stdin, stdout, stderr)) @@ -79,93 +87,11 @@ impl Child { } pub fn kill(&mut self) -> io::Result<()> { - if !self.reaped { - // NB: SIGKILL cannnot be caught, so the process will definitely exit immediately. - // We're not waiting for the process itself but for the kernel to execute the kill. - self.inner.kill()?; - let _ = self.try_wait(true); - } - - Ok(()) + self.inner.kill() } pub fn poll_exit(&mut self) -> Poll { - loop { - // Ensure we don't register for additional notifications - // if the child has already finished. - if self.reaped { - return Ok(Async::NotReady); - } - - // If the child hasn't exited yet, then it's our responsibility to - // ensure the current task gets notified when it might be able to - // make progress. - // - // As described in `spawn` above, we just indicate that we can - // next make progress once a SIGCHLD is received. - // - // However, we will register for a notification on the next signal - // BEFORE we poll the child. Otherwise it is possible that the child - // can exit and the signal can arrive after we last polled the child, - // but before we've registered for a notification on the next signal - // (this can cause a deadlock if there are no more spawned children - // which can generate a different signal for us). A side effect of - // pre-registering for signal notifications is that when the child - // exits, we will have already registered for an additional - // notification we don't need to consume. If another signal arrives, - // this future's task will be notified/woken up again. Since the - // futures model allows for spurious wake ups this extra wakeup - // should not cause significant issues with parent futures. - let registered_interest = try!(self.sigchld.poll()).is_not_ready(); - - if let Some(e) = try!(self.try_wait(false)) { - return Ok(e.into()); - } - - // If our attempt to poll for the next signal was not ready, then - // we've arranged for our task to get notified and we can bail out. - if registered_interest { - return Ok(Async::NotReady); - } else { - // Otherwise, if the signal stream delivered a signal to us, we - // won't get notified at the next signal, so we'll loop and try - // again. - continue; - } - } - } - - fn try_wait(&mut self, block_on_wait: bool) -> io::Result> { - assert!(!self.reaped); - let exit = try!(try_wait_process(self.id() as libc::pid_t, block_on_wait)); - - if let Some(_) = exit { - self.reaped = true; - } - - Ok(exit) - } -} - -fn try_wait_process(id: libc::pid_t, block_on_wait: bool) -> io::Result> { - let wait_flags = if block_on_wait { 0 } else { libc::WNOHANG }; - let mut status = 0; - - loop { - match unsafe { libc::waitpid(id, &mut status, wait_flags) } { - 0 => return Ok(None), - n if n < 0 => { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::Interrupted { - continue - } - return Err(err) - } - n => { - assert_eq!(n, id); - return Ok(Some(ExitStatus::from_raw(status))) - } - } + self.inner.poll() } } diff --git a/src/unix/reap.rs b/src/unix/reap.rs new file mode 100644 index 000000000..2922cc426 --- /dev/null +++ b/src/unix/reap.rs @@ -0,0 +1,313 @@ +use futures::{Async, Future, Poll, Stream}; +use std::io; +use std::ops::Deref; +use std::process::ExitStatus; + +/// An interface for waiting on a process to exit. +pub trait Wait { + /// Try waiting for a process to exit in a non-blocking manner. + fn try_wait(&mut self) -> io::Result>; +} + +/// An interface for killing a running process. +pub trait Kill { + /// Forcefully kill the process. + fn kill(&mut self) -> io::Result<()>; +} + +#[derive(Debug, PartialEq)] +enum WaitResult { + Exited(ExitStatus), + Reaped, +} + +/// An interface for safely reaping a child process. +trait Reap { + /// Try to reap the child process if ready. + fn try_reap(&mut self) -> Poll; +} + +#[derive(Debug)] +struct Reaper { + reaped: bool, + proc: W, +} + +impl Reaper { + fn new(proc: W) -> Self { + Self { + reaped: false, + proc, + } + } + + fn reaped(&self) -> bool { + self.reaped + } +} + +impl Deref for Reaper { + type Target = W; + + fn deref(&self) -> &Self::Target { + &self.proc + } +} + +impl Reap for Reaper { + fn try_reap(&mut self) -> Poll { + if self.reaped { + return Ok(Async::Ready(WaitResult::Reaped)); + } + + match self.proc.try_wait()? { + Some(exit) => { + self.reaped = true; + Ok(Async::Ready(WaitResult::Exited(exit))) + }, + None => Ok(Async::NotReady), + } + } +} + +impl Kill for Reaper { + fn kill(&mut self) -> io::Result<()> { + // NB: ensure we don't issue a kill after we've reaped the child + // since its process identifier could have been reused. + if self.reaped { + Ok(()) + } else { + self.proc.kill() + } + } +} + +/// Orchestrates between registering interest for receiving signals when a +/// child process has exited, and attempting to poll for process completion. +#[derive(Debug)] +pub struct EventedReaper { + inner: Reaper, + signal: S, +} + +impl Deref for EventedReaper { + type Target = W; + + fn deref(&self) -> &Self::Target { + &*self.inner + } +} + +impl EventedReaper { + pub fn new(inner: W, signal: S) -> Self { + Self { + inner: Reaper::new(inner), + signal, + } + } +} + +impl Future for EventedReaper + where W: Wait, + S: Stream, +{ + type Item = ExitStatus; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + loop { + // Ensure we don't register for additional notifications + // if the child has already finished. + if self.inner.reaped() { + return Ok(Async::NotReady); + } + + // If the child hasn't exited yet, then it's our responsibility to + // ensure the current task gets notified when it might be able to + // make progress. + // + // As described in `spawn` above, we just indicate that we can + // next make progress once a SIGCHLD is received. + // + // However, we will register for a notification on the next signal + // BEFORE we poll the child. Otherwise it is possible that the child + // can exit and the signal can arrive after we last polled the child, + // but before we've registered for a notification on the next signal + // (this can cause a deadlock if there are no more spawned children + // which can generate a different signal for us). A side effect of + // pre-registering for signal notifications is that when the child + // exits, we will have already registered for an additional + // notification we don't need to consume. If another signal arrives, + // this future's task will be notified/woken up again. Since the + // futures model allows for spurious wake ups this extra wakeup + // should not cause significant issues with parent futures. + let registered_interest = self.signal.poll()?.is_not_ready(); + + if let Async::Ready(WaitResult::Exited(status)) = self.inner.try_reap()? { + return Ok(Async::Ready(status)); + } + + // If our attempt to poll for the next signal was not ready, then + // we've arranged for our task to get notified and we can bail out. + if registered_interest { + return Ok(Async::NotReady); + } else { + // Otherwise, if the signal stream delivered a signal to us, we + // won't get notified at the next signal, so we'll loop and try + // again. + continue; + } + } + } +} + +impl Kill for EventedReaper + where W: Kill, +{ + fn kill(&mut self) -> io::Result<()> { + self.inner.kill() + } +} + +#[cfg(test)] +mod test { + use futures::{Async, Poll, Stream}; + use std::process::ExitStatus; + use std::os::unix::process::ExitStatusExt; + use super::*; + + struct MockWait { + total_kills: usize, + total_waits: usize, + num_wait_until_status: usize, + status: ExitStatus, + } + + impl MockWait { + fn new(status: ExitStatus, num_wait_until_status: usize) -> Self { + Self { + total_kills: 0, + total_waits: 0, + num_wait_until_status, + status + } + } + } + + impl Wait for MockWait { + fn try_wait(&mut self) -> io::Result> { + let ret = if self.num_wait_until_status == self.total_waits { + Some(self.status.clone()) + } else { + None + }; + + self.total_waits += 1; + Ok(ret) + } + } + + impl Kill for MockWait { + fn kill(&mut self) -> io::Result<()> { + self.total_kills += 1; + Ok(()) + } + } + + struct MockStream { + total_polls: usize, + values: Vec>, + } + + impl MockStream { + fn new(values: Vec>) -> Self { + Self { + total_polls: 0, + values + } + } + } + + impl Stream for MockStream { + type Item = (); + type Error = io::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.total_polls += 1; + match self.values.remove(0) { + Some(()) => Ok(Async::Ready(Some(()))), + None => Ok(Async::NotReady), + } + } + } + + #[test] + fn reaper() { + let exit = ExitStatus::from_raw(0); + let mock = MockWait::new(exit.clone(), 1); + let mut grim = Reaper::new(mock); + + // Not yet exited + assert_eq!(Async::NotReady, grim.try_reap().expect("failed to wait")); + assert_eq!(1, grim.total_waits); + + // Exited + assert_eq!(Async::Ready(WaitResult::Exited(exit)), grim.try_reap().expect("failed to wait")); + assert_eq!(2, grim.total_waits); + + // Cannot call wait another time + assert_eq!(Async::Ready(WaitResult::Reaped), grim.try_reap().expect("failed to wait")); + assert_eq!(2, grim.total_waits); + } + + #[test] + fn evented_reaper() { + let exit = ExitStatus::from_raw(0); + let mock = MockWait::new(exit.clone(), 3); + let mut grim = EventedReaper::new(mock, MockStream::new(vec!( + None, + Some(()), + None, + None, + None, + ))); + + // Not yet exited, interest registered + assert_eq!(Async::NotReady, grim.poll().expect("failed to wait")); + assert_eq!(1, grim.signal.total_polls); + assert_eq!(1, grim.total_waits); + + // Not yet exited, couldn't register interest the first time + // but managed to register interest the second time around + assert_eq!(Async::NotReady, grim.poll().expect("failed to wait")); + assert_eq!(3, grim.signal.total_polls); + assert_eq!(3, grim.total_waits); + + // Exited + assert_eq!(Async::Ready(exit), grim.poll().expect("failed to wait")); + assert_eq!(4, grim.signal.total_polls); + assert_eq!(4, grim.total_waits); + + // Already reaped, no further calls + assert_eq!(Async::NotReady, grim.poll().expect("failed to poll")); + assert_eq!(4, grim.signal.total_polls); + assert_eq!(4, grim.total_waits); + } + + #[test] + fn kill() { + let exit = ExitStatus::from_raw(0); + let mut grim = EventedReaper::new( + MockWait::new(exit, 0), + MockStream::new(vec!(None)) + ); + + grim.kill().unwrap(); + assert_eq!(1, grim.total_kills); + + // Do not kill after reaping + assert_eq!(Async::Ready(exit), grim.poll().expect("failed to poll")); + grim.kill().unwrap(); + assert_eq!(1, grim.total_kills); + } +} From 42d0f53ddbac386988a171622a174ec38d17303a Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 May 2019 15:36:47 -0700 Subject: [PATCH 085/110] process: Optimize out the "reaped" flag --- src/unix/reap.rs | 110 ++--------------------------------------------- 1 file changed, 4 insertions(+), 106 deletions(-) diff --git a/src/unix/reap.rs b/src/unix/reap.rs index 2922cc426..2e6d36e66 100644 --- a/src/unix/reap.rs +++ b/src/unix/reap.rs @@ -15,78 +15,11 @@ pub trait Kill { fn kill(&mut self) -> io::Result<()>; } -#[derive(Debug, PartialEq)] -enum WaitResult { - Exited(ExitStatus), - Reaped, -} - -/// An interface for safely reaping a child process. -trait Reap { - /// Try to reap the child process if ready. - fn try_reap(&mut self) -> Poll; -} - -#[derive(Debug)] -struct Reaper { - reaped: bool, - proc: W, -} - -impl Reaper { - fn new(proc: W) -> Self { - Self { - reaped: false, - proc, - } - } - - fn reaped(&self) -> bool { - self.reaped - } -} - -impl Deref for Reaper { - type Target = W; - - fn deref(&self) -> &Self::Target { - &self.proc - } -} - -impl Reap for Reaper { - fn try_reap(&mut self) -> Poll { - if self.reaped { - return Ok(Async::Ready(WaitResult::Reaped)); - } - - match self.proc.try_wait()? { - Some(exit) => { - self.reaped = true; - Ok(Async::Ready(WaitResult::Exited(exit))) - }, - None => Ok(Async::NotReady), - } - } -} - -impl Kill for Reaper { - fn kill(&mut self) -> io::Result<()> { - // NB: ensure we don't issue a kill after we've reaped the child - // since its process identifier could have been reused. - if self.reaped { - Ok(()) - } else { - self.proc.kill() - } - } -} - /// Orchestrates between registering interest for receiving signals when a /// child process has exited, and attempting to poll for process completion. #[derive(Debug)] pub struct EventedReaper { - inner: Reaper, + inner: W, signal: S, } @@ -94,14 +27,14 @@ impl Deref for EventedReaper { type Target = W; fn deref(&self) -> &Self::Target { - &*self.inner + &self.inner } } impl EventedReaper { pub fn new(inner: W, signal: S) -> Self { Self { - inner: Reaper::new(inner), + inner, signal, } } @@ -116,12 +49,6 @@ impl Future for EventedReaper fn poll(&mut self) -> Poll { loop { - // Ensure we don't register for additional notifications - // if the child has already finished. - if self.inner.reaped() { - return Ok(Async::NotReady); - } - // If the child hasn't exited yet, then it's our responsibility to // ensure the current task gets notified when it might be able to // make progress. @@ -143,7 +70,7 @@ impl Future for EventedReaper // should not cause significant issues with parent futures. let registered_interest = self.signal.poll()?.is_not_ready(); - if let Async::Ready(WaitResult::Exited(status)) = self.inner.try_reap()? { + if let Some(status) = self.inner.try_wait()? { return Ok(Async::Ready(status)); } @@ -241,25 +168,6 @@ mod test { } } - #[test] - fn reaper() { - let exit = ExitStatus::from_raw(0); - let mock = MockWait::new(exit.clone(), 1); - let mut grim = Reaper::new(mock); - - // Not yet exited - assert_eq!(Async::NotReady, grim.try_reap().expect("failed to wait")); - assert_eq!(1, grim.total_waits); - - // Exited - assert_eq!(Async::Ready(WaitResult::Exited(exit)), grim.try_reap().expect("failed to wait")); - assert_eq!(2, grim.total_waits); - - // Cannot call wait another time - assert_eq!(Async::Ready(WaitResult::Reaped), grim.try_reap().expect("failed to wait")); - assert_eq!(2, grim.total_waits); - } - #[test] fn evented_reaper() { let exit = ExitStatus::from_raw(0); @@ -287,11 +195,6 @@ mod test { assert_eq!(Async::Ready(exit), grim.poll().expect("failed to wait")); assert_eq!(4, grim.signal.total_polls); assert_eq!(4, grim.total_waits); - - // Already reaped, no further calls - assert_eq!(Async::NotReady, grim.poll().expect("failed to poll")); - assert_eq!(4, grim.signal.total_polls); - assert_eq!(4, grim.total_waits); } #[test] @@ -304,10 +207,5 @@ mod test { grim.kill().unwrap(); assert_eq!(1, grim.total_kills); - - // Do not kill after reaping - assert_eq!(Async::Ready(exit), grim.poll().expect("failed to poll")); - grim.kill().unwrap(); - assert_eq!(1, grim.total_kills); } } From 8a1777b8006ab2dc08d5eb8c9c76fc80a96ab55f Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 May 2019 15:39:38 -0700 Subject: [PATCH 086/110] process: Rename EventedReaper to Reaper --- src/unix/mod.rs | 6 +++--- src/unix/reap.rs | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/unix/mod.rs b/src/unix/mod.rs index 2ac0a128b..4743bf587 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -32,7 +32,7 @@ use mio::unix::{EventedFd, UnixReady}; use mio::{PollOpt, Ready, Token}; use mio::event::Evented; use mio; -use self::reap::{EventedReaper, Kill, Wait}; +use self::reap::{Kill, Reaper, Wait}; use self::tokio_signal::unix::Signal; use std::fmt; use std::io; @@ -55,7 +55,7 @@ impl Kill for process::Child { #[must_use = "futures do nothing unless polled"] pub struct Child { - inner: EventedReaper>>, + inner: Reaper>>, } impl fmt::Debug for Child { @@ -76,7 +76,7 @@ impl Child { let signal = Signal::with_handle(libc::SIGCHLD, handle).flatten_stream(); let child = Child { - inner: EventedReaper::new(inner, signal), + inner: Reaper::new(inner, signal), }; Ok((child, stdin, stdout, stderr)) diff --git a/src/unix/reap.rs b/src/unix/reap.rs index 2e6d36e66..2326c650b 100644 --- a/src/unix/reap.rs +++ b/src/unix/reap.rs @@ -18,12 +18,12 @@ pub trait Kill { /// Orchestrates between registering interest for receiving signals when a /// child process has exited, and attempting to poll for process completion. #[derive(Debug)] -pub struct EventedReaper { +pub struct Reaper { inner: W, signal: S, } -impl Deref for EventedReaper { +impl Deref for Reaper { type Target = W; fn deref(&self) -> &Self::Target { @@ -31,7 +31,7 @@ impl Deref for EventedReaper { } } -impl EventedReaper { +impl Reaper { pub fn new(inner: W, signal: S) -> Self { Self { inner, @@ -40,7 +40,7 @@ impl EventedReaper { } } -impl Future for EventedReaper +impl Future for Reaper where W: Wait, S: Stream, { @@ -88,7 +88,7 @@ impl Future for EventedReaper } } -impl Kill for EventedReaper +impl Kill for Reaper where W: Kill, { fn kill(&mut self) -> io::Result<()> { @@ -169,10 +169,10 @@ mod test { } #[test] - fn evented_reaper() { + fn reaper() { let exit = ExitStatus::from_raw(0); let mock = MockWait::new(exit.clone(), 3); - let mut grim = EventedReaper::new(mock, MockStream::new(vec!( + let mut grim = Reaper::new(mock, MockStream::new(vec!( None, Some(()), None, @@ -200,7 +200,7 @@ mod test { #[test] fn kill() { let exit = ExitStatus::from_raw(0); - let mut grim = EventedReaper::new( + let mut grim = Reaper::new( MockWait::new(exit, 0), MockStream::new(vec!(None)) ); From d0d13d0bd04527c5b82974aab9725c0513b1b221 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 May 2019 15:42:04 -0700 Subject: [PATCH 087/110] process: Change codecov comment behavior to default --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 04cde1cdc..345ce912e 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,4 +3,4 @@ ignore: - "tests" comment: - behavior: new + behavior: default From 6fa2fdab44afecba274e3b518de675a964affee8 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 May 2019 16:07:11 -0700 Subject: [PATCH 088/110] process: Ensure all tests are run with an explicit timeout --- Cargo.toml | 10 ++++++---- tests/smoke.rs | 4 ++-- tests/stdio.rs | 23 +++++++---------------- tests/support/mod.rs | 30 ++++++++++++++++++++++++++++-- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab8baac95..09014021c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ name = "tokio-process" # - Update CHANGELOG.md. # - Create "X.Y.Z" git tag. version = "0.2.3" -authors = ["Alex Crichton "] +authors = ["Alex Crichton ", "Ivan Petkov "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" homepage = "https://github.com/alexcrichton/tokio-process" @@ -27,11 +27,13 @@ tokio-io = "0.1" tokio-reactor = "0.1" [dev-dependencies] -env_logger = { version = "0.4", default-features = false } failure = "0.1" log = "0.4" -tokio = "0.1" -tokio-current-thread = "0.1" + +[dev-dependencies.tokio] +version = "0.1" +default-features = false +features = ["rt-full"] [target.'cfg(windows)'.dependencies] mio-named-pipes = "0.1" diff --git a/tests/smoke.rs b/tests/smoke.rs index e14a59429..a1dc7ec52 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -1,4 +1,3 @@ -extern crate tokio_current_thread; extern crate tokio_process; use tokio_process::CommandExt; @@ -15,7 +14,8 @@ fn simple() { let id = child.id(); assert!(id > 0); - let status = tokio_current_thread::block_on_all(&mut child).unwrap(); + let status = support::run_with_timeout(&mut child) + .expect("failed to run future"); assert_eq!(status.code(), Some(2)); assert_eq!(child.id(), id); diff --git a/tests/stdio.rs b/tests/stdio.rs index 045d127b3..1776f3ae3 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -1,21 +1,16 @@ extern crate futures; -extern crate tokio; -extern crate tokio_current_thread; -extern crate tokio_io; -extern crate tokio_process; #[macro_use] extern crate log; -extern crate env_logger; +extern crate tokio_io; +extern crate tokio_process; use std::io; use std::process::{Stdio, ExitStatus, Command}; -use std::time::Duration; use futures::future::Future; use futures::stream::{self, Stream}; use tokio_io::io::{read_until, write_all, read_to_end}; use tokio_process::{CommandExt, Child}; -use tokio::timer::Timeout; mod support; @@ -72,7 +67,6 @@ fn feed_cat(mut cat: Child, n: usize) -> Box Box Command { let mut me = env::current_exe().unwrap(); @@ -14,3 +17,26 @@ pub fn cmd(s: &str) -> Command { me.push(s); Command::new(me) } + +fn with_timeout(future: F) -> impl Future { + Timeout::new(future, Duration::from_secs(1)).map_err(|e| { + if e.is_timer() { + panic!("failed to register timer"); + } else if e.is_elapsed() { + panic!("timed out") + } else { + e.into_inner().expect("missing inner error") + } + }) +} + +pub fn run_with_timeout(future: F) -> Result +where + F: Future, +{ + // NB: Timeout requires a timer registration which is provided by + // tokio's `current_thread::Runtime`, but isn't available by just using + // tokio's default CurrentThread executor which powers `current_thread::block_on_all`. + let mut rt = current_thread::Runtime::new().expect("failed to get runtime"); + rt.block_on(with_timeout(future)) +} From 0938ccfefd461760dd4ce5b9d083bec4c41bce6d Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 May 2019 16:41:04 -0700 Subject: [PATCH 089/110] process: ci: cache cargo tarpaulin build --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d726d628c..83b8be058 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ matrix: - rust: beta - rust: nightly + cache: $HOME/.cargo/bin sudo: required addons: apt: @@ -23,12 +24,15 @@ matrix: - libssl-dev before_script: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH + - command -v cargo-install-update >/dev/null || cargo install cargo-update + - command -v cargo-tarpaulin >/dev/null || + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin + - cargo install-update --all script: - cargo test - cargo doc --no-deps --all-features after_success: - travis-cargo --only nightly doc-upload - - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin - cargo tarpaulin -v --forward --out Xml - bash <(curl -s https://codecov.io/bash) From 784d21ae317bac7634e5cc6db186667763193dee Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 May 2019 17:42:02 -0700 Subject: [PATCH 090/110] process: Try pinning mio to 0.1.16 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 09014021c..6dd830a60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ codecov = { repository = "alexcrichton/tokio-process" } [dependencies] futures = "0.1.11" -mio = "0.6.5" +mio = "=0.6.16" # TODO: investigate further and unpin this tokio-io = "0.1" tokio-reactor = "0.1" From 93680357dd83e0be8675b4f4e775d82a2004318f Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 27 May 2019 13:16:36 -0700 Subject: [PATCH 091/110] process: Fix `drop_kills` test when running on macOS with a single thread --- Cargo.toml | 2 +- tests/stdio.rs | 19 +++++++++++++++++-- tests/support/mod.rs | 9 +++++---- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6dd830a60..09014021c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ codecov = { repository = "alexcrichton/tokio-process" } [dependencies] futures = "0.1.11" -mio = "=0.6.16" # TODO: investigate further and unpin this +mio = "0.6.5" tokio-io = "0.1" tokio-reactor = "0.1" diff --git a/tests/stdio.rs b/tests/stdio.rs index 1776f3ae3..abb00d416 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -85,6 +85,18 @@ fn feed_a_lot() { assert_eq!(status.code(), Some(0)); } +// FIXME: delete this test once we have a resolution for #51 +// This test's setup is flaky, and setting up a consistent test is nearly +// impossible: right now we invoke `cat` and immediately kill it, expecting +// that it didn't write anything, but if there's something wrong with the +// command itself (e.g. redirection issues, it doesn't actually print anything +// out, etc.) this test can falsely pass. Attempting a solution which writes +// some data, *then* kill the child, write more data, and assert that only the +// first write is echoed back seems like a good approach, however, due to the +// ordering of context switches or how the kernel buffers data we can get +// inconsistent results. We can keep this test around for now, but as soon as +// we have a solution for #51, we may have a better avenue for testing this +// functionality. #[test] fn drop_kills() { let mut child = cat().spawn_async().unwrap(); @@ -96,9 +108,12 @@ fn drop_kills() { let writer = write_all(stdin, b"1234").then(|_| Ok(())); let reader = read_to_end(stdout, Vec::new()); - let future = writer.join(reader).map(|(_, (_, out))| out); + let (_, output) = support::CurrentThreadRuntime::new() + .expect("failed to get rt") + .spawn(writer) + .block_on(support::with_timeout(reader)) + .expect("failed to get output"); - let output = support::run_with_timeout(future).unwrap(); assert_eq!(output.len(), 0); } diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 38435c8cd..0e9ce6845 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -2,12 +2,13 @@ extern crate futures; extern crate tokio; use self::futures::Future; -use self::tokio::runtime::current_thread; use self::tokio::timer::Timeout; use std::env; use std::process::Command; use std::time::Duration; +pub use self::tokio::runtime::current_thread::Runtime as CurrentThreadRuntime; + pub fn cmd(s: &str) -> Command { let mut me = env::current_exe().unwrap(); me.pop(); @@ -18,8 +19,8 @@ pub fn cmd(s: &str) -> Command { Command::new(me) } -fn with_timeout(future: F) -> impl Future { - Timeout::new(future, Duration::from_secs(1)).map_err(|e| { +pub fn with_timeout(future: F) -> impl Future { + Timeout::new(future, Duration::from_secs(3)).map_err(|e| { if e.is_timer() { panic!("failed to register timer"); } else if e.is_elapsed() { @@ -37,6 +38,6 @@ where // NB: Timeout requires a timer registration which is provided by // tokio's `current_thread::Runtime`, but isn't available by just using // tokio's default CurrentThread executor which powers `current_thread::block_on_all`. - let mut rt = current_thread::Runtime::new().expect("failed to get runtime"); + let mut rt = CurrentThreadRuntime::new().expect("failed to get runtime"); rt.block_on(with_timeout(future)) } From caf43221b5a43cc676c64117238dad45a15a3012 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 29 May 2019 09:12:57 -0700 Subject: [PATCH 092/110] process: ci: fix cargo binary caching --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 83b8be058..e6166edd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,9 @@ matrix: - rust: beta - rust: nightly - cache: $HOME/.cargo/bin + cache: + directories: + - $HOME/.cargo/bin sudo: required addons: apt: From f16725ea9f275408d100ad9b5f46cdadec7f2d32 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 29 May 2019 20:30:29 -0700 Subject: [PATCH 093/110] process: Fix clippy warnings --- src/bin/cat.rs | 4 ++-- src/lib.rs | 26 ++++++++++++++++---------- src/unix/mod.rs | 32 ++++++++++++++++++-------------- src/unix/reap.rs | 4 ++-- src/windows.rs | 36 ++++++++++++++++++++---------------- tests/issue_42.rs | 2 +- tests/stdio.rs | 2 +- 7 files changed, 60 insertions(+), 46 deletions(-) diff --git a/src/bin/cat.rs b/src/bin/cat.rs index b982fceaa..7acfeb695 100644 --- a/src/bin/cat.rs +++ b/src/bin/cat.rs @@ -10,10 +10,10 @@ fn main() { loop { line.clear(); stdin.read_line(&mut line).unwrap(); - if line.len() == 0 { + if line.is_empty() { break; } - stdout.write(line.as_bytes()).unwrap(); + stdout.write_all(line.as_bytes()).unwrap(); } stdout.flush().unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index eced66ad6..26e60ec16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -319,15 +319,21 @@ pub trait CommandExt { fn output_async_with_handle(&mut self, handle: &Handle) -> OutputAsync; } +struct SpawnedChild { + child: imp::Child, + stdin: Option, + stdout: Option, + stderr: Option, +} impl CommandExt for Command { fn spawn_async_with_handle(&mut self, handle: &Handle) -> io::Result { - imp::Child::new(self.spawn()?, handle) - .map(|(child, stdin, stdout, stderr)| Child { - child, - stdin: stdin.map(|inner| ChildStdin { inner }), - stdout: stdout.map(|inner| ChildStdout { inner }), - stderr: stderr.map(|inner| ChildStderr { inner }), + imp::spawn_child(self, handle) + .map(|spawned_child| Child { + child: spawned_child.child, + stdin: spawned_child.stdin.map(|inner| ChildStdin { inner }), + stdout: spawned_child.stdout.map(|inner| ChildStdout { inner }), + stderr: spawned_child.stderr.map(|inner| ChildStderr { inner }), kill_on_drop: true, }) } @@ -353,7 +359,7 @@ impl CommandExt for Command { let inner = self.spawn_async_with_handle(handle) .into_future() - .and_then(|c| c.wait_with_output()); + .and_then(Child::wait_with_output); OutputAsync { inner: Box::new(inner), @@ -445,9 +451,9 @@ impl Child { WaitWithOutput { inner: Box::new(self.join3(stdout, stderr).map(|(status, stdout, stderr)| { Output { - status: status, - stdout: stdout, - stderr: stderr, + status, + stdout, + stderr, } })) } diff --git a/src/unix/mod.rs b/src/unix/mod.rs index 4743bf587..6ceeaaeda 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -38,6 +38,7 @@ use std::fmt; use std::io; use std::os::unix::io::{AsRawFd, RawFd}; use std::process::{self, ExitStatus}; +use super::SpawnedChild; use tokio_io::IoFuture; use tokio_reactor::{Handle, PollEvented}; @@ -66,21 +67,24 @@ impl fmt::Debug for Child { } } +pub(crate) fn spawn_child(cmd: &mut process::Command, handle: &Handle) -> io::Result { + let mut child = cmd.spawn()?; + let stdin = stdio(child.stdin.take(), handle)?; + let stdout = stdio(child.stdout.take(), handle)?; + let stderr = stdio(child.stderr.take(), handle)?; + + let signal = Signal::with_handle(libc::SIGCHLD, handle).flatten_stream(); + Ok(SpawnedChild { + child: Child { + inner: Reaper::new(child, signal), + }, + stdin, + stdout, + stderr, + }) +} + impl Child { - pub fn new(mut inner: process::Child, handle: &Handle) - -> io::Result<(Child, Option, Option, Option)> - { - let stdin = stdio(inner.stdin.take(), handle)?; - let stdout = stdio(inner.stdout.take(), handle)?; - let stderr = stdio(inner.stderr.take(), handle)?; - - let signal = Signal::with_handle(libc::SIGCHLD, handle).flatten_stream(); - let child = Child { - inner: Reaper::new(inner, signal), - }; - - Ok((child, stdin, stdout, stderr)) - } pub fn id(&self) -> u32 { self.inner.id() diff --git a/src/unix/reap.rs b/src/unix/reap.rs index 2326c650b..c17ac21ab 100644 --- a/src/unix/reap.rs +++ b/src/unix/reap.rs @@ -124,7 +124,7 @@ mod test { impl Wait for MockWait { fn try_wait(&mut self) -> io::Result> { let ret = if self.num_wait_until_status == self.total_waits { - Some(self.status.clone()) + Some(self.status) } else { None }; @@ -171,7 +171,7 @@ mod test { #[test] fn reaper() { let exit = ExitStatus::from_raw(0); - let mock = MockWait::new(exit.clone(), 3); + let mock = MockWait::new(exit, 3); let mut grim = Reaper::new(mock, MockStream::new(vec!( None, Some(()), diff --git a/src/windows.rs b/src/windows.rs index 9fa5bdfe1..a4f50f358 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -23,6 +23,7 @@ use std::io; use std::os::windows::prelude::*; use std::os::windows::process::ExitStatusExt; use std::process::{self, ExitStatus}; +use std::ptr; use futures::future::Fuse; use futures::sync::oneshot; @@ -36,6 +37,7 @@ use self::winapi::um::synchapi::*; use self::winapi::um::threadpoollegacyapiset::*; use self::winapi::um::winbase::*; use self::winapi::um::winnt::*; +use super::SpawnedChild; use tokio_reactor::{Handle, PollEvented}; #[must_use = "futures do nothing unless polled"] @@ -63,22 +65,24 @@ struct Waiting { unsafe impl Sync for Waiting {} unsafe impl Send for Waiting {} -impl Child { - pub fn new(mut inner: process::Child, handle: &Handle) - -> io::Result<(Child, Option, Option, Option)> - { - let stdin = stdio(inner.stdin.take(), handle)?; - let stdout = stdio(inner.stdout.take(), handle)?; - let stderr = stdio(inner.stderr.take(), handle)?; +pub(crate) fn spawn_child(cmd: &mut process::Command, handle: &Handle) -> io::Result { + let mut child = cmd.spawn()?; + let stdin = stdio(child.stdin.take(), handle)?; + let stdout = stdio(child.stdout.take(), handle)?; + let stderr = stdio(child.stderr.take(), handle)?; - let child = Child { - child: inner, + Ok(SpawnedChild { + child: Child { + child, waiting: None, - }; - - Ok((child, stdin, stdout, stderr)) - } + }, + stdin, + stdout, + stderr, + }) +} +impl Child { pub fn id(&self) -> u32 { self.child.id() } @@ -103,7 +107,7 @@ impl Child { } let (tx, rx) = oneshot::channel(); let ptr = Box::into_raw(Box::new(Some(tx))); - let mut wait_object = 0 as *mut _; + let mut wait_object = ptr::null_mut(); let rc = unsafe { RegisterWaitForSingleObject(&mut wait_object, self.child.as_raw_handle(), @@ -120,7 +124,7 @@ impl Child { } self.waiting = Some(Waiting { rx: rx.fuse(), - wait_object: wait_object, + wait_object, tx: ptr, }); } @@ -142,7 +146,7 @@ impl Drop for Waiting { unsafe extern "system" fn callback(ptr: PVOID, _timer_fired: BOOLEAN) { let complete = &mut *(ptr as *mut Option>); - drop(complete.take().unwrap().send(())); + let _ = complete.take().unwrap().send(()); } pub fn try_wait(child: &process::Child) -> io::Result> { diff --git a/tests/issue_42.rs b/tests/issue_42.rs index 0bc55b621..a0a9700c2 100644 --- a/tests/issue_42.rs +++ b/tests/issue_42.rs @@ -16,7 +16,7 @@ fn run_test() { let finished_clone = finished.clone(); thread::spawn(move || { - let _ = stream::iter_ok((0..2).into_iter()) + let _ = stream::iter_ok(0..2) .map(|i| Command::new("echo") .arg(format!("I am spawned process #{}", i)) .stdin(Stdio::null()) diff --git a/tests/stdio.rs b/tests/stdio.rs index abb00d416..38f48d5ec 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -36,7 +36,7 @@ fn feed_cat(mut cat: Child, n: usize) -> Box= n; debug!("starting read from child"); From 26faefcc34b477b49d8eee9134576219b62b3ee5 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 29 May 2019 20:33:29 -0700 Subject: [PATCH 094/110] process: ci: enable clippy checks as part of the build --- .travis.yml | 5 +++++ appveyor.yml | 2 ++ 2 files changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index e6166edd7..1556c5a12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,12 @@ matrix: - cargo tarpaulin -v --forward --out Xml - bash <(curl -s https://codecov.io/bash) +before_script: + - rustup component add clippy + script: + - cargo clippy --all-targets --all-features + - cargo build - cargo test env: diff --git a/appveyor.yml b/appveyor.yml index 84ade714f..51a344250 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,9 +7,11 @@ install: - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - rustc -V - cargo -V + - rustup component add clippy build: false test_script: + - cargo clippy - cargo build --target %TARGET% - cargo test --target %TARGET% From a70a3b599aeaa56d4a3ffcdd67f6df868d388181 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 29 May 2019 21:08:16 -0700 Subject: [PATCH 095/110] process: ci: move cargo tool installation to after_success --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1556c5a12..1d3369313 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,15 +26,15 @@ matrix: - libssl-dev before_script: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH - - command -v cargo-install-update >/dev/null || cargo install cargo-update - - command -v cargo-tarpaulin >/dev/null || - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin - - cargo install-update --all script: - cargo test - cargo doc --no-deps --all-features after_success: - travis-cargo --only nightly doc-upload + - command -v cargo-install-update >/dev/null || cargo install cargo-update + - command -v cargo-tarpaulin >/dev/null || + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin + - cargo install-update --all - cargo tarpaulin -v --forward --out Xml - bash <(curl -s https://codecov.io/bash) From fc15d7d4a4b4a18a5f18af17bad01e4a9471da69 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 10 Jun 2019 23:41:04 -0700 Subject: [PATCH 096/110] process: Only pull in mio dependency on unix platforms --- Cargo.toml | 2 +- src/lib.rs | 1 - src/unix/mod.rs | 15 +++++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 09014021c..a2b8997c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ codecov = { repository = "alexcrichton/tokio-process" } [dependencies] futures = "0.1.11" -mio = "0.6.5" tokio-io = "0.1" tokio-reactor = "0.1" @@ -53,4 +52,5 @@ features = [ [target.'cfg(unix)'.dependencies] libc = "0.2" +mio = "0.6.5" tokio-signal = "0.2.5" diff --git a/src/lib.rs b/src/lib.rs index 26e60ec16..ec42c7ca2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,7 +161,6 @@ extern crate futures; extern crate tokio_io; extern crate tokio_reactor; -extern crate mio; use std::io::{self, Read, Write}; use std::process::{Command, ExitStatus, Output, Stdio}; diff --git a/src/unix/mod.rs b/src/unix/mod.rs index 6ceeaaeda..6b023de3d 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -22,17 +22,16 @@ //! bad in theory... extern crate libc; +extern crate mio; extern crate tokio_signal; mod reap; use futures::future::FlattenStream; use futures::{Future, Poll}; -use mio::unix::{EventedFd, UnixReady}; -use mio::{PollOpt, Ready, Token}; -use mio::event::Evented; -use mio; -use self::reap::{Kill, Reaper, Wait}; +use self::mio::{Poll as MioPoll, PollOpt, Ready, Token}; +use self::mio::unix::{EventedFd, UnixReady}; +use self::mio::event::Evented; use self::tokio_signal::unix::Signal; use std::fmt; use std::io; @@ -130,7 +129,7 @@ pub type ChildStderr = PollEvented>; impl Evented for Fd where T: AsRawFd { fn register(&self, - poll: &mio::Poll, + poll: &MioPoll, token: Token, interest: Ready, opts: PollOpt) @@ -142,7 +141,7 @@ impl Evented for Fd where T: AsRawFd { } fn reregister(&self, - poll: &mio::Poll, + poll: &MioPoll, token: Token, interest: Ready, opts: PollOpt) @@ -153,7 +152,7 @@ impl Evented for Fd where T: AsRawFd { opts) } - fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { + fn deregister(&self, poll: &MioPoll) -> io::Result<()> { EventedFd(&self.as_raw_fd()).deregister(poll) } } From ecaa069f0f78b0e0dcac9c3dd731329c5e5ba182 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Tue, 11 Jun 2019 00:02:26 -0700 Subject: [PATCH 097/110] process: Implement a queue for repeatedly attempting to reap orphaned processes --- Cargo.toml | 3 + src/lib.rs | 7 ++ src/unix/mod.rs | 29 ++++++++ src/unix/orphan.rs | 173 +++++++++++++++++++++++++++++++++++++++++++++ src/unix/reap.rs | 17 +++-- 5 files changed, 220 insertions(+), 9 deletions(-) create mode 100644 src/unix/orphan.rs diff --git a/Cargo.toml b/Cargo.toml index a2b8997c0..3c5d131ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,9 @@ features = [ ] [target.'cfg(unix)'.dependencies] +crossbeam-queue = "0.1.2" +lazy_static = "1.3" libc = "0.2" +log = "0.4" mio = "0.6.5" tokio-signal = "0.2.5" diff --git a/src/lib.rs b/src/lib.rs index ec42c7ca2..2d0116fc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,6 +162,13 @@ extern crate futures; extern crate tokio_io; extern crate tokio_reactor; +#[cfg(unix)] +#[macro_use] +extern crate lazy_static; +#[cfg(unix)] +#[macro_use] +extern crate log; + use std::io::{self, Read, Write}; use std::process::{Command, ExitStatus, Output, Stdio}; diff --git a/src/unix/mod.rs b/src/unix/mod.rs index 6b023de3d..78c40b561 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -25,6 +25,7 @@ extern crate libc; extern crate mio; extern crate tokio_signal; +mod orphan; mod reap; use futures::future::FlattenStream; @@ -32,6 +33,8 @@ use futures::{Future, Poll}; use self::mio::{Poll as MioPoll, PollOpt, Ready, Token}; use self::mio::unix::{EventedFd, UnixReady}; use self::mio::event::Evented; +use self::orphan::{AtomicOrphanQueue, OrphanQueue, Wait}; +use self::reap::{Kill, Reaper}; use self::tokio_signal::unix::Signal; use std::fmt; use std::io; @@ -42,6 +45,10 @@ use tokio_io::IoFuture; use tokio_reactor::{Handle, PollEvented}; impl Wait for process::Child { + fn id(&self) -> u32 { + self.id() + } + fn try_wait(&mut self) -> io::Result> { self.try_wait() } @@ -53,6 +60,28 @@ impl Kill for process::Child { } } +lazy_static! { + static ref ORPHAN_QUEUE: AtomicOrphanQueue = AtomicOrphanQueue::new(); +} + +struct GlobalOrphanQueue; + +impl fmt::Debug for GlobalOrphanQueue { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + ORPHAN_QUEUE.fmt(fmt) + } +} + +impl OrphanQueue for GlobalOrphanQueue { + fn push_orphan(&self, orphan: process::Child) { + ORPHAN_QUEUE.push_orphan(orphan) + } + + fn reap_orphans(&self) { + ORPHAN_QUEUE.reap_orphans() + } +} + #[must_use = "futures do nothing unless polled"] pub struct Child { inner: Reaper>>, diff --git a/src/unix/orphan.rs b/src/unix/orphan.rs new file mode 100644 index 000000000..3641c7828 --- /dev/null +++ b/src/unix/orphan.rs @@ -0,0 +1,173 @@ +extern crate crossbeam_queue; + +use self::crossbeam_queue::SegQueue; +use std::io; +use std::process::ExitStatus; + +/// An interface for waiting on a process to exit. +pub(crate) trait Wait { + /// Get the identifier for this process or diagnostics. + fn id(&self) -> u32; + /// Try waiting for a process to exit in a non-blocking manner. + fn try_wait(&mut self) -> io::Result>; +} + +/// An interface for queueing up an orphaned process so that it can be reaped. +pub(crate) trait OrphanQueue { + /// Add an orphan to the queue. + fn push_orphan(&self, orphan: T); + /// Attempt to reap every process in the queue, ignoring any errors and + /// enqueueing any orphans which have not yet exited. + fn reap_orphans(&self); +} + +/// An atomic implementation of `OrphanQueue`. +#[derive(Debug)] +pub(crate) struct AtomicOrphanQueue { + queue: SegQueue, +} + +impl AtomicOrphanQueue { + pub(crate) fn new() -> Self { + Self { + queue: SegQueue::new(), + } + } +} + +impl OrphanQueue for AtomicOrphanQueue { + fn push_orphan(&self, orphan: T) { + self.queue.push(orphan) + } + + fn reap_orphans(&self) { + let len = self.queue.len(); + + if len == 0 { + return; + } + + let mut orphans = Vec::with_capacity(len); + while let Ok(mut orphan) = self.queue.pop() { + match orphan.try_wait() { + Ok(Some(_)) => {}, + Err(e) => error!( + "leaking orphaned process {} due to try_wait() error: {}", + orphan.id(), + e, + ), + + // Still not done yet, we need to put it back in the queue + // when were done draining it, so that we don't get stuck + // in an infinite loop here + Ok(None) => orphans.push(orphan), + } + } + + for orphan in orphans { + self.queue.push(orphan); + } + } +} + +#[cfg(test)] +mod test { + use std::cell::Cell; + use std::io; + use std::os::unix::process::ExitStatusExt; + use std::process::ExitStatus; + use std::rc::Rc; + use super::{AtomicOrphanQueue, OrphanQueue}; + use super::Wait; + + struct MockWait { + total_waits: Rc>, + num_wait_until_status: usize, + return_err: bool, + } + + impl MockWait { + fn new(num_wait_until_status: usize) -> Self { + Self { + total_waits: Rc::new(Cell::new(0)), + num_wait_until_status, + return_err: false, + } + } + + fn with_err() -> Self { + Self { + total_waits: Rc::new(Cell::new(0)), + num_wait_until_status: 0, + return_err: true, + } + } + } + + impl Wait for MockWait { + fn id(&self) -> u32 { + 42 + } + + fn try_wait(&mut self) -> io::Result> { + let waits = self.total_waits.get(); + + let ret = if self.num_wait_until_status == waits { + if self.return_err { + Ok(Some(ExitStatus::from_raw(0))) + } else { + Err(io::Error::new(io::ErrorKind::Other, "mock err")) + } + } else { + Ok(None) + }; + + self.total_waits.set(waits + 1); + ret + } + } + + #[test] + fn drain_attempts_a_single_reap_of_all_queued_orphans() { + let first_orphan = MockWait::new(0); + let second_orphan = MockWait::new(1); + let third_orphan = MockWait::new(2); + let fourth_orphan = MockWait::with_err(); + + let first_waits = first_orphan.total_waits.clone(); + let second_waits = second_orphan.total_waits.clone(); + let third_waits = third_orphan.total_waits.clone(); + let fourth_waits = fourth_orphan.total_waits.clone(); + + let orphanage = AtomicOrphanQueue::new(); + orphanage.push_orphan(first_orphan); + orphanage.push_orphan(third_orphan); + orphanage.push_orphan(second_orphan); + orphanage.push_orphan(fourth_orphan); + + assert_eq!(orphanage.queue.len(), 4); + + orphanage.reap_orphans(); + assert_eq!(orphanage.queue.len(), 2); + assert_eq!(first_waits.get(), 1); + assert_eq!(second_waits.get(), 1); + assert_eq!(third_waits.get(), 1); + assert_eq!(fourth_waits.get(), 1); + + orphanage.reap_orphans(); + assert_eq!(orphanage.queue.len(), 1); + assert_eq!(first_waits.get(), 1); + assert_eq!(second_waits.get(), 2); + assert_eq!(third_waits.get(), 2); + assert_eq!(fourth_waits.get(), 1); + + orphanage.reap_orphans(); + assert_eq!(orphanage.queue.len(), 0); + assert_eq!(first_waits.get(), 1); + assert_eq!(second_waits.get(), 2); + assert_eq!(third_waits.get(), 3); + assert_eq!(fourth_waits.get(), 1); + + orphanage.reap_orphans(); // Safe to reap when empty + } +} diff --git a/src/unix/reap.rs b/src/unix/reap.rs index c17ac21ab..831eefd5a 100644 --- a/src/unix/reap.rs +++ b/src/unix/reap.rs @@ -2,15 +2,10 @@ use futures::{Async, Future, Poll, Stream}; use std::io; use std::ops::Deref; use std::process::ExitStatus; - -/// An interface for waiting on a process to exit. -pub trait Wait { - /// Try waiting for a process to exit in a non-blocking manner. - fn try_wait(&mut self) -> io::Result>; -} +use super::orphan::Wait; /// An interface for killing a running process. -pub trait Kill { +pub(crate) trait Kill { /// Forcefully kill the process. fn kill(&mut self) -> io::Result<()>; } @@ -18,7 +13,7 @@ pub trait Kill { /// Orchestrates between registering interest for receiving signals when a /// child process has exited, and attempting to poll for process completion. #[derive(Debug)] -pub struct Reaper { +pub(crate) struct Reaper { inner: W, signal: S, } @@ -32,7 +27,7 @@ impl Deref for Reaper { } impl Reaper { - pub fn new(inner: W, signal: S) -> Self { + pub(crate) fn new(inner: W, signal: S) -> Self { Self { inner, signal, @@ -122,6 +117,10 @@ mod test { } impl Wait for MockWait { + fn id(&self) -> u32 { + 0 + } + fn try_wait(&mut self) -> io::Result> { let ret = if self.num_wait_until_status == self.total_waits { Some(self.status) From fa5da27d98bd03df94361d0a59457bdc46de2391 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 15 Jun 2019 13:34:51 -0700 Subject: [PATCH 098/110] process: Utilize a global orphan process queue to avoid leaks --- src/unix/mod.rs | 4 +- src/unix/orphan.rs | 20 ++++++ src/unix/reap.rs | 147 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 155 insertions(+), 16 deletions(-) diff --git a/src/unix/mod.rs b/src/unix/mod.rs index 78c40b561..b5eebc6fd 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -84,7 +84,7 @@ impl OrphanQueue for GlobalOrphanQueue { #[must_use = "futures do nothing unless polled"] pub struct Child { - inner: Reaper>>, + inner: Reaper>>, } impl fmt::Debug for Child { @@ -104,7 +104,7 @@ pub(crate) fn spawn_child(cmd: &mut process::Command, handle: &Handle) -> io::Re let signal = Signal::with_handle(libc::SIGCHLD, handle).flatten_stream(); Ok(SpawnedChild { child: Child { - inner: Reaper::new(child, signal), + inner: Reaper::new(child, GlobalOrphanQueue, signal), }, stdin, stdout, diff --git a/src/unix/orphan.rs b/src/unix/orphan.rs index 3641c7828..6b6a2f287 100644 --- a/src/unix/orphan.rs +++ b/src/unix/orphan.rs @@ -12,6 +12,16 @@ pub(crate) trait Wait { fn try_wait(&mut self) -> io::Result>; } +impl<'a, T: 'a + Wait> Wait for &'a mut T { + fn id(&self) -> u32 { + (**self).id() + } + + fn try_wait(&mut self) -> io::Result> { + (**self).try_wait() + } +} + /// An interface for queueing up an orphaned process so that it can be reaped. pub(crate) trait OrphanQueue { /// Add an orphan to the queue. @@ -21,6 +31,16 @@ pub(crate) trait OrphanQueue { fn reap_orphans(&self); } +impl<'a, T, O: 'a + OrphanQueue> OrphanQueue for &'a O { + fn push_orphan(&self, orphan: T) { + (**self).push_orphan(orphan); + } + + fn reap_orphans(&self) { + (**self).reap_orphans() + } +} + /// An atomic implementation of `OrphanQueue`. #[derive(Debug)] pub(crate) struct AtomicOrphanQueue { diff --git a/src/unix/reap.rs b/src/unix/reap.rs index 831eefd5a..d24829eb7 100644 --- a/src/unix/reap.rs +++ b/src/unix/reap.rs @@ -2,7 +2,7 @@ use futures::{Async, Future, Poll, Stream}; use std::io; use std::ops::Deref; use std::process::ExitStatus; -use super::orphan::Wait; +use super::orphan::{OrphanQueue, Wait}; /// An interface for killing a running process. pub(crate) trait Kill { @@ -13,30 +13,50 @@ pub(crate) trait Kill { /// Orchestrates between registering interest for receiving signals when a /// child process has exited, and attempting to poll for process completion. #[derive(Debug)] -pub(crate) struct Reaper { - inner: W, +pub(crate) struct Reaper + where W: Wait, + Q: OrphanQueue, +{ + inner: Option, + orphan_queue: Q, signal: S, } -impl Deref for Reaper { +impl Deref for Reaper + where W: Wait, + Q: OrphanQueue, +{ type Target = W; fn deref(&self) -> &Self::Target { - &self.inner + self.inner() } } -impl Reaper { - pub(crate) fn new(inner: W, signal: S) -> Self { +impl Reaper + where W: Wait, + Q: OrphanQueue, +{ + pub(crate) fn new(inner: W, orphan_queue: Q, signal: S) -> Self { Self { - inner, + inner: Some(inner), + orphan_queue, signal, } } + + fn inner(&self) -> &W { + self.inner.as_ref().expect("inner has gone away") + } + + fn inner_mut(&mut self) -> &mut W { + self.inner.as_mut().expect("inner has gone away") + } } -impl Future for Reaper +impl Future for Reaper where W: Wait, + Q: OrphanQueue, S: Stream, { type Item = ExitStatus; @@ -65,7 +85,8 @@ impl Future for Reaper // should not cause significant issues with parent futures. let registered_interest = self.signal.poll()?.is_not_ready(); - if let Some(status) = self.inner.try_wait()? { + self.orphan_queue.reap_orphans(); + if let Some(status) = self.inner_mut().try_wait()? { return Ok(Async::Ready(status)); } @@ -83,21 +104,39 @@ impl Future for Reaper } } -impl Kill for Reaper - where W: Kill, +impl Kill for Reaper + where W: Kill + Wait, + Q: OrphanQueue, { fn kill(&mut self) -> io::Result<()> { - self.inner.kill() + self.inner_mut().kill() + } +} + + +impl Drop for Reaper + where W: Wait, + Q: OrphanQueue, +{ + fn drop(&mut self) { + if let Ok(Some(_)) = self.inner_mut().try_wait() { + return; + } + + let orphan = self.inner.take().unwrap(); + self.orphan_queue.push_orphan(orphan); } } #[cfg(test)] mod test { use futures::{Async, Poll, Stream}; + use std::cell::{Cell, RefCell}; use std::process::ExitStatus; use std::os::unix::process::ExitStatusExt; use super::*; + #[derive(Debug)] struct MockWait { total_kills: usize, total_waits: usize, @@ -167,11 +206,36 @@ mod test { } } + struct MockQueue { + all_enqueued: RefCell>, + total_reaps: Cell, + } + + impl MockQueue { + fn new() -> Self { + Self { + all_enqueued: RefCell::new(Vec::new()), + total_reaps: Cell::new(0), + } + } + } + + impl OrphanQueue for MockQueue { + fn push_orphan(&self, orphan: W) { + self.all_enqueued.borrow_mut() + .push(orphan); + } + + fn reap_orphans(&self) { + self.total_reaps.set(self.total_reaps.get() + 1); + } + } + #[test] fn reaper() { let exit = ExitStatus::from_raw(0); let mock = MockWait::new(exit, 3); - let mut grim = Reaper::new(mock, MockStream::new(vec!( + let mut grim = Reaper::new(mock, MockQueue::new(), MockStream::new(vec!( None, Some(()), None, @@ -183,17 +247,23 @@ mod test { assert_eq!(Async::NotReady, grim.poll().expect("failed to wait")); assert_eq!(1, grim.signal.total_polls); assert_eq!(1, grim.total_waits); + assert_eq!(1, grim.orphan_queue.total_reaps.get()); + assert!(grim.orphan_queue.all_enqueued.borrow().is_empty()); // Not yet exited, couldn't register interest the first time // but managed to register interest the second time around assert_eq!(Async::NotReady, grim.poll().expect("failed to wait")); assert_eq!(3, grim.signal.total_polls); assert_eq!(3, grim.total_waits); + assert_eq!(3, grim.orphan_queue.total_reaps.get()); + assert!(grim.orphan_queue.all_enqueued.borrow().is_empty()); // Exited assert_eq!(Async::Ready(exit), grim.poll().expect("failed to wait")); assert_eq!(4, grim.signal.total_polls); assert_eq!(4, grim.total_waits); + assert_eq!(4, grim.orphan_queue.total_reaps.get()); + assert!(grim.orphan_queue.all_enqueued.borrow().is_empty()); } #[test] @@ -201,10 +271,59 @@ mod test { let exit = ExitStatus::from_raw(0); let mut grim = Reaper::new( MockWait::new(exit, 0), + MockQueue::new(), MockStream::new(vec!(None)) ); grim.kill().unwrap(); assert_eq!(1, grim.total_kills); + assert_eq!(0, grim.orphan_queue.total_reaps.get()); + assert!(grim.orphan_queue.all_enqueued.borrow().is_empty()); + } + + #[test] + fn drop_reaps_if_possible() { + let exit = ExitStatus::from_raw(0); + let mut mock = MockWait::new(exit, 0); + + { + let queue = MockQueue::new(); + + let grim = Reaper::new( + &mut mock, + &queue, + MockStream::new(vec!()) + ); + + drop(grim); + + assert_eq!(0, queue.total_reaps.get()); + assert!(queue.all_enqueued.borrow().is_empty()); + } + + assert_eq!(1, mock.total_waits); + assert_eq!(0, mock.total_kills); + } + + #[test] + fn drop_enqueues_orphan_if_wait_fails() { + let exit = ExitStatus::from_raw(0); + let mut mock = MockWait::new(exit, 2); + + { + let queue = MockQueue::<&mut MockWait>::new(); + let grim = Reaper::new( + &mut mock, + &queue, + MockStream::new(vec!()) + ); + drop(grim); + + assert_eq!(0, queue.total_reaps.get()); + assert_eq!(1, queue.all_enqueued.borrow().len()); + } + + assert_eq!(1, mock.total_waits); + assert_eq!(0, mock.total_kills); } } From e90e33d5df6e59013ceffc6e956c32ba232c6049 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 15 Jun 2019 14:22:39 -0700 Subject: [PATCH 099/110] process: Add unit tests for dropping killing dropped children --- src/kill.rs | 13 ++++ src/lib.rs | 176 ++++++++++++++++++++++++++++++++++++++++++----- src/unix/mod.rs | 15 ++-- src/unix/reap.rs | 7 +- src/windows.rs | 14 +++- 5 files changed, 196 insertions(+), 29 deletions(-) create mode 100644 src/kill.rs diff --git a/src/kill.rs b/src/kill.rs new file mode 100644 index 000000000..25d7d9a5d --- /dev/null +++ b/src/kill.rs @@ -0,0 +1,13 @@ +use std::io; + +/// An interface for killing a running process. +pub(crate) trait Kill { + /// Forcefully kill the process. + fn kill(&mut self) -> io::Result<()>; +} + +impl<'a, T: 'a + Kill> Kill for &'a mut T { + fn kill(&mut self) -> io::Result<()> { + (**self).kill() + } +} diff --git a/src/lib.rs b/src/lib.rs index 2d0116fc4..462cec031 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -172,8 +172,9 @@ extern crate log; use std::io::{self, Read, Write}; use std::process::{Command, ExitStatus, Output, Stdio}; -use futures::{Future, Poll, IntoFuture}; +use futures::{Async, Future, Poll, IntoFuture}; use futures::future::{Either, ok}; +use kill::Kill; use std::fmt; use tokio_io::io::{read_to_end}; use tokio_io::{AsyncWrite, AsyncRead, IoFuture}; @@ -187,6 +188,8 @@ mod imp; #[cfg(windows)] mod imp; +mod kill; + /// Extensions provided by this crate to the `Command` type in the standard /// library. /// @@ -336,11 +339,10 @@ impl CommandExt for Command { fn spawn_async_with_handle(&mut self, handle: &Handle) -> io::Result { imp::spawn_child(self, handle) .map(|spawned_child| Child { - child: spawned_child.child, + child: ChildDropGuard::new(spawned_child.child), stdin: spawned_child.stdin.map(|inner| ChildStdin { inner }), stdout: spawned_child.stdout.map(|inner| ChildStdout { inner }), stderr: spawned_child.stderr.map(|inner| ChildStderr { inner }), - kill_on_drop: true, }) } @@ -373,6 +375,58 @@ impl CommandExt for Command { } } +/// A drop guard which ensures the child process is killed on drop to maintain +/// the contract of dropping a Future leads to "cancellation". +#[derive(Debug)] +struct ChildDropGuard { + inner: T, + kill_on_drop: bool, +} + +impl ChildDropGuard { + fn new(inner: T) -> Self { + Self { + inner, + kill_on_drop: true, + } + } + + fn forget(&mut self) { + self.kill_on_drop = false; + } +} + +impl Kill for ChildDropGuard { + fn kill(&mut self) -> io::Result<()> { + self.inner.kill() + } +} + +impl Drop for ChildDropGuard { + fn drop(&mut self) { + if self.kill_on_drop { + drop(self.kill()); + } + } +} + + +impl Future for ChildDropGuard { + type Item = T::Item; + type Error = T::Error; + + fn poll(&mut self) -> Poll { + let ret = self.inner.poll(); + + if let Ok(Async::Ready(_)) = ret { + // Avoid the overhead of trying to kill a reaped process + self.kill_on_drop = false; + } + + ret + } +} + /// Representation of a child process spawned onto an event loop. /// /// This type is also a future which will yield the `ExitStatus` of the @@ -389,8 +443,7 @@ impl CommandExt for Command { #[must_use = "futures do nothing unless polled"] #[derive(Debug)] pub struct Child { - child: imp::Child, - kill_on_drop: bool, + child: ChildDropGuard, stdin: Option, stdout: Option, stderr: Option, @@ -399,7 +452,7 @@ pub struct Child { impl Child { /// Returns the OS-assigned process identifier associated with this child. pub fn id(&self) -> u32 { - self.child.id() + self.child.inner.id() } /// Forces the child to exit. @@ -497,7 +550,7 @@ impl Child { /// # } /// ``` pub fn forget(mut self) { - self.kill_on_drop = false; + self.child.forget(); } } @@ -506,15 +559,7 @@ impl Future for Child { type Error = io::Error; fn poll(&mut self) -> Poll { - self.child.poll_exit() - } -} - -impl Drop for Child { - fn drop(&mut self) { - if self.kill_on_drop { - drop(self.kill()); - } + self.child.poll() } } @@ -708,3 +753,102 @@ mod sys { } } } + +#[cfg(test)] +mod test { + use futures::{Async, Future, Poll}; + use kill::Kill; + use std::io; + use super::ChildDropGuard; + + struct Mock { + num_kills: usize, + num_polls: usize, + poll_result: Poll<(), ()>, + } + + impl Mock { + fn new() -> Self { + Self::with_result(Ok(Async::NotReady)) + } + + fn with_result(result: Poll<(), ()>) -> Self { + Self { + num_kills: 0, + num_polls: 0, + poll_result: result, + } + } + } + + impl Kill for Mock { + fn kill(&mut self) -> io::Result<()> { + self.num_kills += 1; + Ok(()) + } + } + + impl Future for Mock { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + self.num_polls += 1; + self.poll_result + } + } + + #[test] + fn kills_on_drop() { + let mut mock = Mock::new(); + + { + let guard = ChildDropGuard::new(&mut mock); + drop(guard); + } + + assert_eq!(1, mock.num_kills); + assert_eq!(0, mock.num_polls); + } + + #[test] + fn no_kill_if_reaped() { + let mut mock_pending = Mock::with_result(Ok(Async::NotReady)); + let mut mock_reaped = Mock::with_result(Ok(Async::Ready(()))); + let mut mock_err = Mock::with_result(Err(())); + + { + let mut guard = ChildDropGuard::new(&mut mock_pending); + let _ = guard.poll(); + + let mut guard = ChildDropGuard::new(&mut mock_reaped); + let _ = guard.poll(); + + let mut guard = ChildDropGuard::new(&mut mock_err); + let _ = guard.poll(); + } + + assert_eq!(1, mock_pending.num_kills); + assert_eq!(1, mock_pending.num_polls); + + assert_eq!(0, mock_reaped.num_kills); + assert_eq!(1, mock_reaped.num_polls); + + assert_eq!(1, mock_err.num_kills); + assert_eq!(1, mock_err.num_polls); + } + + #[test] + fn no_kill_on_forget() { + let mut mock = Mock::new(); + + { + let mut guard = ChildDropGuard::new(&mut mock); + guard.forget(); + drop(guard); + } + + assert_eq!(0, mock.num_kills); + assert_eq!(0, mock.num_polls); + } +} diff --git a/src/unix/mod.rs b/src/unix/mod.rs index b5eebc6fd..465117d3c 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -30,11 +30,12 @@ mod reap; use futures::future::FlattenStream; use futures::{Future, Poll}; +use kill::Kill; use self::mio::{Poll as MioPoll, PollOpt, Ready, Token}; use self::mio::unix::{EventedFd, UnixReady}; use self::mio::event::Evented; use self::orphan::{AtomicOrphanQueue, OrphanQueue, Wait}; -use self::reap::{Kill, Reaper}; +use self::reap::Reaper; use self::tokio_signal::unix::Signal; use std::fmt; use std::io; @@ -113,16 +114,22 @@ pub(crate) fn spawn_child(cmd: &mut process::Command, handle: &Handle) -> io::Re } impl Child { - pub fn id(&self) -> u32 { self.inner.id() } +} - pub fn kill(&mut self) -> io::Result<()> { +impl Kill for Child { + fn kill(&mut self) -> io::Result<()> { self.inner.kill() } +} - pub fn poll_exit(&mut self) -> Poll { +impl Future for Child { + type Item = ExitStatus; + type Error = io::Error; + + fn poll(&mut self) -> Poll { self.inner.poll() } } diff --git a/src/unix/reap.rs b/src/unix/reap.rs index d24829eb7..76e995c1b 100644 --- a/src/unix/reap.rs +++ b/src/unix/reap.rs @@ -1,15 +1,10 @@ use futures::{Async, Future, Poll, Stream}; +use kill::Kill; use std::io; use std::ops::Deref; use std::process::ExitStatus; use super::orphan::{OrphanQueue, Wait}; -/// An interface for killing a running process. -pub(crate) trait Kill { - /// Forcefully kill the process. - fn kill(&mut self) -> io::Result<()>; -} - /// Orchestrates between registering interest for receiving signals when a /// child process has exited, and attempting to poll for process completion. #[derive(Debug)] diff --git a/src/windows.rs b/src/windows.rs index a4f50f358..253f35476 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -27,7 +27,8 @@ use std::ptr; use futures::future::Fuse; use futures::sync::oneshot; -use futures::{Future, Poll, Async} ; +use futures::{Future, Poll, Async}; +use kill::Kill; use self::mio_named_pipes::NamedPipe; use self::winapi::shared::minwindef::*; use self::winapi::shared::winerror::*; @@ -86,12 +87,19 @@ impl Child { pub fn id(&self) -> u32 { self.child.id() } +} - pub fn kill(&mut self) -> io::Result<()> { +impl Kill for Child { + fn kill(&mut self) -> io::Result<()> { self.child.kill() } +} - pub fn poll_exit(&mut self) -> Poll { +impl Future for Child { + type Item = ExitStatus; + type Error = io::Error; + + fn poll(&mut self) -> Poll { loop { if let Some(ref mut w) = self.waiting { match w.rx.poll().expect("should not be canceled") { From cf84a59e5a4ef196659b3df0966832ea67868c4e Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 16 Jun 2019 10:35:40 -0700 Subject: [PATCH 100/110] process: Don't kill child on drop if already successfully killed --- src/lib.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 462cec031..7464b69d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -398,7 +398,13 @@ impl ChildDropGuard { impl Kill for ChildDropGuard { fn kill(&mut self) -> io::Result<()> { - self.inner.kill() + let ret = self.inner.kill(); + + if ret.is_ok() { + self.kill_on_drop = false; + } + + ret } } @@ -811,6 +817,20 @@ mod test { assert_eq!(0, mock.num_polls); } + #[test] + fn no_kill_if_already_killed() { + let mut mock = Mock::new(); + + { + let mut guard = ChildDropGuard::new(&mut mock); + let _ = guard.kill(); + drop(guard); + } + + assert_eq!(1, mock.num_kills); + assert_eq!(0, mock.num_polls); + } + #[test] fn no_kill_if_reaped() { let mut mock_pending = Mock::with_result(Ok(Async::NotReady)); From a6b2682309b9379edb83e5e7ab675cea2aab3a11 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Fri, 21 Jun 2019 20:01:43 -0700 Subject: [PATCH 101/110] process: Bump to 0.2.4 --- CHANGELOG.md | 15 +++++++++++++-- Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c16b601f..a1e58defd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.2.4] - 2019-06-21 +### Fixed +* Proccesses "leaked" via `Child::forget` now reaped rather than left as zombies +for the duration of the parent process. +* Dropping a `Child` process no longer blocks the caller until the process fully +exits. This avoids a pathological deadlock if the kernel doesn't kill the child. -## [0.2.2] - 2018-11-01 +### Changed +* Updated the example program for reading lines from a child process to be more +flexible to be copy/pasted and iterated upon. + +## [0.2.3] - 2018-11-01 ### Added * `ChildStd{in, out, err}` now implement `AsRawFd`/`AsRawHandle` on Unix/Windows systems, respectively. @@ -67,7 +77,8 @@ the locally vendored `Command` type. ## 0.1.0 - 2016-09-10 - First release! -[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.3...HEAD +[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.4...HEAD +[0.2.4]: https://github.com/alexcrichton/tokio-process/compare/0.2.3...0.2.4 [0.2.3]: https://github.com/alexcrichton/tokio-process/compare/0.2.2...0.2.3 [0.2.2]: https://github.com/alexcrichton/tokio-process/compare/0.2.1...0.2.2 [0.2.1]: https://github.com/alexcrichton/tokio-process/compare/0.1.6...0.2.1 diff --git a/Cargo.toml b/Cargo.toml index 3c5d131ec..3fbbaa902 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "tokio-process" # - Update html_root_url. # - Update CHANGELOG.md. # - Create "X.Y.Z" git tag. -version = "0.2.3" +version = "0.2.4" authors = ["Alex Crichton ", "Ivan Petkov "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-process" From 061452dc018ef54a7657f14ecd5f0d16b2fc52de Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 24 Jun 2019 16:59:21 -0700 Subject: [PATCH 102/110] process: Delete flaky and (now) unused test --- tests/stdio.rs | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/tests/stdio.rs b/tests/stdio.rs index 38f48d5ec..0f3a77b34 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -9,7 +9,7 @@ use std::process::{Stdio, ExitStatus, Command}; use futures::future::Future; use futures::stream::{self, Stream}; -use tokio_io::io::{read_until, write_all, read_to_end}; +use tokio_io::io::{read_until, write_all}; use tokio_process::{CommandExt, Child}; mod support; @@ -85,38 +85,6 @@ fn feed_a_lot() { assert_eq!(status.code(), Some(0)); } -// FIXME: delete this test once we have a resolution for #51 -// This test's setup is flaky, and setting up a consistent test is nearly -// impossible: right now we invoke `cat` and immediately kill it, expecting -// that it didn't write anything, but if there's something wrong with the -// command itself (e.g. redirection issues, it doesn't actually print anything -// out, etc.) this test can falsely pass. Attempting a solution which writes -// some data, *then* kill the child, write more data, and assert that only the -// first write is echoed back seems like a good approach, however, due to the -// ordering of context switches or how the kernel buffers data we can get -// inconsistent results. We can keep this test around for now, but as soon as -// we have a solution for #51, we may have a better avenue for testing this -// functionality. -#[test] -fn drop_kills() { - let mut child = cat().spawn_async().unwrap(); - let stdin = child.stdin().take().unwrap(); - let stdout = child.stdout().take().unwrap(); - drop(child); - - // Ignore all write errors since we expect a broken pipe here - let writer = write_all(stdin, b"1234").then(|_| Ok(())); - let reader = read_to_end(stdout, Vec::new()); - - let (_, output) = support::CurrentThreadRuntime::new() - .expect("failed to get rt") - .spawn(writer) - .block_on(support::with_timeout(reader)) - .expect("failed to get output"); - - assert_eq!(output.len(), 0); -} - #[test] fn wait_with_output_captures() { let mut child = cat().spawn_async().unwrap(); From ff5381de8d566ac988b7e7c2d9c7e099b5b22f7d Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 24 Jun 2019 17:03:51 -0700 Subject: [PATCH 103/110] process: Update license files --- LICENSE-MIT => LICENSE | 2 +- LICENSE-APACHE | 201 ----------------------------------------- 2 files changed, 1 insertion(+), 202 deletions(-) rename LICENSE-MIT => LICENSE (96%) delete mode 100644 LICENSE-APACHE diff --git a/LICENSE-MIT b/LICENSE similarity index 96% rename from LICENSE-MIT rename to LICENSE index 28e630cf4..cdb28b4b5 100644 --- a/LICENSE-MIT +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016 Alex Crichton +Copyright (c) 2019 Tokio Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/LICENSE-APACHE b/LICENSE-APACHE deleted file mode 100644 index 16fe87b06..000000000 --- a/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. From 4d639e246bf2c41fb526fe0935a2b7bbd5afe824 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 24 Jun 2019 17:08:24 -0700 Subject: [PATCH 104/110] process: Update Cargo.toml --- Cargo.toml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3fbbaa902..16cd3f71d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,21 +5,16 @@ name = "tokio-process" # - Update CHANGELOG.md. # - Create "X.Y.Z" git tag. version = "0.2.4" -authors = ["Alex Crichton ", "Ivan Petkov "] -license = "MIT/Apache-2.0" -repository = "https://github.com/alexcrichton/tokio-process" -homepage = "https://github.com/alexcrichton/tokio-process" -documentation = "https://docs.rs/tokio-process" +authors = ["Tokio Contributors "] +license = "MIT" +repository = "https://github.com/tokio-rs/tokio" +homepage = "https://github.com/tokio-rs/tokio" +documentation = "https://docs.rs/tokio-process/0.2.4/tokio_process" description = """ An implementation of an asynchronous process management backed futures. """ categories = ["asynchronous"] -[badges] -travis-ci = { repository = "alexcrichton/tokio-process" } -appveyor = { repository = "alexcrichton/tokio-process" } -codecov = { repository = "alexcrichton/tokio-process" } - [dependencies] futures = "0.1.11" tokio-io = "0.1" From 934a1467d4efcc3f841ff259732676f38a9e2c72 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 24 Jun 2019 17:09:41 -0700 Subject: [PATCH 105/110] process: Update CHANGELOG --- CHANGELOG.md | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1e58defd..61bb06dfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,4 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] -## [0.2.4] - 2019-06-21 +## 0.2.4 - 2019-06-21 ### Fixed * Proccesses "leaked" via `Child::forget` now reaped rather than left as zombies for the duration of the parent process. @@ -16,17 +9,17 @@ exits. This avoids a pathological deadlock if the kernel doesn't kill the child. * Updated the example program for reading lines from a child process to be more flexible to be copy/pasted and iterated upon. -## [0.2.3] - 2018-11-01 +## 0.2.3 - 2018-11-01 ### Added * `ChildStd{in, out, err}` now implement `AsRawFd`/`AsRawHandle` on Unix/Windows systems, respectively. -## [0.2.2] - 2018-05-27 +## 0.2.2 - 2018-05-27 ### Fixed - Fixed a pathological situation where a signal could be missed if it arrived after polling the child but before registering for a new notification -## [0.2.1] - 2018-05-18 +## 0.2.1 - 2018-05-18 ### Changed - **Breaking**: asynchronous spawning of a child process now requires using a reactor handle from the `tokio` crate instead of the `tokio-core` crate @@ -35,17 +28,17 @@ reactor handle from the `tokio` crate instead of the `tokio-core` crate ### Removed - **Breaking**: removed all previously deprecated items -## [0.1.6] - 2018-05-09 +## 0.1.6 - 2018-05-09 ### Fixed - On Unix systems, any child processes that are `kill`ed (or implicitly killed via dropping the child without calling `forget`) are no longer left in a zombie state, which allows the OS to reclaim the process. -## [0.1.5] - 2018-01-03 +## 0.1.5 - 2018-01-03 ### Changed - Minimum required version of `winapi` has been bumped to `0.3`. -## [0.1.4] - 2017-06-25 +## 0.1.4 - 2017-06-25 ### Fixed - Added missing `Debug` impls on all types. - Added missing `must_use` annotations on all futures. @@ -53,20 +46,20 @@ state, which allows the OS to reclaim the process. to prevent potential deadlocks when attempting to interact with any pipes held by the parent process. -## [0.1.3] - 2017-03-15 +## 0.1.3 - 2017-03-15 ### Changed - Minimum required version of `futures` has been bumped to `0.1.11`. - Minimum required version of `mio` has been bumped to `0.6.5`. - Minimum required version of `tokio-core` has been bumped to `0.1.6`. -## [0.1.2] - 2017-01-24 +## 0.1.2 - 2017-01-24 ### Changed - Minimum required version of `tokio-signal` has been bumped to `0.1.2`. ### Fixed - The event loop which spawns the first async child no longer needs to be kept alive for subsequent child spawns to make progress. -## [0.1.1] - 2016-12-19 +## 0.1.1 - 2016-12-19 ### Added - Support performing async I/O operations on the child's stdio handles. ### Changed @@ -76,15 +69,3 @@ the locally vendored `Command` type. ## 0.1.0 - 2016-09-10 - First release! - -[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.4...HEAD -[0.2.4]: https://github.com/alexcrichton/tokio-process/compare/0.2.3...0.2.4 -[0.2.3]: https://github.com/alexcrichton/tokio-process/compare/0.2.2...0.2.3 -[0.2.2]: https://github.com/alexcrichton/tokio-process/compare/0.2.1...0.2.2 -[0.2.1]: https://github.com/alexcrichton/tokio-process/compare/0.1.6...0.2.1 -[0.1.6]: https://github.com/alexcrichton/tokio-process/compare/0.1.5...0.1.6 -[0.1.5]: https://github.com/alexcrichton/tokio-process/compare/0.1.4...0.1.5 -[0.1.4]: https://github.com/alexcrichton/tokio-process/compare/0.1.3...0.1.4 -[0.1.3]: https://github.com/alexcrichton/tokio-process/compare/0.1.2...0.1.3 -[0.1.2]: https://github.com/alexcrichton/tokio-process/compare/0.1.1...0.1.2 -[0.1.1]: https://github.com/alexcrichton/tokio-process/compare/0.1.0...0.1.1 From 0ab25878bd93ece3644403e3d123f988c02cb675 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 24 Jun 2019 17:16:52 -0700 Subject: [PATCH 106/110] process: Update README --- README.md | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 8755b1823..9a252d9de 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,7 @@ An implementation of process management for Tokio -[![Build Status](https://travis-ci.com/alexcrichton/tokio-process.svg?branch=master)](https://travis-ci.com/alexcrichton/tokio-process) -[![Build status](https://ci.appveyor.com/api/projects/status/43c8g7fy801e5902?svg=true)](https://ci.appveyor.com/project/alexcrichton/tokio-process) -[![Crates.io](https://img.shields.io/crates/v/tokio-process.svg?maxAge=2592000)](https://crates.io/crates/tokio-process) -[![Coverage](https://img.shields.io/codecov/c/github/alexcrichton/tokio-process/master.svg)](https://codecov.io/gh/alexcrichton/tokio-process) - -[Documentation](https://docs.rs/tokio-process) +[Documentation](https://docs.rs/tokio-process/0.2.4/tokio_process) ## Usage @@ -18,26 +13,36 @@ First, add this to your `Cargo.toml`: tokio-process = "0.2" ``` -Next, add this to your crate: +Next you can use this in conjunction with the `tokio` and `futures` crates: -```rust -extern crate tokio_process; +```rust,no_run +use std::process::Command; + +use futures::Future; +use tokio_process::CommandExt; + +fn main() { + // Use the standard library's `Command` type to build a process and + // then execute it via the `CommandExt` trait. + let child = Command::new("echo").arg("hello").arg("world").spawn_async(); + + // Make sure our child succeeded in spawning and process the result + let future = child + .expect("failed to spawn") + .map(|status| println!("exit status: {}", status)) + .map_err(|e| panic!("failed to wait for exit: {}", e)); + + // Send the future to the tokio runtime for execution + tokio::run(future) +} ``` +## License -# License - -This project is licensed under either of - - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or - http://opensource.org/licenses/MIT) - -at your option. +This project is licensed under the [MIT license](./LICENSE). ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in tokio-process by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. +for inclusion in Tokio by you, shall be licensed as MIT, without any additional +terms or conditions. From 27c15471c1329f287d47e3a79ccc7a3cd185315b Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 24 Jun 2019 17:19:16 -0700 Subject: [PATCH 107/110] process: Run cargo fmt --- src/lib.rs | 56 +++++++++++++++++------------------- src/unix/mod.rs | 68 ++++++++++++++++++++++--------------------- src/unix/orphan.rs | 6 ++-- src/unix/reap.rs | 72 +++++++++++++++++++++------------------------- src/windows.rs | 40 +++++++++++++------------- tests/issue_42.rs | 28 ++++++++++-------- tests/smoke.rs | 3 +- tests/stdio.rs | 33 ++++++++++----------- 8 files changed, 151 insertions(+), 155 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7464b69d7..5af9f68a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -172,12 +172,12 @@ extern crate log; use std::io::{self, Read, Write}; use std::process::{Command, ExitStatus, Output, Stdio}; -use futures::{Async, Future, Poll, IntoFuture}; -use futures::future::{Either, ok}; +use futures::future::{ok, Either}; +use futures::{Async, Future, IntoFuture, Poll}; use kill::Kill; use std::fmt; -use tokio_io::io::{read_to_end}; -use tokio_io::{AsyncWrite, AsyncRead, IoFuture}; +use tokio_io::io::read_to_end; +use tokio_io::{AsyncRead, AsyncWrite, IoFuture}; use tokio_reactor::Handle; #[path = "unix/mod.rs"] @@ -337,13 +337,12 @@ struct SpawnedChild { impl CommandExt for Command { fn spawn_async_with_handle(&mut self, handle: &Handle) -> io::Result { - imp::spawn_child(self, handle) - .map(|spawned_child| Child { - child: ChildDropGuard::new(spawned_child.child), - stdin: spawned_child.stdin.map(|inner| ChildStdin { inner }), - stdout: spawned_child.stdout.map(|inner| ChildStdout { inner }), - stderr: spawned_child.stderr.map(|inner| ChildStderr { inner }), - }) + imp::spawn_child(self, handle).map(|spawned_child| Child { + child: ChildDropGuard::new(spawned_child.child), + stdin: spawned_child.stdin.map(|inner| ChildStdin { inner }), + stdout: spawned_child.stdout.map(|inner| ChildStdout { inner }), + stderr: spawned_child.stderr.map(|inner| ChildStderr { inner }), + }) } fn status_async_with_handle(&mut self, handle: &Handle) -> io::Result { @@ -355,9 +354,7 @@ impl CommandExt for Command { child.stdout.take(); child.stderr.take(); - StatusAsync { - inner: child, - } + StatusAsync { inner: child } }) } @@ -365,7 +362,8 @@ impl CommandExt for Command { self.stdout(Stdio::piped()); self.stderr(Stdio::piped()); - let inner = self.spawn_async_with_handle(handle) + let inner = self + .spawn_async_with_handle(handle) .into_future() .and_then(Child::wait_with_output); @@ -416,7 +414,6 @@ impl Drop for ChildDropGuard { } } - impl Future for ChildDropGuard { type Item = T::Item; type Error = T::Error; @@ -514,13 +511,14 @@ impl Child { }; WaitWithOutput { - inner: Box::new(self.join3(stdout, stderr).map(|(status, stdout, stderr)| { - Output { - status, - stdout, - stderr, - } - })) + inner: Box::new( + self.join3(stdout, stderr) + .map(|(status, stdout, stderr)| Output { + status, + stdout, + stderr, + }), + ), } } @@ -700,8 +698,7 @@ impl Read for ChildStdout { } } -impl AsyncRead for ChildStdout { -} +impl AsyncRead for ChildStdout {} impl Read for ChildStderr { fn read(&mut self, bytes: &mut [u8]) -> io::Result { @@ -709,13 +706,12 @@ impl Read for ChildStderr { } } -impl AsyncRead for ChildStderr { -} +impl AsyncRead for ChildStderr {} #[cfg(unix)] mod sys { + use super::{ChildStderr, ChildStdin, ChildStdout}; use std::os::unix::io::{AsRawFd, RawFd}; - use super::{ChildStdin, ChildStdout, ChildStderr}; impl AsRawFd for ChildStdin { fn as_raw_fd(&self) -> RawFd { @@ -738,8 +734,8 @@ mod sys { #[cfg(windows)] mod sys { + use super::{ChildStderr, ChildStdin, ChildStdout}; use std::os::windows::io::{AsRawHandle, RawHandle}; - use super::{ChildStdin, ChildStdout, ChildStderr}; impl AsRawHandle for ChildStdin { fn as_raw_handle(&self) -> RawHandle { @@ -762,10 +758,10 @@ mod sys { #[cfg(test)] mod test { + use super::ChildDropGuard; use futures::{Async, Future, Poll}; use kill::Kill; use std::io; - use super::ChildDropGuard; struct Mock { num_kills: usize, diff --git a/src/unix/mod.rs b/src/unix/mod.rs index 465117d3c..ad6f97d76 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -28,20 +28,20 @@ extern crate tokio_signal; mod orphan; mod reap; -use futures::future::FlattenStream; -use futures::{Future, Poll}; -use kill::Kill; -use self::mio::{Poll as MioPoll, PollOpt, Ready, Token}; -use self::mio::unix::{EventedFd, UnixReady}; use self::mio::event::Evented; +use self::mio::unix::{EventedFd, UnixReady}; +use self::mio::{Poll as MioPoll, PollOpt, Ready, Token}; use self::orphan::{AtomicOrphanQueue, OrphanQueue, Wait}; use self::reap::Reaper; use self::tokio_signal::unix::Signal; +use super::SpawnedChild; +use futures::future::FlattenStream; +use futures::{Future, Poll}; +use kill::Kill; use std::fmt; use std::io; use std::os::unix::io::{AsRawFd, RawFd}; use std::process::{self, ExitStatus}; -use super::SpawnedChild; use tokio_io::IoFuture; use tokio_reactor::{Handle, PollEvented}; @@ -153,7 +153,10 @@ impl io::Write for Fd { } } -impl AsRawFd for Fd where T: AsRawFd { +impl AsRawFd for Fd +where + T: AsRawFd, +{ fn as_raw_fd(&self) -> RawFd { self.0.as_raw_fd() } @@ -163,29 +166,28 @@ pub type ChildStdin = PollEvented>; pub type ChildStdout = PollEvented>; pub type ChildStderr = PollEvented>; -impl Evented for Fd where T: AsRawFd { - fn register(&self, - poll: &MioPoll, - token: Token, - interest: Ready, - opts: PollOpt) - -> io::Result<()> { - EventedFd(&self.as_raw_fd()).register(poll, - token, - interest | UnixReady::hup(), - opts) +impl Evented for Fd +where + T: AsRawFd, +{ + fn register( + &self, + poll: &MioPoll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + EventedFd(&self.as_raw_fd()).register(poll, token, interest | UnixReady::hup(), opts) } - fn reregister(&self, - poll: &MioPoll, - token: Token, - interest: Ready, - opts: PollOpt) - -> io::Result<()> { - EventedFd(&self.as_raw_fd()).reregister(poll, - token, - interest | UnixReady::hup(), - opts) + fn reregister( + &self, + poll: &MioPoll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + EventedFd(&self.as_raw_fd()).reregister(poll, token, interest | UnixReady::hup(), opts) } fn deregister(&self, poll: &MioPoll) -> io::Result<()> { @@ -193,9 +195,9 @@ impl Evented for Fd where T: AsRawFd { } } -fn stdio(option: Option, handle: &Handle) - -> io::Result>>> - where T: AsRawFd +fn stdio(option: Option, handle: &Handle) -> io::Result>>> +where + T: AsRawFd, { let io = match option { Some(io) => io, @@ -207,11 +209,11 @@ fn stdio(option: Option, handle: &Handle) let fd = io.as_raw_fd(); let r = libc::fcntl(fd, libc::F_GETFL); if r == -1 { - return Err(io::Error::last_os_error()) + return Err(io::Error::last_os_error()); } let r = libc::fcntl(fd, libc::F_SETFL, r | libc::O_NONBLOCK); if r == -1 { - return Err(io::Error::last_os_error()) + return Err(io::Error::last_os_error()); } } let io = try!(PollEvented::new_with_handle(Fd(io), handle)); diff --git a/src/unix/orphan.rs b/src/unix/orphan.rs index 6b6a2f287..6f456ce0e 100644 --- a/src/unix/orphan.rs +++ b/src/unix/orphan.rs @@ -70,7 +70,7 @@ impl OrphanQueue for AtomicOrphanQueue { let mut orphans = Vec::with_capacity(len); while let Ok(mut orphan) = self.queue.pop() { match orphan.try_wait() { - Ok(Some(_)) => {}, + Ok(Some(_)) => {} Err(e) => error!( "leaking orphaned process {} due to try_wait() error: {}", orphan.id(), @@ -92,13 +92,13 @@ impl OrphanQueue for AtomicOrphanQueue { #[cfg(test)] mod test { + use super::Wait; + use super::{AtomicOrphanQueue, OrphanQueue}; use std::cell::Cell; use std::io; use std::os::unix::process::ExitStatusExt; use std::process::ExitStatus; use std::rc::Rc; - use super::{AtomicOrphanQueue, OrphanQueue}; - use super::Wait; struct MockWait { total_waits: Rc>, diff --git a/src/unix/reap.rs b/src/unix/reap.rs index 76e995c1b..567319b36 100644 --- a/src/unix/reap.rs +++ b/src/unix/reap.rs @@ -1,16 +1,17 @@ +use super::orphan::{OrphanQueue, Wait}; use futures::{Async, Future, Poll, Stream}; use kill::Kill; use std::io; use std::ops::Deref; use std::process::ExitStatus; -use super::orphan::{OrphanQueue, Wait}; /// Orchestrates between registering interest for receiving signals when a /// child process has exited, and attempting to poll for process completion. #[derive(Debug)] pub(crate) struct Reaper - where W: Wait, - Q: OrphanQueue, +where + W: Wait, + Q: OrphanQueue, { inner: Option, orphan_queue: Q, @@ -18,8 +19,9 @@ pub(crate) struct Reaper } impl Deref for Reaper - where W: Wait, - Q: OrphanQueue, +where + W: Wait, + Q: OrphanQueue, { type Target = W; @@ -29,8 +31,9 @@ impl Deref for Reaper } impl Reaper - where W: Wait, - Q: OrphanQueue, +where + W: Wait, + Q: OrphanQueue, { pub(crate) fn new(inner: W, orphan_queue: Q, signal: S) -> Self { Self { @@ -50,9 +53,10 @@ impl Reaper } impl Future for Reaper - where W: Wait, - Q: OrphanQueue, - S: Stream, +where + W: Wait, + Q: OrphanQueue, + S: Stream, { type Item = ExitStatus; type Error = io::Error; @@ -100,18 +104,19 @@ impl Future for Reaper } impl Kill for Reaper - where W: Kill + Wait, - Q: OrphanQueue, +where + W: Kill + Wait, + Q: OrphanQueue, { fn kill(&mut self) -> io::Result<()> { self.inner_mut().kill() } } - impl Drop for Reaper - where W: Wait, - Q: OrphanQueue, +where + W: Wait, + Q: OrphanQueue, { fn drop(&mut self) { if let Ok(Some(_)) = self.inner_mut().try_wait() { @@ -125,11 +130,11 @@ impl Drop for Reaper #[cfg(test)] mod test { + use super::*; use futures::{Async, Poll, Stream}; use std::cell::{Cell, RefCell}; - use std::process::ExitStatus; use std::os::unix::process::ExitStatusExt; - use super::*; + use std::process::ExitStatus; #[derive(Debug)] struct MockWait { @@ -145,7 +150,7 @@ mod test { total_kills: 0, total_waits: 0, num_wait_until_status, - status + status, } } } @@ -183,7 +188,7 @@ mod test { fn new(values: Vec>) -> Self { Self { total_polls: 0, - values + values, } } } @@ -217,8 +222,7 @@ mod test { impl OrphanQueue for MockQueue { fn push_orphan(&self, orphan: W) { - self.all_enqueued.borrow_mut() - .push(orphan); + self.all_enqueued.borrow_mut().push(orphan); } fn reap_orphans(&self) { @@ -230,13 +234,11 @@ mod test { fn reaper() { let exit = ExitStatus::from_raw(0); let mock = MockWait::new(exit, 3); - let mut grim = Reaper::new(mock, MockQueue::new(), MockStream::new(vec!( - None, - Some(()), - None, - None, - None, - ))); + let mut grim = Reaper::new( + mock, + MockQueue::new(), + MockStream::new(vec![None, Some(()), None, None, None]), + ); // Not yet exited, interest registered assert_eq!(Async::NotReady, grim.poll().expect("failed to wait")); @@ -267,7 +269,7 @@ mod test { let mut grim = Reaper::new( MockWait::new(exit, 0), MockQueue::new(), - MockStream::new(vec!(None)) + MockStream::new(vec![None]), ); grim.kill().unwrap(); @@ -284,11 +286,7 @@ mod test { { let queue = MockQueue::new(); - let grim = Reaper::new( - &mut mock, - &queue, - MockStream::new(vec!()) - ); + let grim = Reaper::new(&mut mock, &queue, MockStream::new(vec![])); drop(grim); @@ -307,11 +305,7 @@ mod test { { let queue = MockQueue::<&mut MockWait>::new(); - let grim = Reaper::new( - &mut mock, - &queue, - MockStream::new(vec!()) - ); + let grim = Reaper::new(&mut mock, &queue, MockStream::new(vec![])); drop(grim); assert_eq!(0, queue.total_reaps.get()); diff --git a/src/windows.rs b/src/windows.rs index 253f35476..a368d510b 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -15,8 +15,8 @@ //! `RegisterWaitForSingleObject` and then wait on the other end of the oneshot //! from then on out. -extern crate winapi; extern crate mio_named_pipes; +extern crate winapi; use std::fmt; use std::io; @@ -25,10 +25,6 @@ use std::os::windows::process::ExitStatusExt; use std::process::{self, ExitStatus}; use std::ptr; -use futures::future::Fuse; -use futures::sync::oneshot; -use futures::{Future, Poll, Async}; -use kill::Kill; use self::mio_named_pipes::NamedPipe; use self::winapi::shared::minwindef::*; use self::winapi::shared::winerror::*; @@ -39,6 +35,10 @@ use self::winapi::um::threadpoollegacyapiset::*; use self::winapi::um::winbase::*; use self::winapi::um::winnt::*; use super::SpawnedChild; +use futures::future::Fuse; +use futures::sync::oneshot; +use futures::{Async, Future, Poll}; +use kill::Kill; use tokio_reactor::{Handle, PollEvented}; #[must_use = "futures do nothing unless polled"] @@ -107,28 +107,29 @@ impl Future for Child { Async::NotReady => return Ok(Async::NotReady), } let status = try!(try_wait(&self.child)).expect("not ready yet"); - return Ok(status.into()) + return Ok(status.into()); } if let Some(e) = try!(try_wait(&self.child)) { - return Ok(e.into()) + return Ok(e.into()); } let (tx, rx) = oneshot::channel(); let ptr = Box::into_raw(Box::new(Some(tx))); let mut wait_object = ptr::null_mut(); let rc = unsafe { - RegisterWaitForSingleObject(&mut wait_object, - self.child.as_raw_handle(), - Some(callback), - ptr as *mut _, - INFINITE, - WT_EXECUTEINWAITTHREAD | - WT_EXECUTEONLYONCE) + RegisterWaitForSingleObject( + &mut wait_object, + self.child.as_raw_handle(), + Some(callback), + ptr as *mut _, + INFINITE, + WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE, + ) }; if rc == 0 { let err = io::Error::last_os_error(); drop(unsafe { Box::from_raw(ptr) }); - return Err(err) + return Err(err); } self.waiting = Some(Waiting { rx: rx.fuse(), @@ -151,8 +152,7 @@ impl Drop for Waiting { } } -unsafe extern "system" fn callback(ptr: PVOID, - _timer_fired: BOOLEAN) { +unsafe extern "system" fn callback(ptr: PVOID, _timer_fired: BOOLEAN) { let complete = &mut *(ptr as *mut Option>); let _ = complete.take().unwrap().send(()); } @@ -178,9 +178,9 @@ pub type ChildStdin = PollEvented; pub type ChildStdout = PollEvented; pub type ChildStderr = PollEvented; -fn stdio(option: Option, handle: &Handle) - -> io::Result>> - where T: IntoRawHandle, +fn stdio(option: Option, handle: &Handle) -> io::Result>> +where + T: IntoRawHandle, { let io = match option { Some(io) => io, diff --git a/tests/issue_42.rs b/tests/issue_42.rs index a0a9700c2..0b34efcf1 100644 --- a/tests/issue_42.rs +++ b/tests/issue_42.rs @@ -3,10 +3,10 @@ extern crate futures; extern crate tokio_process; -use futures::{Future, IntoFuture, Stream, stream}; +use futures::{stream, Future, IntoFuture, Stream}; use std::process::{Command, Stdio}; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::thread; use std::time::Duration; use tokio_process::CommandExt; @@ -17,15 +17,16 @@ fn run_test() { thread::spawn(move || { let _ = stream::iter_ok(0..2) - .map(|i| Command::new("echo") - .arg(format!("I am spawned process #{}", i)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn_async() - .into_future() - .flatten() - ) + .map(|i| { + Command::new("echo") + .arg(format!("I am spawned process #{}", i)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn_async() + .into_future() + .flatten() + }) .buffered(2) .collect() .wait(); @@ -34,7 +35,10 @@ fn run_test() { }); thread::sleep(Duration::from_millis(100)); - assert!(finished.load(Ordering::SeqCst), "FINISHED flag not set, maybe we deadlocked?"); + assert!( + finished.load(Ordering::SeqCst), + "FINISHED flag not set, maybe we deadlocked?" + ); } #[test] diff --git a/tests/smoke.rs b/tests/smoke.rs index a1dc7ec52..9d11002ad 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -14,8 +14,7 @@ fn simple() { let id = child.id(); assert!(id > 0); - let status = support::run_with_timeout(&mut child) - .expect("failed to run future"); + let status = support::run_with_timeout(&mut child).expect("failed to run future"); assert_eq!(status.code(), Some(2)); assert_eq!(child.id(), id); diff --git a/tests/stdio.rs b/tests/stdio.rs index 0f3a77b34..647350f72 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -5,19 +5,18 @@ extern crate tokio_io; extern crate tokio_process; use std::io; -use std::process::{Stdio, ExitStatus, Command}; +use std::process::{Command, ExitStatus, Stdio}; use futures::future::Future; use futures::stream::{self, Stream}; use tokio_io::io::{read_until, write_all}; -use tokio_process::{CommandExt, Child}; +use tokio_process::{Child, CommandExt}; mod support; fn cat() -> Command { let mut cmd = support::cmd("cat"); - cmd.stdin(Stdio::piped()) - .stdout(Stdio::piped()); + cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); cmd } @@ -28,10 +27,12 @@ fn feed_cat(mut cat: Child, n: usize) -> Box Box= n; debug!("starting read from child"); read_until(reader, b'\n', Vec::new()).and_then(move |(reader, vec)| { - debug!("read line {} from child ({} bytes, done: {})", - i, vec.len(), done); + debug!( + "read line {} from child ({} bytes, done: {})", + i, + vec.len(), + done + ); match (done, vec.len()) { - (false, 0) => { - Err(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe")) - }, - (true, n) if n != 0 => { - Err(io::Error::new(io::ErrorKind::Other, "extraneous data")) - }, + (false, 0) => Err(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe")), + (true, n) if n != 0 => Err(io::Error::new(io::ErrorKind::Other, "extraneous data")), _ => { let s = std::str::from_utf8(&vec).unwrap(); let expected = format!("line {}\n", i); From cb8607a816c2040f9e1b7f6546a96963e5ac8c76 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 24 Jun 2019 17:29:04 -0700 Subject: [PATCH 108/110] process: Update to 2018 edition --- Cargo.toml | 1 + src/lib.rs | 4 ++-- src/unix/mod.rs | 4 ++-- src/unix/reap.rs | 2 +- src/windows.rs | 6 +++--- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 16cd3f71d..2c3112b84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ name = "tokio-process" # - Update CHANGELOG.md. # - Create "X.Y.Z" git tag. version = "0.2.4" +edition = "2018" authors = ["Tokio Contributors "] license = "MIT" repository = "https://github.com/tokio-rs/tokio" diff --git a/src/lib.rs b/src/lib.rs index 5af9f68a2..e9e963689 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -172,9 +172,9 @@ extern crate log; use std::io::{self, Read, Write}; use std::process::{Command, ExitStatus, Output, Stdio}; +use crate::kill::Kill; use futures::future::{ok, Either}; use futures::{Async, Future, IntoFuture, Poll}; -use kill::Kill; use std::fmt; use tokio_io::io::read_to_end; use tokio_io::{AsyncRead, AsyncWrite, IoFuture}; @@ -759,8 +759,8 @@ mod sys { #[cfg(test)] mod test { use super::ChildDropGuard; + use crate::kill::Kill; use futures::{Async, Future, Poll}; - use kill::Kill; use std::io; struct Mock { diff --git a/src/unix/mod.rs b/src/unix/mod.rs index ad6f97d76..adf0f4ba2 100644 --- a/src/unix/mod.rs +++ b/src/unix/mod.rs @@ -35,9 +35,9 @@ use self::orphan::{AtomicOrphanQueue, OrphanQueue, Wait}; use self::reap::Reaper; use self::tokio_signal::unix::Signal; use super::SpawnedChild; +use crate::kill::Kill; use futures::future::FlattenStream; use futures::{Future, Poll}; -use kill::Kill; use std::fmt; use std::io; use std::os::unix::io::{AsRawFd, RawFd}; @@ -216,6 +216,6 @@ where return Err(io::Error::last_os_error()); } } - let io = try!(PollEvented::new_with_handle(Fd(io), handle)); + let io = PollEvented::new_with_handle(Fd(io), handle)?; Ok(Some(io)) } diff --git a/src/unix/reap.rs b/src/unix/reap.rs index 567319b36..125aaf30f 100644 --- a/src/unix/reap.rs +++ b/src/unix/reap.rs @@ -1,6 +1,6 @@ use super::orphan::{OrphanQueue, Wait}; +use crate::kill::Kill; use futures::{Async, Future, Poll, Stream}; -use kill::Kill; use std::io; use std::ops::Deref; use std::process::ExitStatus; diff --git a/src/windows.rs b/src/windows.rs index a368d510b..0d43f29fa 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -106,11 +106,11 @@ impl Future for Child { Async::Ready(()) => {} Async::NotReady => return Ok(Async::NotReady), } - let status = try!(try_wait(&self.child)).expect("not ready yet"); + let status = try_wait(&self.child)?.expect("not ready yet"); return Ok(status.into()); } - if let Some(e) = try!(try_wait(&self.child)) { + if let Some(e) = try_wait(&self.child)? { return Ok(e.into()); } let (tx, rx) = oneshot::channel(); @@ -187,6 +187,6 @@ where None => return Ok(None), }; let pipe = unsafe { NamedPipe::from_raw_handle(io.into_raw_handle()) }; - let io = try!(PollEvented::new_with_handle(pipe, handle)); + let io = PollEvented::new_with_handle(pipe, handle)?; Ok(Some(io)) } From b7846a4e2f41cfe38889aae4a3ffdf313913d482 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 24 Jun 2019 17:31:00 -0700 Subject: [PATCH 109/110] process: Remove unneeded files --- .gitignore | 2 -- .travis.yml | 55 ---------------------------------------------------- appveyor.yml | 17 ---------------- codecov.yml | 6 ------ 4 files changed, 80 deletions(-) delete mode 100644 .gitignore delete mode 100644 .travis.yml delete mode 100644 appveyor.yml delete mode 100644 codecov.yml diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a9d37c560..000000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target -Cargo.lock diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1d3369313..000000000 --- a/.travis.yml +++ /dev/null @@ -1,55 +0,0 @@ -language: rust -sudo: false - -matrix: - include: - # This represents the minimum Rust version supported by - # Tokio. Updating this should be done in a dedicated PR and - # cannot be greater than two 0.x releases prior to the - # current stable. - # - # Tests are not run as tests may require newer versions of - # rust. - - rust: 1.33.0 - - rust: stable - - os: osx - - rust: beta - - - rust: nightly - cache: - directories: - - $HOME/.cargo/bin - sudo: required - addons: - apt: - packages: - - libssl-dev - before_script: - - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH - script: - - cargo test - - cargo doc --no-deps --all-features - after_success: - - travis-cargo --only nightly doc-upload - - command -v cargo-install-update >/dev/null || cargo install cargo-update - - command -v cargo-tarpaulin >/dev/null || - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin - - cargo install-update --all - - cargo tarpaulin -v --forward --out Xml - - bash <(curl -s https://codecov.io/bash) - -before_script: - - rustup component add clippy - -script: - - cargo clippy --all-targets --all-features - - cargo build - - cargo test - -env: - global: - secure: "mTrrxm6AHgbh+k6/GKhKwQoKmLF4tZQLZ7671jvJ42Yu3U6mH4xGWnQDEQ6E883SvoBi0W5KwsvRqKRqNpPXYbWIMsS46gpMu0jEL6uz7+zwip64847OdbXAbS8NZsnXhS0w5b9dYdQUCoj71TrbWGVS/sqNb2twn+GJGIqfsjUHRnkHLIMmwoILgzYMbd3d1Jy/KlicIGtHq8Sb23EVr7tdkN++k21ZSDbmD+q5Pmf9MZH3yyk2YIpgCooVzqYAtS8Ua6ug1L+u3MBDWtqUFEOxGP5ya1+s312TGsBShaVvtQrH2IFOG+izdVjvRkpeM/FJXlqQAh02VxHK8ST9B6zjYO7Mnn8yT4gAA5PfoMlwZ5UxYQmr5fcjPAWvUkIdb6qRdqEupBbtCGO4EWTi2Gw8/w8tZGMS3JqPSsl8QJCu+fj3FWRwRwKmUaFIgIojbJQ3NCRCu9RGdbOoOOlhjYbw51lN9kxGxHLiiZzEtxv+nay5vNqIVkprRz4gbXOAi27Fglz2rYie88vb7XgrAVnaoahBA7C6l7KXZ/W3EuDDGjlxChj0kAqKz9AbbKAkYGN02hOn2Y26ag/THd4LKNE7QfcaEBKfJx3YEKVBI0fmoyT+iT1RMr9vmEySJmgZXHgd6TTIIpvIN8ouIy6LxzQby0bgP1++NSH05jgf/Sk=" - -notifications: - email: - on_success: never diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 51a344250..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,17 +0,0 @@ -environment: - matrix: - - TARGET: x86_64-pc-windows-msvc -install: - - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - rustup-init.exe -y --default-host %TARGET% --default-toolchain beta - - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - - rustc -V - - cargo -V - - rustup component add clippy - -build: false - -test_script: - - cargo clippy - - cargo build --target %TARGET% - - cargo test --target %TARGET% diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 345ce912e..000000000 --- a/codecov.yml +++ /dev/null @@ -1,6 +0,0 @@ -ignore: - - "src/bin" - - "tests" - -comment: - behavior: default From c6defbce4bbb9dbd9474cc7b5cb8c9624734f545 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Mon, 24 Jun 2019 17:31:47 -0700 Subject: [PATCH 110/110] process: Move files to their own directory --- CHANGELOG.md => tokio-process/CHANGELOG.md | 0 Cargo.toml => tokio-process/Cargo.toml | 0 LICENSE => tokio-process/LICENSE | 0 README.md => tokio-process/README.md | 0 {src => tokio-process/src}/bin/cat.rs | 0 {src => tokio-process/src}/bin/exit.rs | 0 {src => tokio-process/src}/kill.rs | 0 {src => tokio-process/src}/lib.rs | 0 {src => tokio-process/src}/unix/mod.rs | 0 {src => tokio-process/src}/unix/orphan.rs | 0 {src => tokio-process/src}/unix/reap.rs | 0 {src => tokio-process/src}/windows.rs | 0 {tests => tokio-process/tests}/issue_42.rs | 0 {tests => tokio-process/tests}/smoke.rs | 0 {tests => tokio-process/tests}/stdio.rs | 0 {tests => tokio-process/tests}/support/mod.rs | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename CHANGELOG.md => tokio-process/CHANGELOG.md (100%) rename Cargo.toml => tokio-process/Cargo.toml (100%) rename LICENSE => tokio-process/LICENSE (100%) rename README.md => tokio-process/README.md (100%) rename {src => tokio-process/src}/bin/cat.rs (100%) rename {src => tokio-process/src}/bin/exit.rs (100%) rename {src => tokio-process/src}/kill.rs (100%) rename {src => tokio-process/src}/lib.rs (100%) rename {src => tokio-process/src}/unix/mod.rs (100%) rename {src => tokio-process/src}/unix/orphan.rs (100%) rename {src => tokio-process/src}/unix/reap.rs (100%) rename {src => tokio-process/src}/windows.rs (100%) rename {tests => tokio-process/tests}/issue_42.rs (100%) rename {tests => tokio-process/tests}/smoke.rs (100%) rename {tests => tokio-process/tests}/stdio.rs (100%) rename {tests => tokio-process/tests}/support/mod.rs (100%) diff --git a/CHANGELOG.md b/tokio-process/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to tokio-process/CHANGELOG.md diff --git a/Cargo.toml b/tokio-process/Cargo.toml similarity index 100% rename from Cargo.toml rename to tokio-process/Cargo.toml diff --git a/LICENSE b/tokio-process/LICENSE similarity index 100% rename from LICENSE rename to tokio-process/LICENSE diff --git a/README.md b/tokio-process/README.md similarity index 100% rename from README.md rename to tokio-process/README.md diff --git a/src/bin/cat.rs b/tokio-process/src/bin/cat.rs similarity index 100% rename from src/bin/cat.rs rename to tokio-process/src/bin/cat.rs diff --git a/src/bin/exit.rs b/tokio-process/src/bin/exit.rs similarity index 100% rename from src/bin/exit.rs rename to tokio-process/src/bin/exit.rs diff --git a/src/kill.rs b/tokio-process/src/kill.rs similarity index 100% rename from src/kill.rs rename to tokio-process/src/kill.rs diff --git a/src/lib.rs b/tokio-process/src/lib.rs similarity index 100% rename from src/lib.rs rename to tokio-process/src/lib.rs diff --git a/src/unix/mod.rs b/tokio-process/src/unix/mod.rs similarity index 100% rename from src/unix/mod.rs rename to tokio-process/src/unix/mod.rs diff --git a/src/unix/orphan.rs b/tokio-process/src/unix/orphan.rs similarity index 100% rename from src/unix/orphan.rs rename to tokio-process/src/unix/orphan.rs diff --git a/src/unix/reap.rs b/tokio-process/src/unix/reap.rs similarity index 100% rename from src/unix/reap.rs rename to tokio-process/src/unix/reap.rs diff --git a/src/windows.rs b/tokio-process/src/windows.rs similarity index 100% rename from src/windows.rs rename to tokio-process/src/windows.rs diff --git a/tests/issue_42.rs b/tokio-process/tests/issue_42.rs similarity index 100% rename from tests/issue_42.rs rename to tokio-process/tests/issue_42.rs diff --git a/tests/smoke.rs b/tokio-process/tests/smoke.rs similarity index 100% rename from tests/smoke.rs rename to tokio-process/tests/smoke.rs diff --git a/tests/stdio.rs b/tokio-process/tests/stdio.rs similarity index 100% rename from tests/stdio.rs rename to tokio-process/tests/stdio.rs diff --git a/tests/support/mod.rs b/tokio-process/tests/support/mod.rs similarity index 100% rename from tests/support/mod.rs rename to tokio-process/tests/support/mod.rs