Repository: tokio-rs/mio (rust)

Issue:
A way to add EPOLLPRI event type to the epoll selector 
While working with Video4Linux2 I am searching for a way to (a)wait `POLLPRI` events coming from the kernel.
I do see, that an `Event` can be checked if it `is_prioprity`:
https://github.com/tokio-rs/mio/blob/fa4e4b3c58af76909d047abd81d3eaca1d8c5736/src/sys/unix/selector/epoll.rs#L227-L229
But I wonder how to register at the `Selector` in a way that it receives this type of events?
Looking at https://github.com/tokio-rs/mio/blob/fa4e4b3c58af76909d047abd81d3eaca1d8c5736/src/sys/unix/selector/epoll.rs#L114-L123 it seems, that some kind of  PRIORITY `Interest` would be needed, but it is not there: https://github.com/tokio-rs/mio/blob/fa4e4b3c58af76909d047abd81d3eaca1d8c5736/src/interest.rs#L19-L34
I searched a bit through the history and found something similar was already there:
https://github.com/tokio-rs/mio/blob/434405a66c66c1e2b15a07388dd0ed4edcc7e3c8/src/event_imp.rs#L613-L622
But it is gone and I don't know why.
So my questions:
* Why is the `is_priority` function there? It is of no use at the moment, right?
* What happened to the old `Ready:PRIORITY`? Is there some reason why this got removed or was it a mistake?
* What would be a good (upstreamable) way to implement the support for `POLLPRI` type events?

Pull request description:
Add Interest::PRIORITY
To trigger Event::is_priority on Linux and Android.

In the future we might want to include EV_OOBAND for kqueue, but that seems to be Apple only (FreeBSD and OpenBSD don't implement it at least).

Closes #1645

Does this solve the problem @poeschel, @Urist-McGit?

Patch:
diff --git a/src/interest.rs b/src/interest.rs
index 1424786..50d1bf0 100644
--- a/src/interest.rs
+++ b/src/interest.rs
@@ -20,18 +20,9 @@ pub struct Interest(NonZeroU8);
 const READABLE: u8 = 0b0001;
 const WRITABLE: u8 = 0b0010;
 // The following are not available on all platforms.
-#[cfg_attr(
-    not(any(
-        target_os = "dragonfly",
-        target_os = "freebsd",
-        target_os = "ios",
-        target_os = "macos",
-    )),
-    allow(dead_code)
-)]
 const AIO: u8 = 0b0100;
-#[cfg_attr(not(target_os = "freebsd"), allow(dead_code))]
 const LIO: u8 = 0b1000;
+const PRIORITY: u8 = 0b10000;
 
 impl Interest {
     /// Returns a `Interest` set representing readable interests.
@@ -53,6 +44,10 @@ impl Interest {
     #[cfg(target_os = "freebsd")]
     pub const LIO: Interest = Interest(unsafe { NonZeroU8::new_unchecked(LIO) });
 
+    /// Returns a `Interest` set representing priority completion interests.
+    #[cfg(any(target_os = "linux", target_os = "android"))]
+    pub const PRIORITY: Interest = Interest(unsafe { NonZeroU8::new_unchecked(PRIORITY) });
+
     /// Add together two `Interest`.
     ///
     /// This does the same thing as the `BitOr` implementation, but is a
@@ -104,15 +99,20 @@ impl Interest {
         (self.0.get() & WRITABLE) != 0
     }
 
-    /// Returns true if `Interest` contains AIO readiness
+    /// Returns true if `Interest` contains AIO readiness.
     pub const fn is_aio(self) -> bool {
         (self.0.get() & AIO) != 0
     }
 
-    /// Returns true if `Interest` contains LIO readiness
+    /// Returns true if `Interest` contains LIO readiness.
     pub const fn is_lio(self) -> bool {
         (self.0.get() & LIO) != 0
     }
+
+    /// Returns true if `Interest` contains priority readiness.
+    pub const fn is_priority(self) -> bool {
+        (self.0.get() & PRIORITY) != 0
+    }
 }
 
 impl ops::BitOr for Interest {
@@ -173,6 +173,16 @@ impl fmt::Debug for Interest {
                 one = true
             }
         }
+        #[cfg(any(target_os = "linux", target_os = "android"))]
+        {
+            if self.is_priority() {
+                if one {
+                    write!(fmt, " | ")?
+                }
+                write!(fmt, "PRIORITY")?;
+                one = true
+            }
+        }
         debug_assert!(one, "printing empty interests");
         Ok(())
     }
diff --git a/src/sys/unix/selector/epoll.rs b/src/sys/unix/selector/epoll.rs
index 8bad383..1809a2b 100644
--- a/src/sys/unix/selector/epoll.rs
+++ b/src/sys/unix/selector/epoll.rs
@@ -1,6 +1,6 @@
 use crate::{Interest, Token};
 
-use libc::{EPOLLET, EPOLLIN, EPOLLOUT, EPOLLRDHUP};
+use libc::{EPOLLET, EPOLLIN, EPOLLOUT, EPOLLPRI, EPOLLRDHUP};
 use log::error;
 use std::os::unix::io::{AsRawFd, RawFd};
 #[cfg(debug_assertions)]
@@ -177,6 +177,10 @@ fn interests_to_epoll(interests: Interest) -> u32 {
         kind |= EPOLLOUT;
     }
 
+    if interests.is_priority() {
+        kind |= EPOLLPRI;
+    }
+
     kind as u32
 }
 


Test patch:
diff --git a/tests/tcp_stream.rs b/tests/tcp_stream.rs
index a2288a1..26a5b47 100644
--- a/tests/tcp_stream.rs
+++ b/tests/tcp_stream.rs
@@ -793,3 +793,52 @@ fn hup_event_on_disconnect() {
         vec![ExpectEvent::new(Token(1), Interest::READABLE)],
     );
 }
+
+#[test]
+#[cfg(any(target_os = "linux", target_os = "android"))]
+fn priority_event_on_oob_data() {
+    let (mut poll, mut events) = init_with_poll();
+    let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
+
+    let listener = std::net::TcpListener::bind(addr).unwrap();
+    let addr = listener.local_addr().unwrap();
+
+    let mut client = TcpStream::connect(addr).unwrap();
+    poll.registry()
+        .register(
+            &mut client,
+            Token(0),
+            Interest::READABLE | Interest::PRIORITY,
+        )
+        .unwrap();
+
+    let (stream, _) = listener.accept().unwrap();
+
+    // Sending out of bound data should trigger priority event.
+    send_oob_data(&stream, DATA1).unwrap();
+    expect_events(
+        &mut poll,
+        &mut events,
+        vec![ExpectEvent::new(
+            Token(0),
+            Readiness::READABLE | Readiness::PRIORITY,
+        )],
+    );
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+fn send_oob_data<S: AsRawFd>(stream: &S, data: &[u8]) -> io::Result<usize> {
+    unsafe {
+        let res = libc::send(
+            stream.as_raw_fd(),
+            data.as_ptr().cast(),
+            data.len(),
+            libc::MSG_OOB,
+        );
+        if res == -1 {
+            Err(io::Error::last_os_error())
+        } else {
+            Ok(res as usize)
+        }
+    }
+}