Skip to content

Commit 7aab3d3

Browse files
committed
Auto merge of #38866 - alexcrichton:try-wait, r=aturon
std: Add a nonblocking `Child::try_wait` method This commit adds a new method to the `Child` type in the `std::process` module called `try_wait`. This method is the same as `wait` except that it will not block the calling thread and instead only attempt to collect the exit status. On Unix this means that we call `waitpid` with the `WNOHANG` flag and on Windows it just means that we pass a 0 timeout to `WaitForSingleObject`. Currently it's possible to build this method out of tree, but it's unfortunately tricky to do so. Specifically on Unix you essentially lose ownership of the pid for the process once a call to `waitpid` has succeeded. Although `Child` tracks this state internally to be resilient to multiple calls to `wait` or a `kill` after a successful wait, if the child is waited on externally then the state inside of `Child` is not updated. This means that external implementations of this method must be extra careful to essentially not use a `Child`'s methods after a call to `waitpid` has succeeded (even in a nonblocking fashion). By adding this functionality to the standard library it should help canonicalize these external implementations and ensure they can continue to robustly reuse the `Child` type from the standard library without worrying about pid ownership.
2 parents f5cfe83 + abb9189 commit 7aab3d3

File tree

5 files changed

+140
-0
lines changed

5 files changed

+140
-0
lines changed

src/libstd/process.rs

+42
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,48 @@ impl Child {
800800
self.handle.wait().map(ExitStatus)
801801
}
802802

803+
/// Attempts to collect the exit status of the child if it has already
804+
/// exited.
805+
///
806+
/// This function will not block the calling thread and will only advisorily
807+
/// check to see if the child process has exited or not. If the child has
808+
/// exited then on Unix the process id is reaped. This function is
809+
/// guaranteed to repeatedly return a successful exit status so long as the
810+
/// child has already exited.
811+
///
812+
/// If the child has exited, then `Ok(status)` is returned. If the exit
813+
/// status is not available at this time then an error is returned with the
814+
/// error kind `WouldBlock`. If an error occurs, then that error is returned.
815+
///
816+
/// Note that unlike `wait`, this function will not attempt to drop stdin.
817+
///
818+
/// # Examples
819+
///
820+
/// Basic usage:
821+
///
822+
/// ```no_run
823+
/// #![feature(process_try_wait)]
824+
///
825+
/// use std::io;
826+
/// use std::process::Command;
827+
///
828+
/// let mut child = Command::new("ls").spawn().unwrap();
829+
///
830+
/// match child.try_wait() {
831+
/// Ok(status) => println!("exited with: {}", status),
832+
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
833+
/// println!("status not ready yet, let's really wait");
834+
/// let res = child.wait();
835+
/// println!("result: {:?}", res);
836+
/// }
837+
/// Err(e) => println!("error attempting to wait: {}", e),
838+
/// }
839+
/// ```
840+
#[unstable(feature = "process_try_wait", issue = "38903")]
841+
pub fn try_wait(&mut self) -> io::Result<ExitStatus> {
842+
self.handle.try_wait().map(ExitStatus)
843+
}
844+
803845
/// Simultaneously waits for the child to exit and collect all remaining
804846
/// output on the stdout/stderr handles, returning an `Output`
805847
/// instance.

src/libstd/sys/unix/process/process_unix.rs

+17
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ impl Process {
237237
cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(|_| ())
238238
}
239239
}
240+
240241
pub fn wait(&mut self) -> io::Result<ExitStatus> {
241242
use sys::cvt_r;
242243
if let Some(status) = self.status {
@@ -247,4 +248,20 @@ impl Process {
247248
self.status = Some(ExitStatus::new(status));
248249
Ok(ExitStatus::new(status))
249250
}
251+
252+
pub fn try_wait(&mut self) -> io::Result<ExitStatus> {
253+
if let Some(status) = self.status {
254+
return Ok(status)
255+
}
256+
let mut status = 0 as c_int;
257+
let pid = cvt(unsafe {
258+
libc::waitpid(self.pid, &mut status, libc::WNOHANG)
259+
})?;
260+
if pid == 0 {
261+
Err(io::Error::from_raw_os_error(libc::EWOULDBLOCK))
262+
} else {
263+
self.status = Some(ExitStatus::new(status));
264+
Ok(ExitStatus::new(status))
265+
}
266+
}
250267
}

src/libstd/sys/windows/c.rs

+1
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ pub const FILE_CURRENT: DWORD = 1;
265265
pub const FILE_END: DWORD = 2;
266266

267267
pub const WAIT_OBJECT_0: DWORD = 0x00000000;
268+
pub const WAIT_TIMEOUT: DWORD = 258;
268269

269270
#[cfg(target_env = "msvc")]
270271
pub const MAX_SYM_NAME: usize = 2000;

src/libstd/sys/windows/process.rs

+15
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,21 @@ impl Process {
340340
}
341341
}
342342

343+
pub fn try_wait(&mut self) -> io::Result<ExitStatus> {
344+
unsafe {
345+
match c::WaitForSingleObject(self.handle.raw(), 0) {
346+
c::WAIT_OBJECT_0 => {}
347+
c::WAIT_TIMEOUT => {
348+
return Err(io::Error::from_raw_os_error(c::WSAEWOULDBLOCK))
349+
}
350+
_ => return Err(io::Error::last_os_error()),
351+
}
352+
let mut status = 0;
353+
cvt(c::GetExitCodeProcess(self.handle.raw(), &mut status))?;
354+
Ok(ExitStatus(status))
355+
}
356+
}
357+
343358
pub fn handle(&self) -> &Handle { &self.handle }
344359

345360
pub fn into_handle(self) -> Handle { self.handle }

src/test/run-pass/try-wait.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(process_try_wait)]
12+
13+
use std::env;
14+
use std::io;
15+
use std::process::Command;
16+
use std::thread;
17+
use std::time::Duration;
18+
19+
fn main() {
20+
let args = env::args().collect::<Vec<_>>();
21+
if args.len() != 1 {
22+
match &args[1][..] {
23+
"sleep" => thread::sleep(Duration::new(1_000, 0)),
24+
_ => {}
25+
}
26+
return
27+
}
28+
29+
let mut me = Command::new(env::current_exe().unwrap())
30+
.arg("sleep")
31+
.spawn()
32+
.unwrap();
33+
let err = me.try_wait().unwrap_err();
34+
assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
35+
let err = me.try_wait().unwrap_err();
36+
assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
37+
38+
me.kill().unwrap();
39+
me.wait().unwrap();
40+
41+
let status = me.try_wait().unwrap();
42+
assert!(!status.success());
43+
let status = me.try_wait().unwrap();
44+
assert!(!status.success());
45+
46+
let mut me = Command::new(env::current_exe().unwrap())
47+
.arg("return-quickly")
48+
.spawn()
49+
.unwrap();
50+
loop {
51+
match me.try_wait() {
52+
Ok(res) => {
53+
assert!(res.success());
54+
break
55+
}
56+
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
57+
thread::sleep(Duration::from_millis(1));
58+
}
59+
Err(e) => panic!("error in try_wait: {}", e),
60+
}
61+
}
62+
63+
let status = me.try_wait().unwrap();
64+
assert!(status.success());
65+
}

0 commit comments

Comments
 (0)