Skip to content

Commit ab22d99

Browse files
committed
auto merge of #13751 : alexcrichton/rust/io-close-read, r=brson
Two new methods were added to TcpStream and UnixStream: fn close_read(&mut self) -> IoResult<()>; fn close_write(&mut self) -> IoResult<()>; These two methods map to shutdown()'s behavior (the system call on unix), closing the reading or writing half of a duplex stream. These methods are primarily added to allow waking up a pending read in another task. By closing the reading half of a connection, all pending readers will be woken up and will return with EndOfFile. The close_write() method was added for symmetry with close_read(), and I imagine that it will be quite useful at some point. Implementation-wise, librustuv got the short end of the stick this time. The native versions just delegate to the shutdown() syscall (easy). The uv versions can leverage uv_shutdown() for tcp/unix streams, but only for closing the writing half. Closing the reading half is done through some careful dancing to wake up a pending reader. As usual, windows likes to be different from unix. The windows implementation uses shutdown() for sockets, but shutdown() is not available for named pipes. Instead, CancelIoEx was used with same fancy synchronization to make sure everyone knows what's up. cc #11165
2 parents c217a84 + ec9ade9 commit ab22d99

File tree

14 files changed

+534
-98
lines changed

14 files changed

+534
-98
lines changed

src/liblibc/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ pub use consts::os::bsd44::{SOL_SOCKET, SO_KEEPALIVE, SO_ERROR};
118118
pub use consts::os::bsd44::{SO_REUSEADDR, SO_BROADCAST, SHUT_WR, IP_MULTICAST_LOOP};
119119
pub use consts::os::bsd44::{IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP};
120120
pub use consts::os::bsd44::{IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP};
121-
pub use consts::os::bsd44::{IP_MULTICAST_TTL, IP_TTL};
121+
pub use consts::os::bsd44::{IP_MULTICAST_TTL, IP_TTL, SHUT_RD};
122122

