URLSessionWebSocketTask - sendPing's pongReceiveHandler called twice

Hello, I'm working on adding a URLSessionWebSocketTask based web socket connection to have live data in a single view. When the user navigates to it, the connection is established and live data is received. The task is being cancelled when this view disappears:

task.cancel(with: .goingAway, reason: nil)

After calling resume() on the task, I ping the server to see if the connection works before sending any messages. I opt to use async API instead of closure based wherever possible. Foundation provides both APIs for most URLSessionWebSocketTask's methods, but for some reason it misses async version of sendPing.

Such method, however, is available in swift-corelibs-foundation project here. So I've added a similar code locally:

extension URLSessionWebSocketTask {
    func sendPing() async throws {
        let _: Void = try await withCheckedThrowingContinuation { continuation in
            sendPing { error in
                if let error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume()
                }
            }
        }
    }
}

The issue that I have is that if the user navigates from the view after sendPing was called, but before pong is received, pongReceiveHandler is called twice with error:

Error Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort" UserInfo={NSDescription=Software caused connection abort}

This results in an exception:

Fatal error: SWIFT TASK CONTINUATION MISUSE: sendPing() tried to resume its continuation more than once, throwing Error Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort" UserInfo={NSDescription=Software caused connection abort}!

There are no issues when the task is cancelled after successful ping.

The documentation does not state that pongReceiveHandler is always called only once, but by looking at the code in swift-corelibs-foundation I think that it should be the case.

Am I misusing sendPing, or is it a bug in the Foundation? Perhaps there is no func sendPing() async throws for some reason?

I use Xcode 15.3 with Swift 5.10.

Great thanks for any help.

Best Regards, Michal Pastwa

My general advice is that you not use URLSessionWebSocketTask for new code [1]. Rather, I recommend that you use the WebSocket support in Network framework. This is, IME, just better overall.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Per TN3151 Choosing the right networking API. And if you’re curious as to why, see this.

Thank you for your quick response!

I wasn't aware of this technote before I choosed URLSessionWebSocketTask. I have now changed Web Socket implementation to use the NWConnection and looks good so far :).

However, NWProtocolWebSocket.Metadata's pong handler seems to have exactly the same issue, so this is where it came from. In a situation when the connection is being cancelled right after "ping" is sent, handler is called twice with POSIXErrorCode(rawValue: 53): Software caused connection abort error.

This is fairly easy to overcome, but still it complicates things a little as it has to be assumed that the handler can be called more than once, especially when using withCheckedThrowingContinuation.

BR,

Michal Pastwa

However, NWProtocolWebSocket.Metadata’s pong handler seems to have exactly the same issue

Bummer.

We have an internal bug report about this (r. 129686846). If you want to track it’s status, file your own bug and ask that it be dup’d to that one.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

URLSessionWebSocketTask - sendPing's pongReceiveHandler called twice
 
 
Q