Skip to content

Fix crash when a connection release races with its own close/fail - #3

Merged
o-nnerb merged 1 commit into
betafrom
fix/connection-pool-release-race
Aug 27, 2026
Merged

Fix crash when a connection release races with its own close/fail#3
o-nnerb merged 1 commit into
betafrom
fix/connection-pool-release-race

Conversation

@o-nnerb

@o-nnerb o-nnerb commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

Found while validating the new enable-apple-tests CI step on #2: iOS/iPadOS/watchOS crashed consistently around testBiDirectionalStreamingEarly200 with:

Fatal error: A connection that we don't know was released? Something is very wrong...

macOS/tvOS/visionOS/Catalyst didn't hit it in the time it takes to run the suite. Upstream's own CI never ran this test suite against any Apple platform, so this had no way of surfacing before.

Root cause: http1ConnectionReleased force-unwrapped releaseConnection(_:), crashing via preconditionFailure if the connection was no longer tracked. Its sibling http1ConnectionClosed already handles that same situation gracefully (guard let ... else { return .none }) — the two events (channel.closeFuture and taskCompleted()) run on the same EventLoop, but their relative order depends on I/O timing, so a connection can be torn down (idle timeout, peer closing early, HTTP1→HTTP2 migration) right before its already-queued release event gets processed. This asymmetry existed at both call sites: HTTP1StateMachine.http1ConnectionReleased and HTTP2StateMachine.http1ConnectionReleased.

Fix: HTTP1Connections.releaseConnection(_:) now returns (Int, IdleConnectionContext)? instead of crashing on an unknown connection, and both call sites handle nil the same way their http1ConnectionClosed sibling already does.

Test plan

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

http1ConnectionReleased force-unwrapped releaseConnection(_:), crashing
via preconditionFailure if the connectionID was no longer in the state
machine's connections array. Its sibling http1ConnectionClosed already
handled this gracefully (guard let ... else { return .none }), because
the same race exists for both events: the channel's closeFuture and
taskCompleted() callbacks run on the same EventLoop but their relative
order depends on I/O timing, so a connection can be torn down (idle
timeout, peer closing early, HTTP1->HTTP2 migration) right before its
queued release event is processed.

Found via the new Apple platform CI: enabling apple-tests exposed this
on iOS/iPadOS/watchOS simulators (crashing consistently around
testBiDirectionalStreamingEarly200 with "Fatal error: A connection
that we don't know was released? Something is very wrong..."), while
macOS/tvOS/visionOS/Catalyst didn't hit the race in the time it takes
to run the suite. Upstream's own CI never ran this suite against an
Apple platform at all, so the bug had no way of surfacing there.

Same fix applied at both call sites (HTTP1StateMachine and
HTTP2StateMachine), mirroring the existing http1ConnectionClosed
pattern in each.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@o-nnerb
o-nnerb merged commit 7119df5 into beta Aug 27, 2026
33 of 35 checks passed
@o-nnerb
o-nnerb deleted the fix/connection-pool-release-race branch August 27, 2026 11:58
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.

1 participant