-
Notifications
You must be signed in to change notification settings - Fork 754
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Currently, EPOLLRDHUP sets UnixReady::hup(). This is incorrect behavior because higher-level libraries like tokio (correctly) assume that UnixReady::hup() is unclearable since it signals that both the read and write halfs are shutdown. In reality, EPOLLRDHUP only means that the TCP stream has been half-closed and such a half-closed stream can still be written to. This will fix a current issue with tokio, which is that tokio infinitely attempts to write to a half-closed socket that's returning WouldBlock when it's write buffer is full, an issue which manifests with excessive CPU usage. I think this may help some of the issues discussed in tokio-rs/tokio#449 After this change, EOF will still be propagated correctly, because read-hangups also trigger read-readiness via EPOLLIN. However, if handling of EPOLLRDHUP is desired to be retained, I believe it should be implemented as another readiness kind on UnixReady, perhaps UnixReady::read_hup(). Possible concern of a breaking change: Since it's not currently possible for a user of mio to differentiate between EPOLLHUP and EPOLLRDHUP, it must be that no users of mio currently are. There _may_ be applications that test the "health" of a socket by checking for UnixRead::hup(), which would previously trigger on EPOLLRDHUP but will no longer with this change. This will change such applications from considering a half-closed connection as closed to considering it open. However, I still beleive this change is a correction of the semantics of HUP and the desired behavior such applications was already ambiguous. This also fixes a similar issue with kqueue.
- Loading branch information
1 parent
5e39b58
commit b89e3bd
Showing
6 changed files
with
93 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
use std::net::Shutdown; | ||
use std::time::Duration; | ||
|
||
use mio::{Token, Ready, PollOpt, Poll, Events}; | ||
use mio::net::TcpStream; | ||
|
||
macro_rules! wait { | ||
($poll:ident, $ready:ident) => {{ | ||
use std::time::Instant; | ||
|
||
let now = Instant::now(); | ||
let mut events = Events::with_capacity(16); | ||
let mut found = false; | ||
|
||
while !found { | ||
if now.elapsed() > Duration::from_secs(5) { | ||
panic!("not ready"); | ||
} | ||
|
||
$poll.poll(&mut events, Some(Duration::from_secs(1))).unwrap(); | ||
|
||
for event in &events { | ||
#[cfg(unix)] | ||
{ | ||
use mio::unix::UnixReady; | ||
assert!(!UnixReady::from(event.readiness()).is_hup()); | ||
} | ||
|
||
if event.token() == Token(0) && event.readiness().$ready() { | ||
found = true; | ||
break; | ||
} | ||
} | ||
} | ||
}}; | ||
} | ||
|
||
#[test] | ||
fn test_write_shutdown() { | ||
let poll = Poll::new().unwrap(); | ||
|
||
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); | ||
let addr = listener.local_addr().unwrap(); | ||
|
||
let mut ready = Ready::readable() | Ready::writable(); | ||
|
||
#[cfg(unix)] | ||
{ | ||
ready |= mio::unix::UnixReady::hup(); | ||
} | ||
|
||
let client = TcpStream::connect(&addr).unwrap(); | ||
poll.register(&client, | ||
Token(0), | ||
ready, | ||
PollOpt::edge()).unwrap(); | ||
|
||
let (socket, _) = listener.accept().unwrap(); | ||
|
||
wait!(poll, is_writable); | ||
|
||
let mut events = Events::with_capacity(16); | ||
|
||
// Polling should not have any events | ||
poll.poll(&mut events, Some(Duration::from_millis(100))).unwrap(); | ||
assert!(events.iter().next().is_none()); | ||
|
||
println!("SHUTTING DOWN"); | ||
// Now, shutdown the write half of the socket. | ||
socket.shutdown(Shutdown::Write).unwrap(); | ||
|
||
wait!(poll, is_readable); | ||
} |