Skip to content

Commit 05c188d

Browse files
committed
std: Don't spawn threads in wait_with_output
Semantically there's actually no reason for us to spawn threads as part of the call to `wait_with_output`, and that's generally an incredibly heavyweight operation for just reading a few bytes (especially when stderr probably rarely has bytes!). An equivalent operation in terms of what's implemented today would be to just drain both pipes of all contents and then call `wait` on the child process itself. On Unix we can implement this through some convenient use of the `select` function, whereas on Windows we can make use of overlapped I/O. Note that on Windows this requires us to use named pipes instead of anonymous pipes, but they're semantically the same under the hood.
1 parent 616ca6a commit 05c188d

File tree

7 files changed

+381
-29
lines changed

7 files changed

+381
-29
lines changed

src/libstd/process.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ use fmt;
2020
use io;
2121
use path::Path;
2222
use str;
23-
use sys::pipe::AnonPipe;
23+
use sys::pipe::{read2, AnonPipe};
2424
use sys::process as imp;
2525
use sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
26-
use thread::{self, JoinHandle};
2726

2827
/// Representation of a running or exited child process.
2928
///
@@ -503,24 +502,29 @@ impl Child {
503502
#[stable(feature = "process", since = "1.0.0")]
504503
pub fn wait_with_output(mut self) -> io::Result<Output> {
505504
drop(self.stdin.take());
506-
fn read<R>(mut input: R) -> JoinHandle<io::Result<Vec<u8>>>
507-
where R: Read + Send + 'static
508-
{
509-
thread::spawn(move || {
510-
let mut ret = Vec::new();
511-
input.read_to_end(&mut ret).map(|_| ret)
512-
})
505+
506+
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
507+
match (self.stdout.take(), self.stderr.take()) {
508+
(None, None) => {}
509+
(Some(mut out), None) => {
510+
let res = out.read_to_end(&mut stdout);
511+
debug_assert!(res.is_ok());
512+
}
513+
(None, Some(mut err)) => {
514+
let res = err.read_to_end(&mut stderr);
515+
debug_assert!(res.is_ok());
516+
}
517+
(Some(out), Some(err)) => {
518+
let res = read2(out.inner, &mut stdout, err.inner, &mut stderr);
519+
debug_assert!(res.is_ok());
520+
}
513521
}
514-
let stdout = self.stdout.take().map(read);
515-
let stderr = self.stderr.take().map(read);
516-
let status = try!(self.wait());
517-
let stdout = stdout.and_then(|t| t.join().unwrap().ok());
518-
let stderr = stderr.and_then(|t| t.join().unwrap().ok());
519522

523+
let status = try!(self.wait());
520524
Ok(Output {
521525
status: status,
522-
stdout: stdout.unwrap_or(Vec::new()),
523-
stderr: stderr.unwrap_or(Vec::new()),
526+
stdout: stdout,
527+
stderr: stderr,
524528
})
525529
}
526530
}

src/libstd/sys/unix/fd.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,20 @@ impl FileDesc {
7575
}
7676
}
7777

