io: split read/write ticks in ScheduledIo#8066
Closed
CrossEyedCat wants to merge 3 commits intotokio-rs:masterfrom
Closed
io: split read/write ticks in ScheduledIo#8066CrossEyedCat wants to merge 3 commits intotokio-rs:masterfrom
CrossEyedCat wants to merge 3 commits intotokio-rs:masterfrom
Conversation
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
| @@ -1,3 +1,9 @@ | |||
| # 1.52.2 (unreleased) | |||
Member
There was a problem hiding this comment.
I would say we don't usually update the changelog unless we decide to release just this fix
Author
There was a problem hiding this comment.
Thanks - removed the unreleased changelog section from this PR.
Maintainers add changelog lines when cutting a release. Made-with: Cursor
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, |
Member
There was a problem hiding this comment.
Is it possible to verify that this solves the problem that was reported in the issue?
Member
There was a problem hiding this comment.
If this solves the problem, then that is surprising to me. @sandersaares can you please confirm?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).