Skip to content

io: split read/write ticks in ScheduledIo#8066

Closed
CrossEyedCat wants to merge 3 commits intotokio-rs:masterfrom
CrossEyedCat:fix/io-driver-split-read-write-ticks
Closed

io: split read/write ticks in ScheduledIo#8066
CrossEyedCat wants to merge 3 commits intotokio-rs:masterfrom
CrossEyedCat:fix/io-driver-split-read-write-ticks

Conversation

@CrossEyedCat
Copy link
Copy Markdown

@CrossEyedCat CrossEyedCat commented Apr 18, 2026

Separate read_tick and write_tick so write-only mio notifications do not invalidate clear_readiness for the read path. Fixes livelock when the reactor advances the shared tick between poll_read_ready and clear_readiness.

Fixes #8054

Motivation

ScheduledIo used a single readiness generation counter (tick). The I/O driver incremented it on every mio event via set_readiness(Tick::Set, …), including write-only notifications.

A task can observe read readiness at tick T, then perform a read that returns WouldBlock and call clear_readiness with that snapshot. If another thread handles the reactor and processes a writable-only event before the clear runs, the shared tick becomes T+1. clear_readiness uses Tick::Clear(T), sees a tick mismatch, and does not clear the read bits. Read readiness stays set, so poll_read_ready keeps returning ready while read keeps returning WouldBlock — a busy-loop / livelock.

This was reported on Windows (e.g. Hyper HTTP/2 repro in #8054), but the tick logic is platform-agnostic.

Solution

Replace the single 15-bit tick with 7-bit read_tick and 7-bit write_tick in the packed AtomicUsize (16 + 7 + 7 + 1 bit total so the layout still fits in usize on 32-bit targets, including wasm32).

On each mio event, merge_readiness_from_driver advances read_tick only if the event has read-side bits (readable / read closed / error [+ priority on Linux]), and write_tick only if it has write-side bits (writable / write closed).

ReadyEvent carries both ticks; clear_readiness checks only the tick(s) that correspond to the bits being cleared (via read/write tick masks).

Separate read_tick and write_tick so write-only mio notifications do not
invalidate clear_readiness for the read path. Fixes livelock when the
reactor advances the shared tick between poll_read_ready and clear_readiness.

Fixes tokio-rs#8054

Made-with: Cursor
- Format Tick enum per rustfmt
- Link merge_readiness_from_driver docs to Registration::clear_readiness
- Use 7-bit read/write ticks so packed state fits usize on 32-bit targets

Made-with: Cursor
@CrossEyedCat CrossEyedCat reopened this Apr 18, 2026
@mattiapitossi mattiapitossi added the A-tokio Area: The main tokio crate label Apr 18, 2026
Comment thread tokio/CHANGELOG.md Outdated
@@ -1,3 +1,9 @@
# 1.52.2 (unreleased)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say we don't usually update the changelog unless we decide to release just this fix

Copy link
Copy Markdown
Author

@CrossEyedCat CrossEyedCat Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - removed the unreleased changelog section from this PR.

Maintainers add changelog lines when cutting a release.

Made-with: Cursor
@mattiapitossi mattiapitossi added the M-runtime Module: tokio/runtime label Apr 19, 2026
Comment on lines +75 to +78
/// Generation counter for read-side readiness (readable, read closed, error, …).
pub(super) read_tick: u8,
/// Generation counter for write-side readiness (writable, write closed).
pub(super) write_tick: u8,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to verify that this solves the problem that was reported in the issue?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this solves the problem, then that is surprising to me. @sandersaares can you please confirm?

@mattiapitossi mattiapitossi added the S-waiting-on-author Status: awaiting some action (such as code changes) from the PR or issue author. label Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-tokio Area: The main tokio crate M-runtime Module: tokio/runtime S-waiting-on-author Status: awaiting some action (such as code changes) from the PR or issue author.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Livelock in PollEvented::poll_read() which sometimes does not transition into Pending state despite nothing to read

3 participants