camera: fix waitForConnection race — continuation registered synchronously on actor, timeout resumes directly; 6x suite runs stable at 95 tests
This commit is contained in:
parent
f723ea6177
commit
ea08f8fcb7
1 changed files with 27 additions and 15 deletions
|
|
@ -40,7 +40,8 @@ public actor ConnectionSupervisor {
|
|||
public private(set) var health: ConnectionStatus = .disconnected
|
||||
|
||||
private var stateContinuations: [UUID: AsyncStream<CameraState>.Continuation] = [:]
|
||||
private var connectionWaiters: [CheckedContinuation<Void, Never>] = []
|
||||
private var connectionWaiters: [UUID] = []
|
||||
private var waiterMap: [UUID: CheckedContinuation<Void, Never>] = [:]
|
||||
|
||||
public init(driver: any CameraDriver, backoff: BackoffPolicy) {
|
||||
self.driver = driver
|
||||
|
|
@ -99,9 +100,7 @@ public actor ConnectionSupervisor {
|
|||
health = state.connection
|
||||
if state.connection == .connected {
|
||||
backoff.reset()
|
||||
let waiters = connectionWaiters
|
||||
connectionWaiters.removeAll()
|
||||
for w in waiters { w.resume() }
|
||||
resumeAllWaiters()
|
||||
}
|
||||
for c in stateContinuations.values {
|
||||
c.yield(state)
|
||||
|
|
@ -109,22 +108,29 @@ public actor ConnectionSupervisor {
|
|||
}
|
||||
|
||||
/// Await connected health. Returns false on timeout.
|
||||
/// Continuation registered synchronously on the actor — no gap for a
|
||||
/// connected event to slip through unobserved. Timeout resumes the
|
||||
/// continuation directly (cancellation can't interrupt a checked continuation).
|
||||
public func waitForConnection(timeout: Duration) async -> Bool {
|
||||
if health == .connected { return true }
|
||||
let waitTask = Task {
|
||||
await withCheckedContinuation { (c: CheckedContinuation<Void, Never>) in
|
||||
self.connectionWaiters.append(c)
|
||||
await withCheckedContinuation { (c: CheckedContinuation<Void, Never>) in
|
||||
let id = UUID()
|
||||
waiterMap[id] = c
|
||||
connectionWaiters.append(id)
|
||||
Task {
|
||||
try? await Task.sleep(for: timeout)
|
||||
self.timeoutWaiter(id: id)
|
||||
}
|
||||
}
|
||||
let timeoutTask = Task {
|
||||
try? await Task.sleep(for: timeout)
|
||||
waitTask.cancel()
|
||||
}
|
||||
_ = await waitTask.value
|
||||
timeoutTask.cancel()
|
||||
return health == .connected
|
||||
}
|
||||
|
||||
private func timeoutWaiter(id: UUID) {
|
||||
guard let c = waiterMap.removeValue(forKey: id) else { return }
|
||||
connectionWaiters.removeAll { $0 == id }
|
||||
c.resume()
|
||||
}
|
||||
|
||||
public func stop() async {
|
||||
connectTask?.cancel()
|
||||
pumpTask?.cancel()
|
||||
|
|
@ -132,8 +138,14 @@ public actor ConnectionSupervisor {
|
|||
pumpTask = nil
|
||||
await driver.disconnect()
|
||||
health = .disconnected
|
||||
let waiters = connectionWaiters
|
||||
resumeAllWaiters()
|
||||
}
|
||||
|
||||
private func resumeAllWaiters() {
|
||||
let ids = connectionWaiters
|
||||
connectionWaiters.removeAll()
|
||||
for w in waiters { w.resume() }
|
||||
for id in ids {
|
||||
waiterMap.removeValue(forKey: id)?.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue