|
9 | 9 | // spell-checker:ignore (vars) cvar exitstatus |
10 | 10 | // spell-checker:ignore (sys/unix) WIFSIGNALED |
11 | 11 |
|
12 | | -use libc::{c_int, gid_t, pid_t, uid_t}; |
| 12 | +use libc::{gid_t, pid_t, uid_t}; |
13 | 13 | use std::fmt; |
14 | 14 | use std::io; |
15 | 15 | use std::process::Child; |
16 | | -use std::sync::{Arc, Condvar, Mutex}; |
| 16 | +use std::process::ExitStatus as StdExitStatus; |
17 | 17 | use std::thread; |
18 | 18 | use std::time::{Duration, Instant}; |
19 | 19 |
|
@@ -41,13 +41,18 @@ pub enum ExitStatus { |
41 | 41 | } |
42 | 42 |
|
43 | 43 | impl ExitStatus { |
44 | | - fn from_status(status: c_int) -> ExitStatus { |
45 | | - if status & 0x7F != 0 { |
46 | | - // WIFSIGNALED(status) == terminating by/with unhandled signal |
47 | | - ExitStatus::Signal(status & 0x7F) |
48 | | - } else { |
49 | | - ExitStatus::Code(status & 0xFF00 >> 8) |
| 44 | + fn from_std_status(status: StdExitStatus) -> Self { |
| 45 | + #[cfg(unix)] |
| 46 | + { |
| 47 | + use std::os::unix::process::ExitStatusExt; |
| 48 | + |
| 49 | + if let Some(signal) = status.signal() { |
| 50 | + return ExitStatus::Signal(signal); |
| 51 | + } |
50 | 52 | } |
| 53 | + |
| 54 | + // NOTE: this should never fail as we check if the program exited through a signal above |
| 55 | + ExitStatus::Code(status.code().unwrap()) |
51 | 56 | } |
52 | 57 |
|
53 | 58 | pub fn success(&self) -> bool { |
@@ -100,47 +105,25 @@ impl ChildExt for Child { |
100 | 105 | } |
101 | 106 |
|
102 | 107 | fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>> { |
103 | | - // The result will be written to that Option, protected by a Mutex |
104 | | - // Then the Condvar will be signaled |
105 | | - let state = Arc::new(( |
106 | | - Mutex::new(Option::None::<io::Result<ExitStatus>>), |
107 | | - Condvar::new(), |
108 | | - )); |
109 | | - |
110 | | - // Start the waiting thread |
111 | | - let state_th = state.clone(); |
112 | | - let pid_th = self.id(); |
113 | | - thread::spawn(move || { |
114 | | - let &(ref lock_th, ref cvar_th) = &*state_th; |
115 | | - // Child::wait() would need a &mut to self, can't use that... |
116 | | - // use waitpid() directly, with our own ExitStatus |
117 | | - let mut status: c_int = 0; |
118 | | - let r = unsafe { libc::waitpid(pid_th as i32, &mut status, 0) }; |
119 | | - // Fill the Option and notify on the Condvar |
120 | | - let mut exitstatus_th = lock_th.lock().unwrap(); |
121 | | - if r != pid_th as c_int { |
122 | | - *exitstatus_th = Some(Err(io::Error::last_os_error())); |
123 | | - } else { |
124 | | - let s = ExitStatus::from_status(status); |
125 | | - *exitstatus_th = Some(Ok(s)); |
126 | | - } |
127 | | - cvar_th.notify_one(); |
128 | | - }); |
| 108 | + // .try_wait() doesn't drop stdin, so we do it manually |
| 109 | + drop(self.stdin.take()); |
129 | 110 |
|
130 | | - // Main thread waits |
131 | | - let &(ref lock, ref cvar) = &*state; |
132 | | - let mut exitstatus = lock.lock().unwrap(); |
133 | | - // Condvar::wait_timeout_ms() can wake too soon, in this case wait again |
134 | 111 | let start = Instant::now(); |
135 | 112 | loop { |
136 | | - if let Some(exitstatus) = exitstatus.take() { |
137 | | - return exitstatus.map(Some); |
| 113 | + if let Some(status) = self.try_wait()? { |
| 114 | + return Ok(Some(ExitStatus::from_std_status(status))); |
138 | 115 | } |
| 116 | + |
139 | 117 | if start.elapsed() >= timeout { |
140 | | - return Ok(None); |
| 118 | + break; |
141 | 119 | } |
142 | | - let cvar_timeout = timeout - start.elapsed(); |
143 | | - exitstatus = cvar.wait_timeout(exitstatus, cvar_timeout).unwrap().0; |
| 120 | + |
| 121 | + // XXX: this is kinda gross, but it's cleaner than starting a thread just to wait |
| 122 | + // (which was the previous solution). We might want to use a different duration |
| 123 | + // here as well |
| 124 | + thread::sleep(Duration::from_millis(100)); |
144 | 125 | } |
| 126 | + |
| 127 | + Ok(None) |
145 | 128 | } |
146 | 129 | } |
0 commit comments