diff --git a/src/epoll.rs b/src/epoll.rs index e24862d..e271c22 100644 --- a/src/epoll.rs +++ b/src/epoll.rs @@ -222,6 +222,7 @@ impl Poller { PollMode::Oneshot => libc::EPOLLONESHOT, PollMode::Level => 0, PollMode::Edge => libc::EPOLLET, + PollMode::EdgeOneshot => libc::EPOLLONESHOT | libc::EPOLLET, }; if ev.readable { flags |= read_flags(); diff --git a/src/iocp/mod.rs b/src/iocp/mod.rs index cc618b1..39eb4a9 100644 --- a/src/iocp/mod.rs +++ b/src/iocp/mod.rs @@ -146,7 +146,7 @@ impl Poller { ); // We don't support edge-triggered events. - if matches!(mode, PollMode::Edge) { + if matches!(mode, PollMode::Edge | PollMode::EdgeOneshot) { return Err(io::Error::new( io::ErrorKind::InvalidInput, "edge-triggered events are not supported", @@ -206,7 +206,7 @@ impl Poller { ); // We don't support edge-triggered events. - if matches!(mode, PollMode::Edge) { + if matches!(mode, PollMode::Edge | PollMode::EdgeOneshot) { return Err(io::Error::new( io::ErrorKind::InvalidInput, "edge-triggered events are not supported", diff --git a/src/kqueue.rs b/src/kqueue.rs index bc127d0..fee970a 100644 --- a/src/kqueue.rs +++ b/src/kqueue.rs @@ -251,6 +251,7 @@ pub(crate) fn mode_to_flags(mode: PollMode) -> FilterFlags { PollMode::Oneshot => libc::EV_ONESHOT, PollMode::Level => 0, PollMode::Edge => libc::EV_CLEAR, + PollMode::EdgeOneshot => libc::EV_ONESHOT | libc::EV_CLEAR, } } diff --git a/src/lib.rs b/src/lib.rs index 6cbce52..8b68b66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,6 +168,16 @@ pub enum PollMode { /// this mode in an unsupported operating system will raise an error. You can check if /// the operating system supports this mode by calling `Poller::supports_edge`. Edge, + + /// Poll in both edge-triggered and oneshot mode. + /// + /// This mode is similar to the `Oneshot` mode, but it will only deliver one event per new + /// event. + /// + /// Not all operating system support this mode. Trying to register a file descriptor with + /// this mode in an unsupported operating system will raise an error. You can check if + /// the operating system supports this mode by calling `Poller::supports_edge`. + EdgeOneshot, } impl Event { diff --git a/src/poll.rs b/src/poll.rs index 22e550a..4e739eb 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -416,7 +416,7 @@ fn cvt_mode_as_remove(mode: PollMode) -> io::Result { match mode { PollMode::Oneshot => Ok(true), PollMode::Level => Ok(false), - PollMode::Edge => Err(crate::unsupported_error( + _ => Err(crate::unsupported_error( "edge-triggered I/O events are not supported in poll()", )), } diff --git a/src/port.rs b/src/port.rs index 778be73..b4cb666 100644 --- a/src/port.rs +++ b/src/port.rs @@ -53,7 +53,7 @@ impl Poller { flags |= libc::POLLOUT; } - if let PollMode::Edge | PollMode::Level = mode { + if mode != PollMode::Oneshot { return Err(crate::unsupported_error( "this kind of event is not supported with event ports", )); diff --git a/tests/other_modes.rs b/tests/other_modes.rs index e499d70..fbe8779 100644 --- a/tests/other_modes.rs +++ b/tests/other_modes.rs @@ -162,6 +162,81 @@ fn edge_triggered() { assert_eq!(events, [Event::readable(reader_token)]); } +#[test] +fn edge_oneshot_triggered() { + // Create our streams. + let (mut reader, mut writer) = tcp_pair().unwrap(); + let reader_token = 1; + + // Create our poller and register our streams. + let poller = Poller::new().unwrap(); + if poller + .add_with_mode( + &reader, + Event::readable(reader_token), + PollMode::EdgeOneshot, + ) + .is_err() + { + // Only panic if we're on a platform that should support level mode. + cfg_if::cfg_if! { + if #[cfg(all( + any( + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "dragonfly" + ), + not(polling_test_poll_backend) + ))] { + panic!("Edge mode should be supported on this platform"); + } else { + return; + } + } + } + + // Write some data to the writer. + let data = [1, 2, 3, 4, 5]; + writer.write_all(&data).unwrap(); + + // A "readable" notification should be delivered. + let mut events = Vec::new(); + poller + .wait(&mut events, Some(Duration::from_secs(10))) + .unwrap(); + + assert_eq!(events, [Event::readable(reader_token)]); + + // If we read some of the data, the notification should not still be available. + reader.read_exact(&mut [0; 3]).unwrap(); + events.clear(); + poller + .wait(&mut events, Some(Duration::from_secs(0))) + .unwrap(); + assert_eq!(events, []); + + // If we modify to re-enable the notification, it should be delivered. + poller + .modify_with_mode( + &reader, + Event::readable(reader_token), + PollMode::EdgeOneshot, + ) + .unwrap(); + events.clear(); + poller + .wait(&mut events, Some(Duration::from_secs(0))) + .unwrap(); + assert_eq!(events, [Event::readable(reader_token)]); +} + fn tcp_pair() -> io::Result<(TcpStream, TcpStream)> { let listener = TcpListener::bind("127.0.0.1:0")?; let a = TcpStream::connect(listener.local_addr()?)?;