Skip to content

Fix h2 close race - #580

Open
sushantshankar2414 wants to merge 6 commits into
coder:mafredri/http2from
sushantshankar2414:fix-h2-close-race
Open

sushantshankar2414 wants to merge 6 commits into
coder:mafredri/http2from
sushantshankar2414:fix-h2-close-race

Conversation

@sushantshankar2414

Copy link
Copy Markdown

Tested on go1.26.4 linux/amd64 (x/net v0.44.0).

go test -race -count=50 -run TestHTTP2Suite_XCONNECT_Enabled in
internal/thirdparty/http2:
before: 19/50 runs fail with a data race (seen on both TLS and h2c)
after: 0/50 (fix below)

Cause: the client's deferred CloseNow resets the stream (RST_STREAM),
x/net cancels r.Context(), and the setupReadTimeout AfterFunc calls
Conn.close → h2ServerStream.Close → Flush on its own goroutine. At the
same time the handler's deferred Conn.Close writes a close frame via
h2ServerStream.Write → Flush. Both use the same http.ResponseWriter,
which isn't safe for concurrent use (unlike net.Conn on the HTTP/1.1
path). Trace: [gist link]

This also answers the TODO in h2ServerStream.Close: the flush isn't
needed, since Write already flushes after every write.
With this change go test -race ./... passes in the root module and
internal/thirdparty. Happy to open a PR against this branch if that's
easier.

Possible follow-up: on HTTP/1.1, rwc.Close() unblocks a stuck Write,
which setupWriteTimeout relies on. On HTTP/2, closing the request body
doesn't unblock a ResponseWriter.Write waiting on flow control, so
write timeouts may not fire when the peer stops reading. Happy to
write a test for that.

mafredri and others added 6 commits October 1, 2025 14:07
- Fix 'exampels' typo in CI workflow comments
- Fix README paths to use 'internal/examples/http2'
- Change visibleAddr to display 0.0.0.0 for all-interface bindings
Rename the Protocol type to HTTPProtocol for consistency with existing
HTTPClient and HTTPHeader field naming in DialOptions.

Constants are also renamed:
- ProtocolHTTP1 → HTTPProtocol1
- ProtocolHTTP2 → HTTPProtocol2
- ProtocolAcceptAny → HTTPProtocolAny
Simplify the TLS example by requiring -cert and -key flags instead of
auto-generating a self-signed certificate. This reduces complexity and
keeps the example focused on HTTP/2 WebSocket usage.
Close can run on a context.AfterFunc goroutine (e.g. after the peer
sends RST_STREAM) while the handler goroutine is in Write. Both
touched the same http.ResponseWriter, which is not safe for concurrent
use. Write already flushes after every write, so the Flush in Close
was unnecessary.
@sushantshankar2414
sushantshankar2414 marked this pull request as draft October 22, 2026 14:02
@sushantshankar2414
sushantshankar2414 marked this pull request as ready for review October 22, 2026 14:02
@mafredri
mafredri changed the base branch from master to mafredri/http2 October 22, 2026 17:06

This branch has not been deployed

No deployments
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.

2 participants