Fix h2 client deadlock on request bodies when the server responds early; emit content-length#1326
Merged
Conversation
…the request body A body writer parked in _reserve_send_window! only woke on WINDOW_UPDATE, connection error, or connection close. A peer that completed its response (END_STREAM), reset the stream, or a canceled request left it waiting for window credit that would never arrive. - Track send-closed streams in a state_lock-guarded set on H2Connection; mark on END_STREAM/RST_STREAM/cancel and notify window_condition. - _reserve_send_window! returns 0 for a send-closed stream; the writer abandons the upload, sends RST_STREAM(NO_ERROR) for a benign early response, and the roundtrip returns the peer's response. - RST_STREAM(NO_ERROR) after a complete response is benign per RFC 9113 §8.1; other resets surface as H2StreamResetError without failing the shared connection. - Emit content-length on h2 requests when the body size is known, matching the HTTP/1.1 wire path. Fixes JuliaWeb#1325 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1326 +/- ##
=======================================
Coverage 87.99% 88.00%
=======================================
Files 30 30
Lines 11807 11841 +34
=======================================
+ Hits 10390 10421 +31
- Misses 1417 1420 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
quinnj
approved these changes
Jul 3, 2026
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.
Fixes #1325.
Issue 1 — flow-control deadlock. A body writer parked in
_reserve_send_window!only woke on WINDOW_UPDATE, connection error, or connection close. A peer that completed its response before consuming the request body (RFC 9113 §8.1), reset the stream, or a canceled request left it sleeping untilrequest_timeout— or forever without one.Fix: track send-closed streams in a
state_lock-guarded set onH2Connection, marked by the read loop on END_STREAM/RST_STREAM and by cancellation, notifyingwindow_condition._reserve_send_window!returns 0 for a send-closed stream; the writer abandons the upload (sending RST_STREAM(NO_ERROR) for a benign early response) and the roundtrip returns the peer's response. RST_STREAM(NO_ERROR) after a complete response is treated as benign per §8.1 ("clients MUST NOT discard responses as a result"); other resets surface asH2StreamResetErrorwithout failing the shared connection.Design note: the set avoids reading per-stream state under
state_lock, which would nest against the existingstate.lock→state_lockorder used in the roundtrip.Issue 2 — missing content-length.
_request_headers_for_h2never emittedcontent-lengthfromrequest.content_length, unlike the HTTP/1.1 wire path; gateways translating to h1 origins then forward the body with no declared length, which origins commonly treat as empty. Now emitted when the body size is known and the caller didn't set one.Tests: four new testsets (early complete response aborts the upload and returns the response; benign NO_ERROR reset keeps the response; mid-upload reset raises without poisoning the connection; content-length on the wire). Full test suite passes locally (macOS arm64, Julia 1.12.6).
Verified end-to-end against the original failing case: a 2 MB upload to a Cloudflare-fronted h2 endpoint that previously hung until timeout now completes (201) in ~1.5 s.
🤖 Generated with Claude Code
E: I'm not a web-dev person so if there's any obvious errors here let me know and I will buckle down and figure out what is going on myself without Claude.