Skip to content

Commit 10e141d

Browse files
authored
io: add Ready::ERROR and report error readiness (#5781)
Add `Ready::ERROR` enabling callers to specify interest in error readiness. Some platforms use error readiness notifications to notify of other events. For example, Linux uses error to notify receipt of messages on a UDP socket's error queue. Using error readiness is platform specific. Closes #5716
1 parent 6e42c26 commit 10e141d

File tree

3 files changed

+325
-19
lines changed

3 files changed

+325
-19
lines changed

tokio/src/io/interest.rs

+168-16
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,70 @@ use crate::io::ready::Ready;
55
use std::fmt;
66
use std::ops;
77

8+
// These must be unique.
9+
// same as mio
10+
const READABLE: usize = 0b0001;
11+
const WRITABLE: usize = 0b0010;
12+
// The following are not available on all platforms.
13+
#[cfg(target_os = "freebsd")]
14+
const AIO: usize = 0b0100;
15+
#[cfg(target_os = "freebsd")]
16+
const LIO: usize = 0b1000;
17+
#[cfg(any(target_os = "linux", target_os = "android"))]
18+
const PRIORITY: usize = 0b0001_0000;
19+
// error is available on all platforms, but behavior is platform-specific
20+
// mio does not have this interest
21+
const ERROR: usize = 0b0010_0000;
22+
823
/// Readiness event interest.
924
///
1025
/// Specifies the readiness events the caller is interested in when awaiting on
1126
/// I/O resource readiness states.
1227
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
1328
#[derive(Clone, Copy, Eq, PartialEq)]
14-
pub struct Interest(mio::Interest);
29+
pub struct Interest(usize);
1530

1631
impl Interest {
1732
// The non-FreeBSD definitions in this block are active only when
1833
// building documentation.
1934
cfg_aio! {
2035
/// Interest for POSIX AIO.
2136
#[cfg(target_os = "freebsd")]
22-
pub const AIO: Interest = Interest(mio::Interest::AIO);
37+
pub const AIO: Interest = Interest(AIO);
2338

2439
/// Interest for POSIX AIO.
2540
#[cfg(not(target_os = "freebsd"))]
26-
pub const AIO: Interest = Interest(mio::Interest::READABLE);
41+
pub const AIO: Interest = Interest(READABLE);
2742

2843
/// Interest for POSIX AIO lio_listio events.
2944
#[cfg(target_os = "freebsd")]
30-
pub const LIO: Interest = Interest(mio::Interest::LIO);
45+
pub const LIO: Interest = Interest(LIO);
3146

3247
/// Interest for POSIX AIO lio_listio events.
3348
#[cfg(not(target_os = "freebsd"))]
34-
pub const LIO: Interest = Interest(mio::Interest::READABLE);
49+
pub const LIO: Interest = Interest(READABLE);
3550
}
3651

3752
/// Interest in all readable events.
3853
///
3954
/// Readable interest includes read-closed events.
40-
pub const READABLE: Interest = Interest(mio::Interest::READABLE);
55+
pub const READABLE: Interest = Interest(READABLE);
4156

4257
/// Interest in all writable events.
4358
///
4459
/// Writable interest includes write-closed events.
45-
pub const WRITABLE: Interest = Interest(mio::Interest::WRITABLE);
60+
pub const WRITABLE: Interest = Interest(WRITABLE);
61+
62+
/// Interest in error events.
63+
///
64+
/// Passes error interest to the underlying OS selector.
65+
/// Behavior is platform-specific, read your platform's documentation.
66+
pub const ERROR: Interest = Interest(ERROR);
4667

4768
/// Returns a `Interest` set representing priority completion interests.
4869
#[cfg(any(target_os = "linux", target_os = "android"))]
4970
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
50-
pub const PRIORITY: Interest = Interest(mio::Interest::PRIORITY);
71+
pub const PRIORITY: Interest = Interest(PRIORITY);
5172

5273
/// Returns true if the value includes readable interest.
5374
///
@@ -63,7 +84,7 @@ impl Interest {
6384
/// assert!(both.is_readable());
6485
/// ```
6586
pub const fn is_readable(self) -> bool {
66-
self.0.is_readable()
87+
self.0 & READABLE != 0
6788
}
6889

6990
/// Returns true if the value includes writable interest.
@@ -80,7 +101,34 @@ impl Interest {
80101
/// assert!(both.is_writable());
81102
/// ```
82103
pub const fn is_writable(self) -> bool {
83-
self.0.is_writable()
104+
self.0 & WRITABLE != 0
105+
}
106+
107+
/// Returns true if the value includes error interest.
108+
///
109+
/// # Examples
110+
///
111+
/// ```
112+
/// use tokio::io::Interest;
113+
///
114+
/// assert!(Interest::ERROR.is_error());
115+
/// assert!(!Interest::WRITABLE.is_error());
116+
///
117+
/// let combined = Interest::READABLE | Interest::ERROR;
118+
/// assert!(combined.is_error());
119+
/// ```
120+
pub const fn is_error(self) -> bool {
121+
self.0 & ERROR != 0
122+
}
123+
124+
#[cfg(target_os = "freebsd")]
125+
const fn is_aio(self) -> bool {
126+
self.0 & AIO != 0
127+
}
128+
129+
#[cfg(target_os = "freebsd")]
130+
const fn is_lio(self) -> bool {
131+
self.0 & LIO != 0
84132
}
85133

86134
/// Returns true if the value includes priority interest.
@@ -99,7 +147,7 @@ impl Interest {
99147
#[cfg(any(target_os = "linux", target_os = "android"))]
100148
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
101149
pub const fn is_priority(self) -> bool {
102-
self.0.is_priority()
150+
self.0 & PRIORITY != 0
103151
}
104152

105153
/// Add together two `Interest` values.
@@ -116,12 +164,60 @@ impl Interest {
116164
/// assert!(BOTH.is_readable());
117165
/// assert!(BOTH.is_writable());
118166
pub const fn add(self, other: Interest) -> Interest {
119-
Interest(self.0.add(other.0))
167+
Self(self.0 | other.0)
120168
}
121169

122170
// This function must be crate-private to avoid exposing a `mio` dependency.
123-
pub(crate) const fn to_mio(self) -> mio::Interest {
124-
self.0
171+
pub(crate) fn to_mio(self) -> mio::Interest {
172+
fn mio_add(wrapped: &mut Option<mio::Interest>, add: mio::Interest) {
173+
match wrapped {
174+
Some(inner) => *inner |= add,
175+
None => *wrapped = Some(add),
176+
}
177+
}
178+
179+
// mio does not allow and empty interest, so use None for empty
180+
let mut mio = None;
181+
182+
if self.is_readable() {
183+
mio_add(&mut mio, mio::Interest::READABLE);
184+
}
185+
186+
if self.is_writable() {
187+
mio_add(&mut mio, mio::Interest::WRITABLE);
188+
}
189+
190+
#[cfg(any(target_os = "linux", target_os = "android"))]
191+
if self.is_priority() {
192+
mio_add(&mut mio, mio::Interest::PRIORITY);
193+
}
194+
195+
#[cfg(target_os = "freebsd")]
196+
if self.is_aio() {
197+
mio_add(&mut mio, mio::Interest::AIO);
198+
}
199+
200+
#[cfg(target_os = "freebsd")]
201+
if self.is_lio() {
202+
mio_add(&mut mio, mio::Interest::LIO);
203+
}
204+
205+
if self.is_error() {
206+
// There is no error interest in mio, because error events are always reported.
207+
// But mio interests cannot be empty and an interest is needed just for the registeration.
208+
//
209+
// read readiness is filtered out in `Interest::mask` or `Ready::from_interest` if
210+
// the read interest was not specified by the user.
211+
mio_add(&mut mio, mio::Interest::READABLE);
212+
}
213+
214+
// the default `mio::Interest::READABLE` should never be used in practice. Either
215+
//
216+
// - at least one tokio interest with a mio counterpart was used
217+
// - only the error tokio interest was specified
218+
//
219+
// in both cases, `mio` is Some already
220+
mio.unwrap_or(mio::Interest::READABLE)
125221
}
126222

127223
pub(crate) fn mask(self) -> Ready {
@@ -130,6 +226,7 @@ impl Interest {
130226
Interest::WRITABLE => Ready::WRITABLE | Ready::WRITE_CLOSED,
131227
#[cfg(any(target_os = "linux", target_os = "android"))]
132228
Interest::PRIORITY => Ready::PRIORITY | Ready::READ_CLOSED,
229+
Interest::ERROR => Ready::ERROR,
133230
_ => Ready::EMPTY,
134231
}
135232
}
@@ -147,12 +244,67 @@ impl ops::BitOr for Interest {
147244
impl ops::BitOrAssign for Interest {
148245
#[inline]
149246
fn bitor_assign(&mut self, other: Self) {
150-
self.0 = (*self | other).0;
247+
*self = *self | other
151248
}
152249
}
153250

154251
impl fmt::Debug for Interest {
155252
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
156-
self.0.fmt(fmt)
253+
let mut separator = false;
254+
255+
if self.is_readable() {
256+
if separator {
257+
write!(fmt, " | ")?;
258+
}
259+
write!(fmt, "READABLE")?;
260+
separator = true;
261+
}
262+
263+
if self.is_writable() {
264+
if separator {
265+
write!(fmt, " | ")?;
266+
}
267+
write!(fmt, "WRITABLE")?;
268+
separator = true;
269+
}
270+
271+
#[cfg(any(target_os = "linux", target_os = "android"))]
272+
if self.is_priority() {
273+
if separator {
274+
write!(fmt, " | ")?;
275+
}
276+
write!(fmt, "PRIORITY")?;
277+
separator = true;
278+
}
279+
280+
#[cfg(target_os = "freebsd")]
281+
if self.is_aio() {
282+
if separator {
283+
write!(fmt, " | ")?;
284+
}
285+
write!(fmt, "AIO")?;
286+
separator = true;
287+
}
288+
289+
#[cfg(target_os = "freebsd")]
290+
if self.is_lio() {
291+
if separator {
292+
write!(fmt, " | ")?;
293+
}
294+
write!(fmt, "LIO")?;
295+
separator = true;
296+
}
297+
298+
if self.is_error() {
299+
if separator {
300+
write!(fmt, " | ")?;
301+
}
302+
write!(fmt, "ERROR")?;
303+
separator = true;
304+
}
305+
306+
let _ = separator;
307+
308+
Ok(())
157309
}
158310
}

tokio/src/io/ready.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const READ_CLOSED: usize = 0b0_0100;
1111
const WRITE_CLOSED: usize = 0b0_1000;
1212
#[cfg(any(target_os = "linux", target_os = "android"))]
1313
const PRIORITY: usize = 0b1_0000;
14+
const ERROR: usize = 0b10_0000;
1415

1516
/// Describes the readiness state of an I/O resources.
1617
///
@@ -40,13 +41,17 @@ impl Ready {
4041
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
4142
pub const PRIORITY: Ready = Ready(PRIORITY);
4243

44+
/// Returns a `Ready` representing error readiness.
45+
pub const ERROR: Ready = Ready(ERROR);
46+
4347
/// Returns a `Ready` representing readiness for all operations.
4448
#[cfg(any(target_os = "linux", target_os = "android"))]
45-
pub const ALL: Ready = Ready(READABLE | WRITABLE | READ_CLOSED | WRITE_CLOSED | PRIORITY);
49+
pub const ALL: Ready =
50+
Ready(READABLE | WRITABLE | READ_CLOSED | WRITE_CLOSED | ERROR | PRIORITY);
4651

4752
/// Returns a `Ready` representing readiness for all operations.
4853
#[cfg(not(any(target_os = "linux", target_os = "android")))]
49-
pub const ALL: Ready = Ready(READABLE | WRITABLE | READ_CLOSED | WRITE_CLOSED);
54+
pub const ALL: Ready = Ready(READABLE | WRITABLE | READ_CLOSED | WRITE_CLOSED | ERROR);
5055

5156
// Must remain crate-private to avoid adding a public dependency on Mio.
5257
pub(crate) fn from_mio(event: &mio::event::Event) -> Ready {
@@ -79,6 +84,10 @@ impl Ready {
7984
ready |= Ready::WRITE_CLOSED;
8085
}
8186

87+
if event.is_error() {
88+
ready |= Ready::ERROR;
89+
}
90+
8291
#[cfg(any(target_os = "linux", target_os = "android"))]
8392
{
8493
if event.is_priority() {
@@ -182,6 +191,21 @@ impl Ready {
182191
self.contains(Ready::PRIORITY)
183192
}
184193

194+
/// Returns `true` if the value includes error `readiness`.
195+
///
196+
/// # Examples
197+
///
198+
/// ```
199+
/// use tokio::io::Ready;
200+
///
201+
/// assert!(!Ready::EMPTY.is_error());
202+
/// assert!(!Ready::WRITABLE.is_error());
203+
/// assert!(Ready::ERROR.is_error());
204+
/// ```
205+
pub fn is_error(self) -> bool {
206+
self.contains(Ready::ERROR)
207+
}
208+
185209
/// Returns true if `self` is a superset of `other`.
186210
///
187211
/// `other` may represent more than one readiness operations, in which case
@@ -230,6 +254,10 @@ impl Ready {
230254
ready |= Ready::READ_CLOSED;
231255
}
232256

257+
if interest.is_error() {
258+
ready |= Ready::ERROR;
259+
}
260+
233261
ready
234262
}
235263

@@ -283,7 +311,8 @@ impl fmt::Debug for Ready {
283311
fmt.field("is_readable", &self.is_readable())
284312
.field("is_writable", &self.is_writable())
285313
.field("is_read_closed", &self.is_read_closed())
286-
.field("is_write_closed", &self.is_write_closed());
314+
.field("is_write_closed", &self.is_write_closed())
315+
.field("is_error", &self.is_error());
287316

288317
#[cfg(any(target_os = "linux", target_os = "android"))]
289318
fmt.field("is_priority", &self.is_priority());

0 commit comments

Comments
 (0)