123123
pub use funcs::c95::ctype::{isalnum, isalpha, iscntrl, isdigit};
124124
pub use funcs::c95::ctype::{islower, isprint, ispunct, isspace};
@@ -226,6 +226,8 @@ pub use funcs::bsd43::{shutdown};
226226
#[cfg(windows)] pub use consts::os::extra::{FILE_WRITE_ATTRIBUTES, FILE_READ_ATTRIBUTES};
227227
#[cfg(windows)] pub use consts::os::extra::{ERROR_PIPE_BUSY, ERROR_IO_PENDING};
228228
#[cfg(windows)] pub use consts::os::extra::{ERROR_PIPE_CONNECTED, WAIT_OBJECT_0};
229+
#[cfg(windows)] pub use consts::os::extra::{ERROR_NOT_FOUND};
230+
#[cfg(windows)] pub use consts::os::extra::{ERROR_OPERATION_ABORTED};
229231
#[cfg(windows)] pub use types::os::common::bsd44::{SOCKET};
230232
#[cfg(windows)] pub use types::os::common::posix01::{stat, utimbuf};
231233
#[cfg(windows)] pub use types::os::arch::extra::{HANDLE, BOOL, LPSECURITY_ATTRIBUTES};
@@ -1740,8 +1742,10 @@ pub mod consts {
17401742
pub static ERROR_NO_DATA: c_int = 232;
17411743
pub static ERROR_INVALID_ADDRESS : c_int = 487;
17421744
pub static ERROR_PIPE_CONNECTED: c_int = 535;
1745+
pub static ERROR_OPERATION_ABORTED: c_int = 995;
17431746
pub static ERROR_IO_PENDING: c_int = 997;
17441747
pub static ERROR_FILE_INVALID : c_int = 1006;
1748+
pub static ERROR_NOT_FOUND: c_int = 1168;
17451749
pub static INVALID_HANDLE_VALUE : c_int = -1;
17461750

17471751
pub static DELETE : DWORD = 0x00010000;

src/libnative/io/c_win32.rs

+2
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,6 @@ extern "system" {
6161
optlen: *mut libc::c_int) -> libc::c_int;
6262

6363
pub fn CancelIo(hFile: libc::HANDLE) -> libc::BOOL;
64+
pub fn CancelIoEx(hFile: libc::HANDLE,
65+
lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL;
6466
}

src/libnative/io/file_unix.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
1313
use libc::{c_int, c_void};
1414
use libc;
15-
use std::sync::arc::UnsafeArc;
1615
use std::c_str::CString;
1716
use std::io::IoError;
1817
use std::io;
1918
use std::mem;
2019
use std::rt::rtio;
20+
use std::sync::arc::UnsafeArc;
2121

2222
use io::{IoResult, retry, keep_going};
2323

@@ -178,6 +178,17 @@ impl rtio::RtioPipe for FileDesc {
178178
fn clone(&self) -> Box<rtio::RtioPipe:Send> {
179179
box FileDesc { inner: self.inner.clone() } as Box<rtio::RtioPipe:Send>
180180
}
181+
182+
// Only supported on named pipes currently. Note that this doesn't have an
183+
// impact on the std::io primitives, this is never called via
184+
// std::io::PipeStream. If the functionality is exposed in the future, then
185+
// these methods will need to be implemented.
186+
fn close_read(&mut self) -> Result<(), IoError> {
187+
Err(io::standard_error(io::InvalidInput))
188+
}
189+
fn close_write(&mut self) -> Result<(), IoError> {
190+
Err(io::standard_error(io::InvalidInput))
191+
}
181192
}
182193

183194
impl rtio::RtioTTY for FileDesc {

src/libnative/io/file_win32.rs

+11
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,17 @@ impl rtio::RtioPipe for FileDesc {
210210
fn clone(&self) -> Box<rtio::RtioPipe:Send> {
211211
box FileDesc { inner: self.inner.clone() } as Box<rtio::RtioPipe:Send>
212212
}
213+
214+
// Only supported on named pipes currently. Note that this doesn't have an
215+
// impact on the std::io primitives, this is never called via
216+
// std::io::PipeStream. If the functionality is exposed in the future, then
217+
// these methods will need to be implemented.
218+
fn close_read(&mut self) -> IoResult<()> {
219+
Err(io::standard_error(io::InvalidInput))
220+
}
221+
fn close_write(&mut self) -> IoResult<()> {
222+
Err(io::standard_error(io::InvalidInput))
223+
}
213224
}
214225

215226
impl rtio::RtioTTY for FileDesc {

src/libnative/io/net.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -357,9 +357,10 @@ impl rtio::RtioTcpStream for TcpStream {
357357
} as Box<rtio::RtioTcpStream:Send>
358358
}
359359
fn close_write(&mut self) -> IoResult<()> {
360-
super::mkerr_libc(unsafe {
361-
libc::shutdown(self.fd(), libc::SHUT_WR)
362-
})
360+
super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_WR) })
361+
}
362+
fn close_read(&mut self) -> IoResult<()> {
363+
super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_RD) })
363364
}
364365
}
365366

src/libnative/io/pipe_unix.rs

+7
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ impl rtio::RtioPipe for UnixStream {
149149
inner: self.inner.clone(),
150150
} as Box<rtio::RtioPipe:Send>
151151
}
152+
153+
fn close_write(&mut self) -> IoResult<()> {
154+
super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_WR) })
155+
}
156+
fn close_read(&mut self) -> IoResult<()> {
157+
super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_RD) })
158+
}
152159
}
153160

154161
////////////////////////////////////////////////////////////////////////////////

src/libnative/io/pipe_win32.rs

+145-30
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,17 @@
8484
//! the test suite passing (the suite is in libstd), and that's good enough for
8585
//! me!
8686
87-
use std::c_str::CString;
8887
use libc;
88+
use std::c_str::CString;
89+
use std::intrinsics;
90+
use std::io;
8991
use std::os::win32::as_utf16_p;
92+
use std::os;
9093
use std::ptr;
9194
use std::rt::rtio;
9295
use std::sync::arc::UnsafeArc;
93-
use std::intrinsics;
96+
use std::sync::atomics;
97+
use std::unstable::mutex;
9498

