Skip to content

Fix h2 client deadlock on request bodies when the server responds early; emit content-length#1326

Merged
quinnj merged 1 commit into
JuliaWeb:masterfrom
rayegun:fix-h2-client-upload-deadlock
Jul 3, 2026
Merged

Fix h2 client deadlock on request bodies when the server responds early; emit content-length#1326
quinnj merged 1 commit into
JuliaWeb:masterfrom
rayegun:fix-h2-client-upload-deadlock

Conversation

@rayegun

@rayegun rayegun commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

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 until request_timeout — or forever without one.

Fix: track send-closed streams in a state_lock-guarded set on H2Connection, marked by the read loop on END_STREAM/RST_STREAM and by cancellation, notifying window_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 as H2StreamResetError without failing the shared connection.

Design note: the set avoids reading per-stream state under state_lock, which would nest against the existing state.lockstate_lock order used in the roundtrip.

Issue 2 — missing content-length. _request_headers_for_h2 never emitted content-length from request.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.

…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

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.00%. Comparing base (768f863) to head (cb13e65).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@quinnj quinnj merged commit 4573cd8 into JuliaWeb:master Jul 3, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

h2 client: request body upload deadlocks when the server responds before consuming the body; also omits content-length

2 participants