Skip to content

Commit 7c3038f

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 6afa32a commit 7c3038f

File tree

10 files changed

+459
-34
lines changed

10 files changed

+459
-34
lines changed

src/libstd/process.rs

+20-16
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+
res.unwrap();
512+
}
513+
(None, Some(mut err)) => {
514+
let res = err.read_to_end(&mut stderr);
515+
res.unwrap();
516+
}
517+
(Some(out), Some(err)) => {
518+
let res = read2(out.inner, &mut stdout, err.inner, &mut stderr);
519+
res.unwrap();
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

+16-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
#![unstable(reason = "not public", issue = "0", feature = "fd")]
12+
1113
use prelude::v1::*;
1214

1315
use io::{self, Read};
@@ -75,6 +77,20 @@ impl FileDesc {
7577
}
7678
}
7779

80+
pub fn set_nonblocking(&self, nonblocking: bool) {
81+
unsafe {
82+
let previous = libc::fcntl(self.fd, libc::F_GETFL);
83+
debug_assert!(previous != -1);
84+
let new = if nonblocking {
85+
previous | libc::O_NONBLOCK
86+
} else {
87+
previous & !libc::O_NONBLOCK
88+
};
89+
let ret = libc::fcntl(self.fd, libc::F_SETFL, new);
90+
debug_assert!(ret != -1);
91+
}
92+
}
93+
7894
pub fn duplicate(&self) -> io::Result<FileDesc> {
7995
// We want to atomically duplicate this file descriptor and set the
8096
// CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This
@@ -126,7 +142,6 @@ impl FileDesc {
126142
}
127143
}
128144

129-
#[unstable(reason = "not public", issue = "0", feature = "fd_read")]
130145
impl<'a> Read for &'a FileDesc {
131146
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
132147
(**self).read(buf)

src/libstd/sys/unix/pipe.rs

+55
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).map(|_| ());
119+
}
120+
if try!(read(&p2, v2)) {
121+
p1.set_nonblocking(false);
122+
return p1.read_to_end(v1).map(|_| ());
123+
}
124+
}
125+
}

src/libstd/sys/unix/process.rs

+1-1
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

+34-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
1313
#![allow(bad_style)]
1414
#![cfg_attr(test, allow(dead_code))]
15+
#![unstable(issue = "0", feature = "windows_c")]
1516

1617
use os::raw::{c_int, c_uint, c_ulong, c_long, c_longlong, c_ushort,};
1718
use os::raw::{c_char, c_ulonglong};
@@ -181,13 +182,15 @@ pub const ERROR_PATH_NOT_FOUND: DWORD = 3;
181182
pub const ERROR_ACCESS_DENIED: DWORD = 5;
182183
pub const ERROR_INVALID_HANDLE: DWORD = 6;
183184
pub const ERROR_NO_MORE_FILES: DWORD = 18;
185+
pub const ERROR_HANDLE_EOF: DWORD = 38;
184186
pub const ERROR_BROKEN_PIPE: DWORD = 109;
185187
pub const ERROR_CALL_NOT_IMPLEMENTED: DWORD = 120;
186188
pub const ERROR_INSUFFICIENT_BUFFER: DWORD = 122;
187189
pub const ERROR_ALREADY_EXISTS: DWORD = 183;
188190
pub const ERROR_NO_DATA: DWORD = 232;
189191
pub const ERROR_ENVVAR_NOT_FOUND: DWORD = 203;
190192
pub const ERROR_OPERATION_ABORTED: DWORD = 995;
193+
pub const ERROR_IO_PENDING: DWORD = 997;
191194
pub const ERROR_TIMEOUT: DWORD = 0x5B4;
192195

193196
pub const INVALID_HANDLE_VALUE: HANDLE = !0 as HANDLE;
@@ -292,6 +295,14 @@ pub const EXCEPTION_UNWIND: DWORD = EXCEPTION_UNWINDING |
292295
EXCEPTION_TARGET_UNWIND |
293296
EXCEPTION_COLLIDED_UNWIND;
294297