78+
pub fn set_nonblocking(&self, nonblocking: bool) {
79+
unsafe {
80+
let previous = libc::fcntl(self.fd, libc::F_GETFL);
81+
debug_assert!(previous != -1);
82+
let new = if nonblocking {
83+
previous | libc::O_NONBLOCK
84+
} else {
85+
previous & !libc::O_NONBLOCK
86+
};
87+
let ret = libc::fcntl(self.fd, libc::F_SETFL, new);
88+
debug_assert!(ret != -1);
89+
}
90+
}
91+
7892
pub fn duplicate(&self) -> io::Result<FileDesc> {
7993
// We want to atomically duplicate this file descriptor and set the
8094
// CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This

src/libstd/sys/unix/pipe.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
use prelude::v1::*;
12+
13+
use cmp;
1114
use io;
1215
use libc::{self, c_int};
16+
use mem;
1317
use sys::cvt_r;
1418
use sys::fd::FileDesc;
1519

@@ -68,3 +72,54 @@ impl AnonPipe {
6872
pub fn fd(&self) -> &FileDesc { &self.0 }
6973
pub fn into_fd(self) -> FileDesc { self.0 }
7074
}
75+
76+
pub fn read2(p1: AnonPipe,
77+
v1: &mut Vec<u8>,
78+
p2: AnonPipe,
79+
v2: &mut Vec<u8>) -> io::Result<()> {
80+
// Set both pipes into nonblocking mode as we're gonna be reading from both
81+
// in the `select` loop below, and we wouldn't want one to block the other!
82+
let p1 = p1.into_fd();
83+
let p2 = p2.into_fd();
84+
p1.set_nonblocking(true);
85+
p2.set_nonblocking(true);
86+
87+
let max = cmp::max(p1.raw(), p2.raw());
88+
loop {
89+
// wait for either pipe to become readable using `select`
90+
try!(cvt_r(|| unsafe {
91+
let mut read: libc::fd_set = mem::zeroed();
92+
libc::FD_SET(p1.raw(), &mut read);
93+
libc::FD_SET(p2.raw(), &mut read);
94+
libc::select(max + 1, &mut read, 0 as *mut _, 0 as *mut _,
95+
0 as *mut _)
96+
}));
97+
98+
// Read as much as we can from each pipe, ignoring EWOULDBLOCK or
99+
// EAGAIN. If we hit EOF, then this will happen because the underlying
100+
// reader will return Ok(0), in which case we'll see `Ok` ourselves. In
101+
// this case we flip the other fd back into blocking mode and read
102+
// whatever's leftover on that file descriptor.
103+
let read = |fd: &FileDesc, dst: &mut Vec<u8>| {
104+
match fd.read_to_end(dst) {
105+
Ok(_) => Ok(true),
106+
Err(e) => {
107+
if e.raw_os_error() == Some(libc::EWOULDBLOCK) ||
108+
e.raw_os_error() == Some(libc::EAGAIN) {
109+
Ok(false)
110+
} else {
111+
Err(e)
112+
}
113+
}
114+
}
115+
};
116+
if try!(read(&p1, v1)) {
117+
p2.set_nonblocking(false);
118+
return p2.read_to_end(v2);
119+
}
120+
if try!(read(&p2, v2)) {
121+
p1.set_nonblocking(false);
122+
return p1.read_to_end(v1);
123+
}
124+
}
125+
}

src/libstd/sys/unix/process.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ mod tests {
651651
cmd.stdin(Stdio::MakePipe);
652652
cmd.stdout(Stdio::MakePipe);
653653

654-
let (mut cat, mut pipes) = t!(cmd.spawn(Stdio::Null));
654+
let (mut cat, mut pipes) = t!(cmd.spawn(Stdio::Null, true));
655655
let stdin_write = pipes.stdin.take().unwrap();
656656
let stdout_read = pipes.stdout.take().unwrap();
657657

src/libstd/sys/windows/c.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ pub const ERROR_PATH_NOT_FOUND: DWORD = 3;
268268
pub const ERROR_ACCESS_DENIED: DWORD = 5;
269269
pub const ERROR_INVALID_HANDLE: DWORD = 6;
270270
pub const ERROR_NO_MORE_FILES: DWORD = 18;
271+
pub const ERROR_HANDLE_EOF: DWORD = 38;
271272
pub const ERROR_BROKEN_PIPE: DWORD = 109;
272273
pub const ERROR_DISK_FULL: DWORD = 112;
273274
pub const ERROR_CALL_NOT_IMPLEMENTED: DWORD = 120;
@@ -361,6 +362,14 @@ pub const EXCEPTION_UNWIND: DWORD = EXCEPTION_UNWINDING |
361362
EXCEPTION_TARGET_UNWIND |
362363
EXCEPTION_COLLIDED_UNWIND;
363364

365+
pub const PIPE_ACCESS_INBOUND: DWORD = 0x00000001;
366+
pub const FILE_FLAG_FIRST_PIPE_INSTANCE: DWORD = 0x00080000;
367+
pub const FILE_FLAG_OVERLAPPED: DWORD = 0x40000000;
368+
pub const PIPE_WAIT: DWORD = 0x00000000;
369+
pub const PIPE_TYPE_BYTE: DWORD = 0x00000000;
370+
pub const PIPE_REJECT_REMOTE_CLIENTS: DWORD = 0x00000008;
371+
pub const PIPE_READMODE_BYTE: DWORD = 0x00000000;
372+
364373
#[repr(C)]
365374
#[cfg(target_arch = "x86")]
366375
pub struct WSADATA {
@@ -1261,6 +1270,29 @@ extern "system" {
12611270
OriginalContext: *const CONTEXT,
12621271
HistoryTable: *const UNWIND_HISTORY_TABLE);
12631272
pub fn GetSystemTimeAsFileTime(lpSystemTimeAsFileTime: LPFILETIME);
1273+
1274+
pub fn CreateEventW(lpEventAttributes: LPSECURITY_ATTRIBUTES,
1275+
bManualReset: BOOL,
1276+
bInitialState: BOOL,
1277+
lpName: LPCWSTR) -> HANDLE;
1278+
pub fn WaitForMultipleObjects(nCount: DWORD,
1279+
lpHandles: *const HANDLE,
1280+
bWaitAll: BOOL,
1281+
dwMilliseconds: DWORD) -> DWORD;
1282+
pub fn CreateNamedPipeW(lpName: LPCWSTR,
1283+
dwOpenMode: DWORD,
1284+
dwPipeMode: DWORD,
1285+
nMaxInstances: DWORD,
1286+
nOutBufferSize: DWORD,
1287+
nInBufferSize: DWORD,
1288+
nDefaultTimeOut: DWORD,
1289+
lpSecurityAttributes: LPSECURITY_ATTRIBUTES)
1290+
-> HANDLE;
1291+
pub fn CancelIo(handle: HANDLE) -> BOOL;
1292+
pub fn GetOverlappedResult(hFile: HANDLE,
1293+
lpOverlapped: LPOVERLAPPED,
1294+
lpNumberOfBytesTransferred: LPDWORD,
1295+
bWait: BOOL) -> BOOL;
12641296
}
12651297

12661298
// Functions that aren't available on Windows XP, but we still use them and just

src/libstd/sys/windows/handle.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ impl RawHandle {
6969
let mut read = 0;
7070
let res = cvt(unsafe {
7171
c::ReadFile(self.0, buf.as_ptr() as c::LPVOID,
72-
buf.len() as c::DWORD, &mut read,
73-
ptr::null_mut())
72+
buf.len() as c::DWORD, &mut read,
73+
0 as *mut _)
7474
});
7575

7676
match res {
@@ -86,6 +86,55 @@ impl RawHandle {
8686
}
8787
}
8888

89+
pub unsafe fn read_overlapped(&self,
90+
buf: &mut [u8],
91+
overlapped: *mut c::OVERLAPPED)
92+
-> io::Result<bool> {
93+
let res = cvt({
94+
c::ReadFile(self.0, buf.as_ptr() as c::LPVOID,
95+
buf.len() as c::DWORD, 0 as *mut _,
96+
overlapped)
97+
});
98+
match res {
99+
Ok(_) => Ok(true),
100+
Err(e) => {
101+
if e.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) {
102+
Ok(false)
103+
} else {
104+
Err(e)
105+
}
106+
}
107+
}
108+
}
109+
110+
pub fn overlapped_result(&self,
111+
overlapped: *mut c::OVERLAPPED,
112+
wait: bool) -> io::Result<usize> {
113+
unsafe {
114+
let mut bytes = 0;
115+
let wait = if wait {c::TRUE} else {c::FALSE};
116+
let res = cvt({
117+
c::GetOverlappedResult(self.raw(), overlapped, &mut bytes, wait)
118+
});
119+
match res {
120+
Ok(_) => Ok(bytes as usize),
121+
Err(e) => {
122+
if e.raw_os_error() == Some(c::ERROR_HANDLE_EOF as i32) {
123+
Ok(0)
124+
} else {
125+
Err(e)
126+
}
127+
}
128+
}
129+
}
130+
}
131+
132+
pub fn cancel_io(&self) -> io::Result<()> {
133+
unsafe {
134+
cvt(c::CancelIo(self.raw())).map(|_| ())
135+
}
136+
}
137+
89138
pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
90139
let mut me = self;
91140
(&mut me).read_to_end(buf)

0 commit comments

Comments
 (0)