From 192ef9a1c10f68e4f0303f18bed191303813f1f0 Mon Sep 17 00:00:00 2001 From: cui fliter Date: Thu, 3 Sep 2026 21:10:43 +0800 Subject: [PATCH] io: preserve the full ScheduledIo tick in readiness events (#8331) --- tokio/src/runtime/io/driver.rs | 4 +-- tokio/src/runtime/io/scheduled_io.rs | 38 +++++++++++++++++++++------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/tokio/src/runtime/io/driver.rs b/tokio/src/runtime/io/driver.rs index 5bcdecbf6..cdd841894 100644 --- a/tokio/src/runtime/io/driver.rs +++ b/tokio/src/runtime/io/driver.rs @@ -72,7 +72,7 @@ pub(crate) struct Handle { #[derive(Debug)] pub(crate) struct ReadyEvent { - pub(super) tick: u8, + pub(super) tick: u16, pub(crate) ready: Ready, pub(super) is_shutdown: bool, } @@ -97,7 +97,7 @@ pub(super) enum Direction { pub(super) enum Tick { Set, - Clear(u8), + Clear(u16), } const TOKEN_WAKEUP: mio::Token = mio::Token(0); diff --git a/tokio/src/runtime/io/scheduled_io.rs b/tokio/src/runtime/io/scheduled_io.rs index 32312e464..6c567664b 100644 --- a/tokio/src/runtime/io/scheduled_io.rs +++ b/tokio/src/runtime/io/scheduled_io.rs @@ -214,8 +214,8 @@ impl ScheduledIo { let new_tick = match tick_op { // Trying to clear readiness with an old event! - Tick::Clear(t) if tick as u8 != t => return None, - Tick::Clear(t) => t as usize, + Tick::Clear(t) if tick != usize::from(t) => return None, + Tick::Clear(t) => usize::from(t), Tick::Set => tick.wrapping_add(1) % MAX_TICK, }; let ready = Ready::from_usize(READINESS.unpack(curr)); @@ -289,7 +289,7 @@ impl ScheduledIo { let curr = self.readiness.load(Acquire); ReadyEvent { - tick: TICK.unpack(curr) as u8, + tick: TICK.unpack(curr) as u16, ready: interest.mask() & Ready::from_usize(READINESS.unpack(curr)), is_shutdown: SHUTDOWN.unpack(curr) != 0, } @@ -332,7 +332,7 @@ impl ScheduledIo { let is_shutdown = SHUTDOWN.unpack(curr) != 0; if is_shutdown { Poll::Ready(ReadyEvent { - tick: TICK.unpack(curr) as u8, + tick: TICK.unpack(curr) as u16, ready: direction.mask(), is_shutdown, }) @@ -340,14 +340,14 @@ impl ScheduledIo { Poll::Pending } else { Poll::Ready(ReadyEvent { - tick: TICK.unpack(curr) as u8, + tick: TICK.unpack(curr) as u16, ready, is_shutdown, }) } } else { Poll::Ready(ReadyEvent { - tick: TICK.unpack(curr) as u8, + tick: TICK.unpack(curr) as u16, ready, is_shutdown, }) @@ -452,7 +452,7 @@ impl Future for Readiness<'_> { if !ready.is_empty() || is_shutdown { // Currently ready! - let tick = TICK.unpack(curr) as u8; + let tick = TICK.unpack(curr) as u16; *state = State::Done; return Poll::Ready(ReadyEvent { tick, @@ -476,7 +476,7 @@ impl Future for Readiness<'_> { if !ready.is_empty() || is_shutdown { // Currently ready! - let tick = TICK.unpack(curr) as u8; + let tick = TICK.unpack(curr) as u16; *state = State::Done; return Poll::Ready(ReadyEvent { tick, @@ -539,7 +539,7 @@ impl Future for Readiness<'_> { // The returned tick might be newer than the event // which notified our waker. This is ok because the future // still didn't return `Poll::Ready`. - let tick = TICK.unpack(curr) as u8; + let tick = TICK.unpack(curr) as u16; // Safety: We don't need to acquire the lock here because // 1. `State::Done`` means `waiter` is no longer shared, @@ -578,3 +578,23 @@ impl Drop for Readiness<'_> { unsafe impl Send for Readiness<'_> {} unsafe impl Sync for Readiness<'_> {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stale_event_does_not_clear_readiness_after_u8_wraparound() { + let io = ScheduledIo::default(); + io.set_readiness(Tick::Set, |curr| curr | Ready::READABLE); + let event = io.ready_event(Interest::READABLE); + + for _ in 0..=u8::MAX { + io.set_readiness(Tick::Set, |curr| curr | Ready::READABLE); + } + + io.clear_readiness(event); + + assert!(io.ready_event(Interest::READABLE).ready.is_readable()); + } +}