mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-28 00:00:11 +02:00
process: Bugfix: ensure status_async closes child's stdio handles after spawning
This commit is contained in:
+14
-4
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-1
@@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user