fix(http): end the h3 request stream at response completion [L01.01.11.44] - #932
Merged
Merged
Conversation
…ient sees the response terminate [L01.01.11.44] An HTTP/3 response body is delimited by the request stream's end (RFC 9114 §4.1), not by Content-Length. The h3 send path wrote HEADERS/DATA, flushed, and returned without ending the request stream's write side, so a real .NET HTTP/3 client stayed in ReadResponseContentAsync waiting for a FIN that only arrived at connection teardown — at which point the control/QPACK critical streams closing surfaced at the client as H3_CLOSED_CRITICAL_STREAM (0x104). The buffered SendAsync and the streaming sink's CompleteFramedAsync now end the request stream's write side (graceful QUIC FIN via the IConnection half-close contract, Output.Complete()) once the response is fully flushed. Best-effort: a teardown race that disposed the stream is swallowed. Both #928 send observations resolve here: a bodyless 200 completes the client's zero-length drain via the FIN, and the buffered-body content length already matched the DATA written (ReadBodyAsync reads the whole buffer, position-independent). Adds platform-guarded real-QUIC round-trip regression tests (full round-trip, bodyless 200, end-position body, sequential requests pinning critical-stream lifetime) and flips the Web.Hosting UseHttp3 e2e from best-effort to a hard client-observed status+body assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
Batch 5, stack 1/4 (base:
main).Root-caused and fixed the defect behind every failed HTTP/3 round-trip. The control stream was innocent — the issue title's hypothesis did not survive contact with the evidence:
SendAsyncwrote HEADERS/DATA, flushed, and returned; the stream was only completed at whole-connection teardown. A real .NET client sits inReadResponseContentAsyncwaiting for the FIN, and when teardown finally arrives it surfaces asH3_CLOSED_CRITICAL_STREAM(0x104) — the critical streams closing was the symptom the client happened to report, not the cause. Proven by a decisive experiment: adding the FIN flips the failing example to a clean200+ body.Output.Complete()— theIConnectionhalf-close contract) once the response is fully flushed, in both send paths: the buffered path (CompleteResponseStreamWritesat the end ofSendAsync) and the streaming path (Http3ResponseBodyStream.CompleteFramedAsyncnow flushes then completes). Best-effort with narrowly-scoped catches (disposed / wire-level teardown races are benign — the bytes are already delivered).ReadBodyAsyncreads position-independently viaToArray()) — locked in with a regression test, no code change.WebHttp3HostingIntegrationTestsbest-effort response handling flipped to hard client-observed status + body assertions; Web.Hosting DESIGN.md's "blocked defect" posture rewritten as verified. The Http.Connections h3 example now completes a full round-trip.Tests
Http.Connections 422 (4 new platform-guarded real-QUIC round-trip tests + a new
Http3LoopbackServerharness; QUIC e2e genuinely ran on this machine, not skipped) · Web.Hosting 58 — all passing.Follow-up candidate (not filed)
Request-stream wrapper lifecycle is connection-scoped (
QuicMultiplexedConnection.\_streamsuntracks only at connection teardown) — unbounded wrapper accumulation on long-lived h3 connections; pre-existing, neither introduced nor worsened here.Closes #928
🤖 Generated with Claude Code