fix(http1): fix rare missed write wakeup on connections v2 - #3988
Merged
Conversation
seanmonstar
approved these changes
Apr 22, 2026
seanmonstar
pushed a commit
that referenced
this pull request
Aug 7, 2026
#4143) `poll_loop`'s main path always calls `poll_flush` after `poll_write`. The "wants_write_again" re-check added in #3988 calls `poll_write` a second time and returns straight out of the loop when it pends, skipping that flush. That second write can buffer bytes before it pends. When a response body reaches end-of-stream between the two write polls, `end_body()` buffers the end of the message and the write then pends on the *next* message (`poll_msg`). Returning there strands the terminating chunk in the write buffer: the wake-ups the connection is left waiting on are for reads, so nothing flushes it. The peer receives the body but never the terminator and waits until it gives up, at which point the connection reports `IncompleteMessage` from `mid_message_detect_eof`. Observed on a server streaming a chunked body fed from another thread, at roughly one connection in 600k. hyper's own trace shows the divergence: healthy: buf.len=24, buf.len=5, flushed 29 bytes stalled: buf.len=24, flushed 24 bytes, buf.len=5, <nothing> Flush what the re-check buffered before yielding. Guard the flush on there being buffered bytes so the call pattern is otherwise unchanged. Add a test that drives the interleaving deterministically: a body that yields one data frame, then pends, then ends the stream on the very next poll, all within a single `poll_loop` iteration.
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.
This is a follow up on #3952 which was reverted by #3977 after reports in #3976.
The conceptual problem this is trying to fix is that the dispatch loop may poll the Write side and determine readiness (either
rt::Write::poll_writeorrt::Write::poll_flushare ready), but will returnPoll::Pendingto the executor and stall; if neither write nor flush are pending, then nothing will wake us.The initial fix did not treat the common "unbuffered" implementation where poll_flush always returns
Poll::Ready; I question the correctness of such a response, as my understanding of flush would be that the "the bytes are flushed out and we may write again", but I can't argue with what the current convention is.For testing, in addition to the contrived hyper Stream that triggers the stall from the previous PR (ie:
ReadyOnPollStream), I've also added anUnbufferedStreamto simulate the implementation where flush always returnsPoll::Ready. The test fails as expected with #3952.The additional fix in this PR is to check again if the connection is writable if and only if we are proceeding to loop because we think the connection may be writable (ie: write or flush have returned Ready). If the write is indeed still pending, we can safely return from the loop without scheduling a waker because we know the writer's waker will schedule us to proceed with the write half. This additional check resolves the hot looping issue for unbuffered streams in #3952.