Merge 801dbb508a6d2c5c473c894938c7a4c8067988ac into 5e417b44e1540f528d2ae63e3e20229a902d1db2
This commit is contained in:
commit
144f4c8580
@ -2,6 +2,29 @@ import OpenClawProtocol
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
/// Thread-safe one-shot wrapper around CheckedContinuation to prevent double-resume crashes.
|
||||
private final class OneShotContinuation: @unchecked Sendable {
|
||||
private let lock = NSLock()
|
||||
private var continuation: CheckedContinuation<Void, Error>?
|
||||
|
||||
init(_ continuation: CheckedContinuation<Void, Error>) {
|
||||
self.continuation = continuation
|
||||
}
|
||||
|
||||
func resume(error: Error?) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let cont = self.continuation
|
||||
self.continuation = nil
|
||||
guard let cont else {
|
||||
// URLSessionWebSocketTask fired pong handler more than once (iOS 18+ race).
|
||||
// The continuation was already resumed; silently drop the duplicate.
|
||||
return
|
||||
}
|
||||
ThrowingContinuationSupport.resumeVoid(cont, error: error)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol WebSocketTasking: AnyObject {
|
||||
var state: URLSessionTask.State { get }
|
||||
func resume()
|
||||
@ -44,8 +67,12 @@ public struct WebSocketTaskBox: @unchecked Sendable {
|
||||
|
||||
public func sendPing() async throws {
|
||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||
// URLSessionWebSocketTask.sendPing can invoke the handler more than once when
|
||||
// the task is cancelled mid-flight (observed on iOS 18+). Guard with a one-shot
|
||||
// wrapper to avoid a CheckedContinuation double-resume crash.
|
||||
let once = OneShotContinuation(continuation)
|
||||
self.task.sendPing { error in
|
||||
ThrowingContinuationSupport.resumeVoid(continuation, error: error)
|
||||
once.resume(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user