Skip to content

Fix UQUICConn.Close deadlock and report handshake errors in NextEvent - #424

Open
ari-ahm wants to merge 1 commit into
refraction-networking:masterfrom
ari-ahm:fix-uquicconn-close-deadlock
Open

ari-ahm wants to merge 1 commit into
refraction-networking:masterfrom
ari-ahm:fix-uquicconn-close-deadlock

Conversation

@ari-ahm

@ari-ahm ari-ahm commented Oct 13, 2026

Copy link
Copy Markdown

Fixes #421.

UQUICConn (u_quic.go) mirrors QUICConn (quic.go), but two changes from the Go 1.26 QUIC handshake rework only made it into QUICConn:

  • Close deadlock. quicWaitForSignal now always sends on signalc before it checks for cancellation, and QUICConn.Close receives that send before draining blockedc. UQUICConn.Close didn't, so closing a connection while the handshake was waiting for data hung forever (with the handshake mutex held). This adds the <-signalc receive, and checks ctx == nil for "never started" like QUICConn does.
  • NextEvent never reported a failed handshake. QUICConn.NextEvent returns the handshake error once as a QUICErrorEvent; UQUICConn.NextEvent kept returning QUICNoEvent, so a TLS-level rejection looked like a stall. This copies the same block.

Both changes are line-for-line what QUICConn already does.

Tests

u_quic_test.go adds UQUICConn versions of TestQUICCanceledWaitingForData and TestQUICHandshakeError, each for HelloGolang and HelloCustom. The Close test fails after a 5 s timeout instead of hanging the test binary.

  • Without this change: TestUQUICCanceledWaitingForData fails ("Close() deadlocked"), TestUQUICHandshakeError fails ("NextEvent() = 2, want QUICErrorEvent").
  • With it: go test ./... passes, and go test -race -count=3 -run QUIC . passes.

Found while bumping uTLS in a project that uses apernet/quic-go's uTLS-based Chrome parrot: its QUIC tests hung in UQUICConn.Close. Thanks to @Capricornus007 for the analysis in #421; their suggestion of a test that checks quic.go and u_quic.go stay in sync isn't included here.

🤖 Generated with Claude Code

UQUICConn mirrors QUICConn, but missed two changes from the Go 1.26 QUIC rework:

- Close: the handshake goroutine now always sends on signalc before checking
  for cancellation, and QUICConn.Close receives it before draining blockedc.
  UQUICConn.Close didn't, so closing during a handshake hung forever.
- NextEvent: QUICConn reports a failed handshake once as QUICErrorEvent;
  UQUICConn never did, so a TLS failure looked like no events.

Both now match QUICConn. Adds UQUICConn versions of
TestQUICCanceledWaitingForData and TestQUICHandshakeError, for HelloGolang
and HelloCustom; both fail without this change.

Fixes refraction-networking#421

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KqYot5fp6kvHireReN6Lu7
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.

UQUICConn.Close() deadlocks: missing the <-signalc receive that QUICConn.Close() has

1 participant