mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-07 00:00:08 +02:00
process: Rewrite the crate with an extension trait
This commit is contained in:
+2
-6
@@ -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" }
|
||||
|
||||
+343
-149
@@ -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<Child>;
|
||||
|
||||
/// 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<u8>` 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<Future<Item=Child, Error=io::Error>>,
|
||||
|
||||
impl CommandExt for Command {
|
||||
fn spawn_async(&mut self, handle: &Handle) -> io::Result<Child> {
|
||||
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<ChildStdin>,
|
||||
stdout: Option<ChildStdout>,
|
||||
stderr: Option<ChildStderr>,
|
||||
}
|
||||
|
||||
pub struct ChildStdin {
|
||||
inner: imp::ChildStdin,
|
||||
}
|
||||
|
||||
pub struct ChildStdout {
|
||||
inner: imp::ChildStdout,
|
||||
}
|
||||
|
||||
pub struct ChildStderr {
|
||||
inner: imp::ChildStderr,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn new<T: AsRef<OsStr>>(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<S: AsRef<OsStr>>(&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<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Command {
|
||||
for arg in args {
|
||||
self._arg(arg.as_ref());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Command
|
||||
where K: AsRef<OsStr>, V: AsRef<OsStr>
|
||||
{
|
||||
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<K: AsRef<OsStr>>(&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<P: AsRef<Path>>(&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<Child, io::Error> {
|
||||
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<ChildStdin> {
|
||||
&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<ChildStdout> {
|
||||
&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<ChildStderr> {
|
||||
&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<ExitStatus, io::Error> {
|
||||
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<Output>,
|
||||
}
|
||||
|
||||
impl Future for WaitWithOutput {
|
||||
type Item = Output;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Output, io::Error> {
|
||||
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<FutureResult<Child, io::Error>>,
|
||||
}
|
||||
|
||||
impl Future for StatusAsync {
|
||||
type Item = ExitStatus;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<ExitStatus, io::Error> {
|
||||
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<Output>,
|
||||
}
|
||||
|
||||
impl Future for OutputAsync {
|
||||
type Item = Output;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Output, io::Error> {
|
||||
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<usize> {
|
||||
self.inner.write(bytes)
|
||||
|
||||
+75
-92
@@ -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<Future<Item=::Child, Error=io::Error>> {
|
||||
struct KillOnDrop(Option<process::Child>);
|
||||
|
||||
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<IoFuture<Signal>>,
|
||||
}
|
||||
|
||||
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<Option<ChildStdin>> {
|
||||
stdio(self.inner.stdin.take(), handle)
|
||||
}
|
||||
|
||||
pub fn register_stdout(&mut self, handle: &Handle)
|
||||
-> io::Result<Option<ChildStdout>> {
|
||||
stdio(self.inner.stdout.take(), handle)
|
||||
}
|
||||
|
||||
pub fn register_stderr(&mut self, handle: &Handle)
|
||||
-> io::Result<Option<ChildStderr>> {
|
||||
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<ExitStatus, io::Error> {
|
||||
pub fn poll_exit(&mut self) -> Poll<ExitStatus, io::Error> {
|
||||
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<Option<ExitStatus>> {
|
||||
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<Option<ExitStatus>> {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-39
@@ -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<Future<Item=::Child, Error=io::Error>> {
|
||||
struct KillOnDrop(Option<process::Child>);
|
||||
|
||||
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<Option<ChildStdin>> {
|
||||
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<Option<ChildStdout>> {
|
||||
stdio(self.child.stdout.take(), handle)
|
||||
}
|
||||
|
||||
pub fn register_stderr(&mut self, handle: &Handle)
|
||||
-> io::Result<Option<ChildStderr>> {
|
||||
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<ExitStatus, io::Error> {
|
||||
pub fn poll_exit(&mut self) -> Poll<ExitStatus, io::Error> {
|
||||
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(),
|
||||
|
||||
+9
-8
@@ -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();
|
||||
|
||||
+25
-12
@@ -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<ExitStatus, io::Error> {
|
||||
/// 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user