mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 14:22:02 +00:00
Fix CheckedContinuation double-resume in waitForConnectionReady
The 'resumed' guard flag was set inside a dispatched MainActor Task rather than synchronously in the connection state handler. Two states arriving in quick succession (e.g. .ready then .failed) could both pass the guard before either Task ran, resuming the continuation twice and trapping. Claim the continuation synchronously on the serial queue before dispatching the side effects. Fixes a SIGTRAP seen in TestFlight build 261.
This commit is contained in:
@@ -1211,7 +1211,19 @@ final class LocalNetworkService {
|
|||||||
let state = ResumedState()
|
let state = ResumedState()
|
||||||
|
|
||||||
connection.stateUpdateHandler = { [weak self] connectionState in
|
connection.stateUpdateHandler = { [weak self] connectionState in
|
||||||
|
// Claim the continuation synchronously here. `stateUpdateHandler`
|
||||||
|
// runs on the serial `queue`, so this test-and-set is atomic.
|
||||||
|
// Previously `state.resumed = true` was set inside the dispatched
|
||||||
|
// MainActor Task, so two states arriving in quick succession (e.g.
|
||||||
|
// .ready then .failed) could both pass the guard before either Task
|
||||||
|
// ran, resuming the continuation twice and trapping.
|
||||||
|
let isTerminal: Bool
|
||||||
|
switch connectionState {
|
||||||
|
case .ready, .failed, .cancelled: isTerminal = true
|
||||||
|
default: isTerminal = false
|
||||||
|
}
|
||||||
guard !state.resumed else { return }
|
guard !state.resumed else { return }
|
||||||
|
if isTerminal { state.resumed = true }
|
||||||
|
|
||||||
Task { @MainActor [weak self] in
|
Task { @MainActor [weak self] in
|
||||||
let elapsed = Int(Date().timeIntervalSince(startTime) * 1000)
|
let elapsed = Int(Date().timeIntervalSince(startTime) * 1000)
|
||||||
@@ -1227,25 +1239,20 @@ final class LocalNetworkService {
|
|||||||
self?.rcLog("CONNECT", "[\(device.name)] State: waiting - \(error.localizedDescription) (\(elapsed)ms)", isWarning: true)
|
self?.rcLog("CONNECT", "[\(device.name)] State: waiting - \(error.localizedDescription) (\(elapsed)ms)", isWarning: true)
|
||||||
|
|
||||||
case .ready:
|
case .ready:
|
||||||
state.resumed = true
|
|
||||||
self?.connectedPeers.insert(device.id)
|
self?.connectedPeers.insert(device.id)
|
||||||
self?.recentlySeenDevices[device.id] = Date()
|
self?.recentlySeenDevices[device.id] = Date()
|
||||||
self?.rcLog("CONNECT", "[\(device.name)] State: READY (\(elapsed)ms)", details: "connectedPeers=\(self?.connectedPeers.count ?? 0)")
|
self?.rcLog("CONNECT", "[\(device.name)] State: READY (\(elapsed)ms)", details: "connectedPeers=\(self?.connectedPeers.count ?? 0)")
|
||||||
continuation.resume()
|
continuation.resume()
|
||||||
|
|
||||||
case .failed(let error):
|
case .failed(let error):
|
||||||
state.resumed = true
|
|
||||||
self?.connections.removeValue(forKey: device.id)
|
self?.connections.removeValue(forKey: device.id)
|
||||||
self?.rcLog("CONNECT", "[\(device.name)] State: FAILED - \(error.localizedDescription) (\(elapsed)ms)", isError: true)
|
self?.rcLog("CONNECT", "[\(device.name)] State: FAILED - \(error.localizedDescription) (\(elapsed)ms)", isError: true)
|
||||||
continuation.resume(throwing: error)
|
continuation.resume(throwing: error)
|
||||||
|
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
if !state.resumed {
|
self?.connections.removeValue(forKey: device.id)
|
||||||
state.resumed = true
|
self?.rcLog("CONNECT", "[\(device.name)] State: cancelled (\(elapsed)ms)", isWarning: true)
|
||||||
self?.connections.removeValue(forKey: device.id)
|
continuation.resume(throwing: CancellationError())
|
||||||
self?.rcLog("CONNECT", "[\(device.name)] State: cancelled (\(elapsed)ms)", isWarning: true)
|
|
||||||
continuation.resume(throwing: CancellationError())
|
|
||||||
}
|
|
||||||
|
|
||||||
@unknown default:
|
@unknown default:
|
||||||
self?.rcDebug("CONNECT", "[\(device.name)] State: unknown (\(elapsed)ms)")
|
self?.rcDebug("CONNECT", "[\(device.name)] State: unknown (\(elapsed)ms)")
|
||||||
|
|||||||
Reference in New Issue
Block a user