298+
pub const PIPE_ACCESS_INBOUND: DWORD = 0x00000001;
299+
pub const FILE_FLAG_FIRST_PIPE_INSTANCE: DWORD = 0x00080000;
300+
pub const FILE_FLAG_OVERLAPPED: DWORD = 0x40000000;
301+
pub const PIPE_WAIT: DWORD = 0x00000000;
302+
pub const PIPE_TYPE_BYTE: DWORD = 0x00000000;
303+
pub const PIPE_REJECT_REMOTE_CLIENTS: DWORD = 0x00000008;
304+
pub const PIPE_READMODE_BYTE: DWORD = 0x00000000;
305+
295306
#[repr(C)]
296307
#[cfg(target_arch = "x86")]
297308
pub struct WSADATA {
@@ -913,10 +924,6 @@ extern "system" {
913924
nOutBufferSize: DWORD,
914925
lpBytesReturned: LPDWORD,
915926
lpOverlapped: LPOVERLAPPED) -> BOOL;
916-
pub fn CreatePipe(hReadPipe: LPHANDLE,
917-
hWritePipe: LPHANDLE,
918-
lpPipeAttributes: LPSECURITY_ATTRIBUTES,
919-
nSize: DWORD) -> BOOL;
920927
pub fn CreateThread(lpThreadAttributes: LPSECURITY_ATTRIBUTES,
921928
dwStackSize: SIZE_T,
922929
lpStartAddress: extern "system" fn(*mut c_void)
@@ -1129,6 +1136,29 @@ extern "system" {
11291136
OriginalContext: *const CONTEXT,
11301137
HistoryTable: *const UNWIND_HISTORY_TABLE);
11311138
pub fn GetSystemTimeAsFileTime(lpSystemTimeAsFileTime: LPFILETIME);
1139+
1140+
pub fn CreateEventW(lpEventAttributes: LPSECURITY_ATTRIBUTES,
1141+
bManualReset: BOOL,
1142+
bInitialState: BOOL,
1143+
lpName: LPCWSTR) -> HANDLE;
1144+
pub fn WaitForMultipleObjects(nCount: DWORD,
1145+
lpHandles: *const HANDLE,
1146+
bWaitAll: BOOL,
1147+
dwMilliseconds: DWORD) -> DWORD;
1148+
pub fn CreateNamedPipeW(lpName: LPCWSTR,
1149+
dwOpenMode: DWORD,
1150+
dwPipeMode: DWORD,
1151+
nMaxInstances: DWORD,
1152+
nOutBufferSize: DWORD,
1153+
nInBufferSize: DWORD,
1154+
nDefaultTimeOut: DWORD,
1155+
lpSecurityAttributes: LPSECURITY_ATTRIBUTES)
1156+
-> HANDLE;
1157+
pub fn CancelIo(handle: HANDLE) -> BOOL;
1158+
pub fn GetOverlappedResult(hFile: HANDLE,
1159+
lpOverlapped: LPOVERLAPPED,
1160+
lpNumberOfBytesTransferred: LPDWORD,
1161+
bWait: BOOL) -> BOOL;
11321162
}
11331163

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

src/libstd/sys/windows/handle.rs

+69-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
#![unstable(issue = "0", feature = "windows_handle")]
12+
1113
use prelude::v1::*;
1214

1315
use cmp;
@@ -42,6 +44,20 @@ impl Handle {
4244
Handle(RawHandle::new(handle))
4345
}
4446

47+
pub fn new_event(manual: bool, init: bool) -> io::Result<Handle> {
48+
unsafe {
49+
let event = c::CreateEventW(0 as *mut _,
50+
manual as c::BOOL,
51+
init as c::BOOL,
52+
0 as *const _);
53+
if event.is_null() {
54+
Err(io::Error::last_os_error())
55+
} else {
56+
Ok(Handle::new(event))
57+
}
58+
}
59+
}
60+
4561
pub fn into_raw(self) -> c::HANDLE {
4662
let ret = self.raw();
4763
mem::forget(self);
@@ -90,6 +106,59 @@ impl RawHandle {
90106
}
91107
}
92108

109+
pub unsafe fn read_overlapped(&self,
110+
buf: &mut [u8],
111+
overlapped: *mut c::OVERLAPPED)
112+
-> io::Result<Option<usize>> {
113+
let len = cmp::min(buf.len(), <c::DWORD>::max_value() as usize) as c::DWORD;
114+
let mut amt = 0;
115+
let res = cvt({
116+
c::ReadFile(self.0, buf.as_ptr() as c::LPVOID,
117+
len, &mut amt, overlapped)
118+
});
119+
match res {
120+
Ok(_) => Ok(Some(amt as usize)),
121+
Err(e) => {
122+
if e.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) {
123+
Ok(None)
124+
} else if e.raw_os_error() == Some(c::ERROR_BROKEN_PIPE as i32) {
125+
Ok(Some(0))
126+
} else {
127+
Err(e)
128+
}
129+
}
130+
}
131+
}
132+
133+
pub fn overlapped_result(&self,
134+
overlapped: *mut c::OVERLAPPED,
135+
wait: bool) -> io::Result<usize> {
136+
unsafe {
137+
let mut bytes = 0;
138+
let wait = if wait {c::TRUE} else {c::FALSE};
139+
let res = cvt({
140+
c::GetOverlappedResult(self.raw(), overlapped, &mut bytes, wait)
141+
});
142+
match res {
143+
Ok(_) => Ok(bytes as usize),
144+
Err(e) => {
145+
if e.raw_os_error() == Some(c::ERROR_HANDLE_EOF as i32) ||
146+
e.raw_os_error() == Some(c::ERROR_BROKEN_PIPE as i32) {
147+
Ok(0)
148+
} else {
149+
Err(e)
150+
}
151+
}
152+
}
153+
}
154+
}
155+
156+
pub fn cancel_io(&self) -> io::Result<()> {
157+
unsafe {
158+
cvt(c::CancelIo(self.raw())).map(|_| ())
159+
}
160+
}
161+
93162
pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
94163
let mut me = self;
95164
(&mut me).read_to_end(buf)
@@ -120,7 +189,6 @@ impl RawHandle {
120189
}
121190
}
122191

123-
#[unstable(reason = "not public", issue = "0", feature = "fd_read")]
124192
impl<'a> Read for &'a RawHandle {
125193
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
126194
(**self).read(buf)

src/libstd/sys/windows/net.rs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
#![unstable(issue = "0", feature = "windows_net")]
12+
1113
use prelude::v1::*;
1214

1315
use cmp;

0 commit comments

Comments
 (0)