Skip to content

Commit

Permalink
Add edge/oneshot combination mode
Browse files Browse the repository at this point in the history
  • Loading branch information
notgull committed Mar 13, 2023
1 parent e10c7e8 commit b235c7b
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/epoll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions src/iocp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/kqueue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ fn cvt_mode_as_remove(mode: PollMode) -> io::Result<bool> {
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()",
)),
}
Expand Down
2 changes: 1 addition & 1 deletion src/port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
));
Expand Down
75 changes: 75 additions & 0 deletions tests/other_modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?)?;
Expand Down

0 comments on commit b235c7b

Please sign in to comment.