]> git.lizzy.rs Git - rust.git/commitdiff
std: Add a nonblocking `Child::try_wait` method
authorAlex Crichton <alex@alexcrichton.com>
Fri, 6 Jan 2017 06:47:09 +0000 (22:47 -0800)
committerAlex Crichton <alex@alexcrichton.com>
Sat, 7 Jan 2017 05:20:39 +0000 (21:20 -0800)
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.

src/libstd/process.rs
src/libstd/sys/unix/process/process_unix.rs
src/libstd/sys/windows/c.rs
src/libstd/sys/windows/process.rs
src/test/run-pass/try-wait.rs [new file with mode: 0644]

index e15c37aaf24911ac6ade0258109aa5150b084350..ac77a9123a9e5253f6578601fe666fc776bf2fa4 100644 (file)
@@ -793,6 +793,48 @@ pub fn wait(&mut self) -> io::Result<ExitStatus> {
         self.handle.wait().map(ExitStatus)
     }
 
+    /// Attempts to collect the exit status of the child if it has already
+    /// exited.
+    ///
+    /// This function will not block the calling thread and will only advisorily
+    /// check to see if the child process has exited or not. If the child has
+    /// exited then on Unix the process id is reaped. This function is
+    /// guaranteed to repeatedly return a successful exit status so long as the
+    /// child has already exited.
+    ///
+    /// If the child has exited, then `Ok(status)` is returned. If the exit
+    /// status is not available at this time then an error is returned with the
+    /// error kind `WouldBlock`. If an error occurs, then that error is returned.
+    ///
+    /// Note that unlike `wait`, this function will not attempt to drop stdin.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```no_run
+    /// #![feature(process_try_wait)]
+    ///
+    /// use std::io;
+    /// use std::process::Command;
+    ///
+    /// let mut child = Command::new("ls").spawn().unwrap();
+    ///
+    /// match child.try_wait() {
+    ///     Ok(status) => println!("exited with: {}", status),
+    ///     Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+    ///         println!("status not ready yet, let's really wait");
+    ///         let res = child.wait();
+    ///         println!("result: {:?}", res);
+    ///     }
+    ///     Err(e) => println!("error attempting to wait: {}", e),
+    /// }
+    /// ```
+    #[unstable(feature = "process_try_wait", issue = "38903")]
+    pub fn try_wait(&mut self) -> io::Result<ExitStatus> {
+        self.handle.try_wait().map(ExitStatus)
+    }
+
     /// Simultaneously waits for the child to exit and collect all remaining
     /// output on the stdout/stderr handles, returning an `Output`
     /// instance.
index aa42672202559f94d5283064627808c1692c2667..0dc1739c1a15aef50a5e7d956ddff298a7c56b85 100644 (file)
@@ -237,6 +237,7 @@ pub fn kill(&mut self) -> io::Result<()> {
             cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(|_| ())
         }
     }
+
     pub fn wait(&mut self) -> io::Result<ExitStatus> {
         use sys::cvt_r;
         if let Some(status) = self.status {
@@ -247,4 +248,20 @@ pub fn wait(&mut self) -> io::Result<ExitStatus> {
         self.status = Some(ExitStatus::new(status));
         Ok(ExitStatus::new(status))
     }
+
+    pub fn try_wait(&mut self) -> io::Result<ExitStatus> {
+        if let Some(status) = self.status {
+            return Ok(status)
+        }
+        let mut status = 0 as c_int;
+        let pid = cvt(unsafe {
+            libc::waitpid(self.pid, &mut status, libc::WNOHANG)
+        })?;
+        if pid == 0 {
+            Err(io::Error::from_raw_os_error(libc::EWOULDBLOCK))
+        } else {
+            self.status = Some(ExitStatus::new(status));
+            Ok(ExitStatus::new(status))
+        }
+    }
 }
index d1c404195bc68d5bde33a61f08cb9765694c2394..4d05904933e322d86e99076deb38f4266c3741df 100644 (file)
@@ -265,6 +265,7 @@ pub struct ipv6_mreq {
 pub const FILE_END: DWORD = 2;
 
 pub const WAIT_OBJECT_0: DWORD = 0x00000000;
+pub const WAIT_TIMEOUT: DWORD = 258;
 
 #[cfg(target_env = "msvc")]
 pub const MAX_SYM_NAME: usize = 2000;
index 969de6b85a6aa93260e1194d8901baef16d057f6..6b70a2f4d8b2ec7addffbd659fa325efe01da853 100644 (file)
@@ -344,6 +344,21 @@ pub fn wait(&mut self) -> io::Result<ExitStatus> {
         }
     }
 
+    pub fn try_wait(&mut self) -> io::Result<ExitStatus> {
+        unsafe {
+            match c::WaitForSingleObject(self.handle.raw(), 0) {
+                c::WAIT_OBJECT_0 => {}
+                c::WAIT_TIMEOUT => {
+                    return Err(io::Error::from_raw_os_error(c::WSAEWOULDBLOCK))
+                }
+                _ => return Err(io::Error::last_os_error()),
+            }
+            let mut status = 0;
+            cvt(c::GetExitCodeProcess(self.handle.raw(), &mut status))?;
+            Ok(ExitStatus(status))
+        }
+    }
+
     pub fn handle(&self) -> &Handle { &self.handle }
 
     pub fn into_handle(self) -> Handle { self.handle }
diff --git a/src/test/run-pass/try-wait.rs b/src/test/run-pass/try-wait.rs
new file mode 100644 (file)
index 0000000..fdaf0cf
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(process_try_wait)]
+
+use std::env;
+use std::io;
+use std::process::Command;
+use std::thread;
+use std::time::Duration;
+
+fn main() {
+    let args = env::args().collect::<Vec<_>>();
+    if args.len() != 1 {
+        match &args[1][..] {
+            "sleep" => thread::sleep(Duration::new(1_000, 0)),
+            _ => {}
+        }
+        return
+    }
+
+    let mut me = Command::new(env::current_exe().unwrap())
+                         .arg("sleep")
+                         .spawn()
+                         .unwrap();
+    let err = me.try_wait().unwrap_err();
+    assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
+    let err = me.try_wait().unwrap_err();
+    assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
+
+    me.kill().unwrap();
+    me.wait().unwrap();
+
+    let status = me.try_wait().unwrap();
+    assert!(!status.success());
+    let status = me.try_wait().unwrap();
+    assert!(!status.success());
+
+    let mut me = Command::new(env::current_exe().unwrap())
+                         .arg("return-quickly")
+                         .spawn()
+                         .unwrap();
+    loop {
+        match me.try_wait() {
+            Ok(res) => {
+                assert!(res.success());
+                break
+            }
+            Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+                thread::sleep(Duration::from_millis(1));
+            }
+            Err(e) => panic!("error in try_wait: {}", e),
+        }
+    }
+
+    let status = me.try_wait().unwrap();
+    assert!(status.success());
+}