Conversation
A `103 Early Hints` (or any 1xx) HEADERS frame was treated as the final response: the adapter parsed its `:status`, completed the response `Completer`, and then completed the same `Completer` again when the real final HEADERS frame arrived, crashing the isolate with `Bad state: Future already completed`. Interim headers also leaked into the final response because `responseHeaders` was never reset between frames. Skip 1xx responses instead of completing on them, and reset the accumulated headers per HEADERS frame so interim headers do not carry over into the final response. Closes cfug#2600 Co-Authored-By: Claude <noreply@anthropic.com>
|
Review notes — both non-blocking, verified locally (the regression test reproduces the exact 1. Trailers are now deterministically dropped — please disclose it and add a hardening testA trailer section arrives as a HEADERS frame without
Proper trailer support is an API addition — opened as #2602. 2. Malformed
|
|
@chiliec Thank you for fixing this issue! Are you planning to update this PR according to the review comments? |
Address review on cfug#2601: - 1xx HEADERS frame with END_STREAM terminates the stream without a final response (RFC 9110 §15.2, RFC 9113 §8.4). Previously the request hung until receiveTimeout on a pooled connection; now it completes with DioException.connectionError, matching the existing precedent in this method (redirect-without-location) and Go's x/net/http2. - Disclose in the CHANGELOG that trailer HEADERS frames (no :status, e.g. gRPC trailing metadata) are now discarded; proper support tracked in cfug#2602. - Add two tests: a trailer HEADERS frame after DATA still completes the request; a 1xx+END_STREAM response fails fast instead of hanging.
|
Both points addressed in 51bc3a0 — verified locally:
One housekeeping item (§8.3): commit 51bc3a0 is missing the With that, this looks ready to me from the implementation side. Trailer support itself: discussion continues in #2602. Comment drafted with AI assistance (GLM), reviewed and submitted by a human. |
CaiJingLong
left a comment
There was a problem hiding this comment.
The 1xx handling, final-header isolation, trailer behavior, and malformed interim END_STREAM case look correct in the reviewed scope. Both points from the earlier review are addressed in this head.
Local verification in an isolated worktree at 51bc3a0, using Dart 3.13.5: 18 tests passed across early_hints_test.dart, redirect_test.dart, and headers_test.dart; analysis of lib/test and formatting of the changed Dart files were clean. Running the interim-response regression against base 4684e29 reproduced Bad state: Future already completed and the incorrect 103 response; it passes on this head.
GitHub's package-verification and CodeQL runs are still action_required, so this approval covers the code review and the stated local checks, not a completed hosted-CI matrix. Review performed with OpenAI Codex assistance.
New Pull Request Checklist
mainbranch to avoid conflicts (via merge from master or rebase)CHANGELOG.mdin the corresponding packageAdditional context and info (if any)
Closes #2600.
Problem. When an HTTP/2 server sends a
103 Early Hints(or any 1xx)HEADERS frame before the final response,
Http2Adapter._fetchtreated theinterim frame as final: it parsed the
103:status, completed the responseCompleter, and then completed the sameCompleteragain when the real200HEADERS frame arrived on the same stream, crashing the isolate withBad state: Future already completed. As a secondary effect,responseHeaderswas never reset between HEADERS frames, so interim headers (e.g.
link) leakedinto the final response.
Fix. In the HEADERS-frame handler, 1xx responses are now skipped (the
handler returns without completing), and the accumulated response headers are
reset per final HEADERS frame so interim headers don't carry over. Single-frame
responses (the common case) are unaffected.
Note (sensitive area — header handling, §3). This touches header handling,
so I kept the change minimal and additive: no public API change, no signature
change, default behavior for normal single-HEADERS-frame responses is
identical.
Verification. Added a regression test (
plugins/http2_adapter/test/early_hints_test.dart)that drives a local h2c server emitting
103then200on the same stream andasserts the request resolves with
200, bodyhello, and that the interimlinkheader does not leak. Confirmed genuine RED→GREEN: reverting only thesource change makes the test fail with the exact
Bad state: Future already completedcrash; with the fix it passes.redirect_test.dart(14 tests, sameHEADERS-frame path) and
headers_test.dartstill pass;dart analyzeanddart formatare clean on both changed files.AI disclosure (§8.3): implementation and tests were produced with Claude; a human owns and has reviewed the change.