Skip to content

Commit 7b24b22

Browse files
authored
io: support PRIORITY epoll events (#5566)
Add support for epoll priority events. The commit adds `Interest::PRIORITY`, `ready`, and `ready_mut` functions to `AsyncFd`. Closes #4885
1 parent 779b9c1 commit 7b24b22

File tree

7 files changed

+595
-25
lines changed

7 files changed

+595
-25
lines changed

tokio/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pin-project-lite = "0.2.0"
103103

104104
# Everything else is optional...
105105
bytes = { version = "1.0.0", optional = true }
106-
mio = { version = "0.8.4", optional = true, default-features = false }
106+
mio = { version = "0.8.6", optional = true, default-features = false }
107107
num_cpus = { version = "1.8.0", optional = true }
108108
parking_lot = { version = "0.12.0", optional = true }
109109

tokio/src/io/async_fd.rs

+416-20
Large diffs are not rendered by default.

tokio/src/io/interest.rs

+26
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ impl Interest {
4444
/// Writable interest includes write-closed events.
4545
pub const WRITABLE: Interest = Interest(mio::Interest::WRITABLE);
4646

47+
/// Returns a `Interest` set representing priority completion interests.
48+
#[cfg(any(target_os = "linux", target_os = "android"))]
49+
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
50+
pub const PRIORITY: Interest = Interest(mio::Interest::PRIORITY);
51+
4752
/// Returns true if the value includes readable interest.
4853
///
4954
/// # Examples
@@ -78,6 +83,25 @@ impl Interest {
7883
self.0.is_writable()
7984
}
8085

86+
/// Returns true if the value includes priority interest.
87+
///
88+
/// # Examples
89+
///
90+
/// ```
91+
/// use tokio::io::Interest;
92+
///
93+
/// assert!(!Interest::READABLE.is_priority());
94+
/// assert!(Interest::PRIORITY.is_priority());
95+
///
96+
/// let both = Interest::READABLE | Interest::PRIORITY;
97+
/// assert!(both.is_priority());
98+
/// ```
99+
#[cfg(any(target_os = "linux", target_os = "android"))]
100+
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
101+
pub const fn is_priority(self) -> bool {
102+
self.0.is_priority()
103+
}
104+
81105
/// Add together two `Interest` values.
82106
///
83107
/// This function works from a `const` context.
@@ -104,6 +128,8 @@ impl Interest {
104128
match self {
105129
Interest::READABLE => Ready::READABLE | Ready::READ_CLOSED,
106130
Interest::WRITABLE => Ready::WRITABLE | Ready::WRITE_CLOSED,
131+
#[cfg(any(target_os = "linux", target_os = "android"))]
132+
Interest::PRIORITY => Ready::PRIORITY | Ready::READ_CLOSED,
107133
_ => Ready::EMPTY,
108134
}
109135
}

tokio/src/io/poll_evented.rs

+3
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ cfg_io_driver! {
7373
impl<E: Source> PollEvented<E> {
7474
/// Creates a new `PollEvented` associated with the default reactor.
7575
///
76+
/// The returned `PollEvented` has readable and writable interests. For more control, use
77+
/// [`Self::new_with_interest`].
78+
///
7679
/// # Panics
7780
///
7881
/// This function panics if thread-local runtime is not set.

tokio/src/io/ready.rs

+51-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const READABLE: usize = 0b0_01;
77
const WRITABLE: usize = 0b0_10;
88
const READ_CLOSED: usize = 0b0_0100;
99
const WRITE_CLOSED: usize = 0b0_1000;
10+
#[cfg(any(target_os = "linux", target_os = "android"))]
11+
const PRIORITY: usize = 0b1_0000;
1012

1113
/// Describes the readiness state of an I/O resources.
1214
///
@@ -31,7 +33,17 @@ impl Ready {
3133
/// Returns a `Ready` representing write closed readiness.
3234
pub const WRITE_CLOSED: Ready = Ready(WRITE_CLOSED);
3335

36+
/// Returns a `Ready` representing priority readiness.
37+
#[cfg(any(target_os = "linux", target_os = "android"))]
38+
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
39+
pub const PRIORITY: Ready = Ready(PRIORITY);
40+
41+
/// Returns a `Ready` representing readiness for all operations.
42+
#[cfg(any(target_os = "linux", target_os = "android"))]
43+
pub const ALL: Ready = Ready(READABLE | WRITABLE | READ_CLOSED | WRITE_CLOSED | PRIORITY);
44+
3445
/// Returns a `Ready` representing readiness for all operations.
46+
#[cfg(not(any(target_os = "linux", target_os = "android")))]
3547
pub const ALL: Ready = Ready(READABLE | WRITABLE | READ_CLOSED | WRITE_CLOSED);
3648

3749
// Must remain crate-private to avoid adding a public dependency on Mio.
@@ -65,6 +77,13 @@ impl Ready {
6577
ready |= Ready::WRITE_CLOSED;
6678
}
6779

80+
#[cfg(any(target_os = "linux", target_os = "android"))]
81+
{
82+
if event.is_priority() {
83+
ready |= Ready::PRIORITY;
84+
}
85+
}
86+
6887
ready
6988
}
7089

@@ -144,6 +163,23 @@ impl Ready {
144163
self.contains(Ready::WRITE_CLOSED)
145164
}
146165

166+
/// Returns `true` if the value includes priority `readiness`.
167+
///
168+
/// # Examples
169+
///
170+
/// ```
171+
/// use tokio::io::Ready;
172+
///
173+
/// assert!(!Ready::EMPTY.is_priority());
174+
/// assert!(!Ready::WRITABLE.is_priority());
175+
/// assert!(Ready::PRIORITY.is_priority());
176+
/// ```
177+
#[cfg(any(target_os = "linux", target_os = "android"))]
178+
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
179+
pub fn is_priority(self) -> bool {
180+
self.contains(Ready::PRIORITY)
181+
}
182+
147183
/// Returns true if `self` is a superset of `other`.
148184
///
149185
/// `other` may represent more than one readiness operations, in which case
@@ -191,6 +227,12 @@ cfg_io_readiness! {
191227
ready |= Ready::WRITE_CLOSED;
192228
}
193229

230+
#[cfg(any(target_os = "linux", target_os = "android"))]
231+
if interest.is_priority() {
232+
ready |= Ready::PRIORITY;
233+
ready |= Ready::READ_CLOSED;
234+
}
235+
194236
ready
195237
}
196238

@@ -240,11 +282,16 @@ impl ops::Sub<Ready> for Ready {
240282

241283
impl fmt::Debug for Ready {
242284
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
243-
fmt.debug_struct("Ready")
244-
.field("is_readable", &self.is_readable())
285+
let mut fmt = fmt.debug_struct("Ready");
286+
287+
fmt.field("is_readable", &self.is_readable())
245288
.field("is_writable", &self.is_writable())
246289
.field("is_read_closed", &self.is_read_closed())
247-
.field("is_write_closed", &self.is_write_closed())
248-
.finish()
290+
.field("is_write_closed", &self.is_write_closed());
291+
292+
#[cfg(any(target_os = "linux", target_os = "android"))]
293+
fmt.field("is_priority", &self.is_priority());
294+
295+
fmt.finish()
249296
}
250297
}

tokio/src/runtime/io/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ pub(crate) struct ReadyEvent {
6363
is_shutdown: bool,
6464
}
6565

66+
cfg_net_unix!(
67+
impl ReadyEvent {
68+
pub(crate) fn with_ready(&self, ready: Ready) -> Self {
69+
Self {
70+
ready,
71+
tick: self.tick,
72+
is_shutdown: self.is_shutdown,
73+
}
74+
}
75+
}
76+
);
77+
6678
struct IoDispatcher {
6779
allocator: slab::Allocator<ScheduledIo>,
6880
is_shutdown: bool,

tokio/tests/io_async_fd.rs

+86
Original file line numberDiff line numberDiff line change
@@ -599,3 +599,89 @@ fn driver_shutdown_wakes_poll_race() {
599599
assert_err!(futures::executor::block_on(poll_writable(&afd_a)));
600600
}
601601
}
602+
603+
#[tokio::test]
604+
#[cfg(any(target_os = "linux", target_os = "android"))]
605+
async fn priority_event_on_oob_data() {
606+
use std::net::SocketAddr;
607+
608+
use tokio::io::Interest;
609+
610+
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
611+
612+
let listener = std::net::TcpListener::bind(addr).unwrap();
613+
let addr = listener.local_addr().unwrap();
614+
615+
let client = std::net::TcpStream::connect(addr).unwrap();
616+
let client = AsyncFd::with_interest(client, Interest::PRIORITY).unwrap();
617+
618+
let (stream, _) = listener.accept().unwrap();
619+
620+
// Sending out of band data should trigger priority event.
621+
send_oob_data(&stream, b"hello").unwrap();
622+
623+
let _ = client.ready(Interest::PRIORITY).await.unwrap();
624+
}
625+
626+
#[cfg(any(target_os = "linux", target_os = "android"))]
627+
fn send_oob_data<S: AsRawFd>(stream: &S, data: &[u8]) -> io::Result<usize> {
628+
unsafe {
629+
let res = libc::send(
630+
stream.as_raw_fd(),
631+
data.as_ptr().cast(),
632+
data.len(),
633+
libc::MSG_OOB,
634+
);
635+
if res == -1 {
636+
Err(io::Error::last_os_error())
637+
} else {
638+
Ok(res as usize)
639+
}
640+
}
641+
}
642+
643+
#[tokio::test]
644+
async fn clear_ready_matching_clears_ready() {
645+
use tokio::io::{Interest, Ready};
646+
647+
let (a, mut b) = socketpair();
648+
649+
let afd_a = AsyncFd::new(a).unwrap();
650+
b.write_all(b"0").unwrap();
651+
652+
let mut guard = afd_a
653+
.ready(Interest::READABLE | Interest::WRITABLE)
654+
.await
655+
.unwrap();
656+
657+
assert_eq!(guard.ready(), Ready::READABLE | Ready::WRITABLE);
658+
659+
guard.clear_ready_matching(Ready::READABLE);
660+
assert_eq!(guard.ready(), Ready::WRITABLE);
661+
662+
guard.clear_ready_matching(Ready::WRITABLE);
663+
assert_eq!(guard.ready(), Ready::EMPTY);
664+
}
665+
666+
#[tokio::test]
667+
async fn clear_ready_matching_clears_ready_mut() {
668+
use tokio::io::{Interest, Ready};
669+
670+
let (a, mut b) = socketpair();
671+
672+
let mut afd_a = AsyncFd::new(a).unwrap();
673+
b.write_all(b"0").unwrap();
674+
675+
let mut guard = afd_a
676+
.ready_mut(Interest::READABLE | Interest::WRITABLE)
677+
.await
678+
.unwrap();
679+
680+
assert_eq!(guard.ready(), Ready::READABLE | Ready::WRITABLE);
681+
682+
guard.clear_ready_matching(Ready::READABLE);
683+
assert_eq!(guard.ready(), Ready::WRITABLE);
684+
685+
guard.clear_ready_matching(Ready::WRITABLE);
686+
assert_eq!(guard.ready(), Ready::EMPTY);
687+
}

0 commit comments

Comments
 (0)