Repository navigation
Fixed a crash when URLSession reports a WebSocket pong more than once - #75
Merged
Merged
Conversation
URLSessionWebSocketTask.sendPing(pongReceiveHandler:) calls its handler more than once, even concurrently, when a connection is torn down between ping and pong (Apple r. 129686846), and sendPing() resumed its CheckedContinuation on every call, which terminated Cirruscope 1.0.0 and 1.1.0 in 21 reported crashes. PendingPing now delivers exactly one outcome and lets the waiting task's cancellation compete for it, since the handler is also known never to be called. PushNotificationsConnection closes its channel as soon as a session ends or its task is cancelled, handshake included, because neither URLSession's receive() nor its pong handler observes cancellation and a stopped stream otherwise kept its socket open. A ping without a pong within ten seconds now closes the connection so the coordinator reconnects. Signed-off-by: Iva Horn <contact@cirruscope.app> Co-Authored-By: Claude Opus 5.5 <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.
URLSessionWebSocketTask.sendPing(pongReceiveHandler:)can call its handler more than once, even concurrently, when a connection is torn down between ping and pong. Apple acknowledges this as its own bug (r. 129686846, forum thread).URLSessionWebSocketChannel.sendPing()resumed itsCheckedContinuationon every call, and the second resume terminates the process. Cirruscope 1.0.0 and 1.1.0 ship Rainmaker 3.3.0, and their Xcode Organizer shows 21 crash reports of this from App Store and TestFlight installs, most 1–9 s after the Mac woke from sleep. 4.1.0 has the same code.Changes
sendPing()now waits through the newPendingPing, a lock-protectedPendingPingStatethat resumes the waiting task exactly once. Repeated handler calls are dropped and logged at.notice. The waiting task's cancellation competes for the same outcome, because the handler is also known never to be called (FB12179546).receive()nor its pong handler observes task cancellation, so a stoppedServer.events(_:)stream used to leave its socket open until the network dropped it.PushNotificationsConnectionnow closes the channel as soon as a session ends, and whenever its task is cancelled, including during the handshake.pingIntervalandpongTimeoutare configurable on the coordinator for tests.WebSocketChanneldocumentation now states what the transport relies on:sendPing()may end withCancellationError, andcancel()ends outstanding calls and may be called more than once.Tests
PendingPingTestsstands in for the framework: it stores the handler a ping hands out and calls it later, from other queues, twice, concurrently (256×), racing cancellation (300 iterations), or never.ServerEventsTestscases cover the socket closing on stop (after and during authentication), reconnecting when a pong never arrives (also with a ping that ignores cancellation), and staying connected while pongs arrive. The mock channel can now ignore cancellation the way URLSession does.swift test: 240/240 pass.Semantic versioning
This is a patch release (4.1.1): no public API changes. The only behaviour change is that a missing pong now leads to a reconnect, which is what the ping loop's documentation already said it did.
🤖 Generated with Claude Code