9599
use super::IoResult;
96100
use super::c;
@@ -124,6 +128,20 @@ impl Drop for Event {
124128

125129
struct Inner {
126130
handle: libc::HANDLE,
131+
lock: mutex::NativeMutex,
132+
read_closed: atomics::AtomicBool,
133+
write_closed: atomics::AtomicBool,
134+
}
135+
136+
impl Inner {
137+
fn new(handle: libc::HANDLE) -> Inner {
138+
Inner {
139+
handle: handle,
140+
lock: unsafe { mutex::NativeMutex::new() },
141+
read_closed: atomics::AtomicBool::new(false),
142+
write_closed: atomics::AtomicBool::new(false),
143+
}
144+
}
127145
}
128146

129147
impl Drop for Inner {
@@ -218,7 +236,7 @@ impl UnixStream {
218236
loop {
219237
match UnixStream::try_connect(p) {
220238
Some(handle) => {
221-
let inner = Inner { handle: handle };
239+
let inner = Inner::new(handle);
222240
let mut mode = libc::PIPE_TYPE_BYTE |
223241
libc::PIPE_READMODE_BYTE |
224242
libc::PIPE_WAIT;
@@ -275,6 +293,24 @@ impl UnixStream {
275293
}
276294

277295
fn handle(&self) -> libc::HANDLE { unsafe { (*self.inner.get()).handle } }
296+
297+
fn read_closed(&self) -> bool {
298+
unsafe { (*self.inner.get()).read_closed.load(atomics::SeqCst) }
299+
}
300+
301+
fn write_closed(&self) -> bool {
302+
unsafe { (*self.inner.get()).write_closed.load(atomics::SeqCst) }
303+
}
304+
305+
fn cancel_io(&self) -> IoResult<()> {
306+
match unsafe { c::CancelIoEx(self.handle(), ptr::mut_null()) } {
307+
0 if os::errno() == libc::ERROR_NOT_FOUND as uint => {
308+
Ok(())
309+
}
310+
0 => Err(super::last_error()),
311+
_ => Ok(())
312+
}
313+
}
278314
}
279315

280316
impl rtio::RtioPipe for UnixStream {
@@ -287,31 +323,60 @@ impl rtio::RtioPipe for UnixStream {
287323
let mut overlapped: libc::OVERLAPPED = unsafe { intrinsics::init() };
288324
overlapped.hEvent = self.read.get_ref().handle();
289325

326+
// Pre-flight check to see if the reading half has been closed. This
327+
// must be done before issuing the ReadFile request, but after we
328+
// acquire the lock.
329+
//
330+
// See comments in close_read() about why this lock is necessary.
331+
let guard = unsafe { (*self.inner.get()).lock.lock() };
332+
if self.read_closed() {
333+
return Err(io::standard_error(io::EndOfFile))
334+
}
335+
336+
// Issue a nonblocking requests, succeeding quickly if it happened to
337+
// succeed.
290338
let ret = unsafe {
291339
libc::ReadFile(self.handle(),
292340
buf.as_ptr() as libc::LPVOID,
293341
buf.len() as libc::DWORD,
294342
&mut bytes_read,
295343
&mut overlapped)
296344
};
297-
if ret == 0 {
298-
let err = unsafe { libc::GetLastError() };
299-
if err == libc::ERROR_IO_PENDING as libc::DWORD {
300-
let ret = unsafe {
301-
libc::GetOverlappedResult(self.handle(),
302-
&mut overlapped,
303-
&mut bytes_read,
304-
libc::TRUE)
305-
};
306-
if ret == 0 {
307-
return Err(super::last_error())
308-
}
309-
} else {
345+
if ret != 0 { return Ok(bytes_read as uint) }
346+
347+
// If our errno doesn't say that the I/O is pending, then we hit some
348+
// legitimate error and reeturn immediately.
349+
if os::errno() != libc::ERROR_IO_PENDING as uint {
350+
return Err(super::last_error())
351+
}
352+
353+
// Now that we've issued a successful nonblocking request, we need to
354+
// wait for it to finish. This can all be done outside the lock because
355+
// we'll see any invocation of CancelIoEx. We also call this in a loop
356+
// because we're woken up if the writing half is closed, we just need to
357+
// realize that the reading half wasn't closed and we go right back to
358+
// sleep.
359+
drop(guard);
360+
loop {
361+
let ret = unsafe {
362+
libc::GetOverlappedResult(self.handle(),
363+
&mut overlapped,
364+
&mut bytes_read,
365+
libc::TRUE)
366+
};
367+
// If we succeeded, or we failed for some reason other than
368+
// CancelIoEx, return immediately
369+
if ret != 0 { return Ok(bytes_read as uint) }
370+
if os::errno() != libc::ERROR_OPERATION_ABORTED as uint {
310371
return Err(super::last_error())
311372
}
312-
}
313373

314-
Ok(bytes_read as uint)
374+
// If the reading half is now closed, then we're done. If we woke up
375+
// because the writing half was closed, keep trying.
376+
if self.read_closed() {
377+
return Err(io::standard_error(io::EndOfFile))
378+
}
379+
}
315380
}
316381

317382
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
@@ -325,27 +390,47 @@ impl rtio::RtioPipe for UnixStream {
325390

326391
while offset < buf.len() {
327392
let mut bytes_written = 0;
393+
394+
// This sequence below is quite similar to the one found in read().
395+
// Some careful looping is done to ensure that if close_write() is
396+
// invoked we bail out early, and if close_read() is invoked we keep
397+
// going after we woke up.
398+
//
399+
// See comments in close_read() about why this lock is necessary.
400+
let guard = unsafe { (*self.inner.get()).lock.lock() };
401+
if self.write_closed() {
402+
return Err(io::standard_error(io::BrokenPipe))
403+
}
328404
let ret = unsafe {
329405
libc::WriteFile(self.handle(),
330406
buf.slice_from(offset).as_ptr() as libc::LPVOID,
331407
(buf.len() - offset) as libc::DWORD,
332408
&mut bytes_written,
333409
&mut overlapped)
334410
};
411+
drop(guard);
412+
335413
if ret == 0 {
336-
let err = unsafe { libc::GetLastError() };
337-
if err == libc::ERROR_IO_PENDING as libc::DWORD {
338-
let ret = unsafe {
339-
libc::GetOverlappedResult(self.handle(),
340-
&mut overlapped,
341-
&mut bytes_written,
342-
libc::TRUE)
343-
};
344-
if ret == 0 {
414+
if os::errno() != libc::ERROR_IO_PENDING as uint {
415+
return Err(super::last_error())
416+
}
417+
let ret = unsafe {
418+
libc::GetOverlappedResult(self.handle(),
419+
&mut overlapped,
420+
&mut bytes_written,
421+
libc::TRUE)
422+
};
423+
// If we weren't aborted, this was a legit error, if we were
424+
// aborted, then check to see if the write half was actually
425+
// closed or whether we woke up from the read half closing.
426+
if ret == 0 {
427+
if os::errno() != libc::ERROR_OPERATION_ABORTED as uint {
345428
return Err(super::last_error())
346429
}
347-
} else {
348-
return Err(super::last_error())
430+
if self.write_closed() {
431+
return Err(io::standard_error(io::BrokenPipe))
432+
}
433+
continue; // retry
349434
}
350435
}
351436
offset += bytes_written as uint;
@@ -360,6 +445,36 @@ impl rtio::RtioPipe for UnixStream {
360445
write: None,
361446
} as Box<rtio::RtioPipe:Send>
362447
}
448+
449+
fn close_read(&mut self) -> IoResult<()> {
450+
// On windows, there's no actual shutdown() method for pipes, so we're
451+
// forced to emulate the behavior manually at the application level. To
452+
// do this, we need to both cancel any pending requests, as well as
453+
// prevent all future requests from succeeding. These two operations are
454+
// not atomic with respect to one another, so we must use a lock to do
455+
// so.
456+
//
457+
// The read() code looks like:
458+
//
459+
// 1. Make sure the pipe is still open
460+
// 2. Submit a read request
461+
// 3. Wait for the read request to finish
462+
//
463+
// The race this lock is preventing is if another thread invokes
464+
// close_read() between steps 1 and 2. By atomically executing steps 1
465+
// and 2 with a lock with respect to close_read(), we're guaranteed that
466+
// no thread will erroneously sit in a read forever.
467+
let _guard = unsafe { (*self.inner.get()).lock.lock() };
468+
unsafe { (*self.inner.get()).read_closed.store(true, atomics::SeqCst) }
469+
self.cancel_io()
470+
}
471+
472+
fn close_write(&mut self) -> IoResult<()> {
473+
// see comments in close_read() for why this lock is necessary
474+
let _guard = unsafe { (*self.inner.get()).lock.lock() };
475+
unsafe { (*self.inner.get()).write_closed.store(true, atomics::SeqCst) }
476+
self.cancel_io()
477+
}
363478
}
364479

365480
////////////////////////////////////////////////////////////////////////////////
@@ -520,7 +635,7 @@ impl UnixAcceptor {
520635

521636
// Transfer ownership of our handle into this stream
522637
Ok(UnixStream {
523-
inner: UnsafeArc::new(Inner { handle: handle }),
638+
inner: UnsafeArc::new(Inner::new(handle)),
524639
read: None,
525640
write: None,
526641
})

0 commit comments

Comments
 (0)