Skip to content

Commit 2d697fc

Browse files
authored
tests: handle spurious EWOULDBLOCK in io_async_fd (#6776)
* tests: handle spurious EWOULDBLOCK in io_async_fd ## Motivation The `io_async_fd.rs` tests contain a `drain()` function, which currently performs synchronous reads from a UDS socket until it returns `io::ErrorKind::WouldBlock` (i.e., errno `EWOULDBLOCK`/`EAGAIN`). The *intent* behind this function is to ensure that all data has been drained from the UDS socket's buffer...which is what it appears to do...on Linux. On other systems, it appears that an `EWOULDBLOCK` or `EAGAIN` may be returned before enough data has been read from the UDS socket to result in the other end being notified that the socket is now writable. In particular, this appears to be the case on illumos, where the tests using this function hang forever (see [this comment][1] on PR #6769). To my knowledge, this behavior is still POSIX-compliant --- the reader will still be notified that the socket is readable, and if it were actually doing non-blocking IO, it would continue reading upon receipt of that notification. So, relying on `EWOULDBLOCK` to indicate that the socket has been sufficiently drained appears to rely on Linux/FreeBSD behavior that isn't necessarily portable to other Unices. ## Solution This commit changes the `drain()` function to take an argument for the number of bytes *written* to the socket previously, and continue looping until it has read that many bytes, regardless of whether `EWOULDBLOCK` is returned. This should ensure that the socket is drained on all POSIX-compliant systems, and indeed, the `io_async_fd::reset_writable` and `io_async_fd::poll_fns` tests no longer hang forever on illumos. I think making this change is an appropriate solution to the test failure here, as the `drain()` function is part of the test, rather than the code in Tokio *being* tested, and (as I mentioned above) the use of blocking reads on a non-blocking socket without a mechanism to continue reading when the socket becomes readable again is not really something a real life program seems likely to do. Ensuring that all the written bytes have been read by passing in a byte count seems more faithful to what the test is actually *trying* to do here, anyway. Thanks to @jclulow for debugging what was going on here! This change was cherry-picked from commit f18d6ed from PR #6769, so that the fix can be merged separately. [1]: #6769 (comment) Signed-off-by: Eliza Weisman <[email protected]>
1 parent 39c3c19 commit 2d697fc

File tree

1 file changed

+14
-12
lines changed

1 file changed

+14
-12
lines changed

tokio/tests/io_async_fd.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,14 @@ fn socketpair() -> (FileDescriptor, FileDescriptor) {
135135
fds
136136
}
137137

138-
fn drain(mut fd: &FileDescriptor) {
138+
fn drain(mut fd: &FileDescriptor, mut amt: usize) {
139139
let mut buf = [0u8; 512];
140-
#[allow(clippy::unused_io_amount)]
141-
loop {
140+
while amt > 0 {
142141
match fd.read(&mut buf[..]) {
143-
Err(e) if e.kind() == ErrorKind::WouldBlock => break,
142+
Err(e) if e.kind() == ErrorKind::WouldBlock => {}
144143
Ok(0) => panic!("unexpected EOF"),
145144
Err(e) => panic!("unexpected error: {:?}", e),
146-
Ok(_) => continue,
145+
Ok(x) => amt -= x,
147146
}
148147
}
149148
}
@@ -219,10 +218,10 @@ async fn reset_writable() {
219218
let mut guard = afd_a.writable().await.unwrap();
220219

221220
// Write until we get a WouldBlock. This also clears the ready state.
222-
while guard
223-
.try_io(|_| afd_a.get_ref().write(&[0; 512][..]))
224-
.is_ok()
225-
{}
221+
let mut bytes = 0;
222+
while let Ok(Ok(amt)) = guard.try_io(|_| afd_a.get_ref().write(&[0; 512][..])) {
223+
bytes += amt;
224+
}
226225

227226
// Writable state should be cleared now.
228227
let writable = afd_a.writable();
@@ -234,7 +233,7 @@ async fn reset_writable() {
234233
}
235234

236235
// Read from the other side; we should become writable now.
237-
drain(&b);
236+
drain(&b, bytes);
238237

239238
let _ = writable.await.unwrap();
240239
}
@@ -386,7 +385,10 @@ async fn poll_fns() {
386385
let afd_b = Arc::new(AsyncFd::new(b).unwrap());
387386

388387
// Fill up the write side of A
389-
while afd_a.get_ref().write(&[0; 512]).is_ok() {}
388+
let mut bytes = 0;
389+
while let Ok(amt) = afd_a.get_ref().write(&[0; 512]) {
390+
bytes += amt;
391+
}
390392

391393
let waker = TestWaker::new();
392394

@@ -446,7 +448,7 @@ async fn poll_fns() {
446448
}
447449

448450
// Make it writable now
449-
drain(afd_b.get_ref());
451+
drain(afd_b.get_ref(), bytes);
450452

451453
// now we should be writable (ie - the waker for poll_write should still be registered after we wake the read side)
452454
let _ = write_fut.await;

0 commit comments

Comments
 (0)