Skip to content

Commit cd9ddc3

Browse files
committed
Fix TritonKit observer concurrency
1 parent d6de6b0 commit cd9ddc3

3 files changed

Lines changed: 37 additions & 5 deletions

File tree

Sources/TritonKit/TritonKit.swift

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ public class TritonKit {
179179
public weak var delegate: TritonKitDelegate?
180180
public private(set) var state: ConnectionState = .disconnected {
181181
didSet {
182+
guard oldValue != state else { return }
182183
delegate?.tritonKit(self, didChangeState: state)
183184
notifyStateObservers(state)
184185
}
@@ -193,6 +194,7 @@ public class TritonKit {
193194
private var pingTimer: Timer?
194195
private var defaultRequestHandler: TritonKitRequestHandler?
195196
private var isStarted = false
197+
private let observerLock = NSLock()
196198
private var stateObservers: [UUID: (ConnectionState) -> Void] = [:]
197199
private var errorObservers: [UUID: (Error) -> Void] = [:]
198200
internal var endpointReadinessTimeout: TimeInterval = 0.25
@@ -256,19 +258,30 @@ public class TritonKit {
256258
@discardableResult
257259
public func onStateChange(_ handler: @escaping (ConnectionState) -> Void) -> ObservationToken {
258260
let id = UUID()
261+
observerLock.lock()
259262
stateObservers[id] = handler
260-
handler(state)
263+
let currentState = state
264+
observerLock.unlock()
265+
handler(currentState)
261266
return ObservationToken { [weak self] in
262-
self?.stateObservers.removeValue(forKey: id)
267+
guard let self else { return }
268+
self.observerLock.lock()
269+
self.stateObservers.removeValue(forKey: id)
270+
self.observerLock.unlock()
263271
}
264272
}
265273

266274
@discardableResult
267275
public func onError(_ handler: @escaping (Error) -> Void) -> ObservationToken {
268276
let id = UUID()
277+
observerLock.lock()
269278
errorObservers[id] = handler
279+
observerLock.unlock()
270280
return ObservationToken { [weak self] in
271-
self?.errorObservers.removeValue(forKey: id)
281+
guard let self else { return }
282+
self.observerLock.lock()
283+
self.errorObservers.removeValue(forKey: id)
284+
self.observerLock.unlock()
272285
}
273286
}
274287

@@ -441,14 +454,20 @@ public class TritonKit {
441454
}
442455

443456
private func notifyStateObservers(_ state: ConnectionState) {
444-
for observer in Array(stateObservers.values) {
457+
observerLock.lock()
458+
let observers = Array(stateObservers.values)
459+
observerLock.unlock()
460+
for observer in observers {
445461
observer(state)
446462
}
447463
}
448464

449465
private func notifyError(_ error: Error) {
450466
delegate?.tritonKit(self, didReceiveError: error)
451-
for observer in Array(errorObservers.values) {
467+
observerLock.lock()
468+
let observers = Array(errorObservers.values)
469+
observerLock.unlock()
470+
for observer in observers {
452471
observer(error)
453472
}
454473
}

Tests/TritonKitTests/TKPlatformFallbackTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ struct TKPlatformFallbackTests {
103103
observed.append(state)
104104
}
105105

106+
#expect(observed == [TritonKit.shared.state])
107+
TritonKit.shared.stop()
106108
#expect(observed == [TritonKit.shared.state])
107109
token.cancel()
108110
}

docs-linhay/memory/2026-06-07.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,14 @@
4949
- `swift test --filter TKHostAdapterModelsTests` 通过。
5050
- `swift test --package-path CLI --scratch-path .build/cli --filter DeviceCrossPlatformTests` 通过。
5151
- `docs-linhay/scripts/verify.sh --local` 通过。
52+
53+
## 主仓合并后门禁修复
54+
55+
- Android feature fast-forward 合入主仓后,主仓 `docs-linhay/scripts/verify.sh --local` 暴露 `TKPlatformFallbackTests` 在 Swift Testing 并发执行下会偶发重复收到 `.disconnected` 状态,并在 suite 级重跑时触发 `stateObservers` 字典并发读写崩溃。
56+
- 修复:
57+
- `TritonKit.state` 只有状态真实变化时才通知 delegate / state observers。
58+
- `stateObservers``errorObservers` 增加锁保护,注册、取消和通知时先拷贝 observer 列表再回调。
59+
- `stateObserverToken` 测试增加重复 `stop()` 不应重复广播当前状态的断言。
60+
- 回归:
61+
- `swift test --filter TKPlatformFallbackTests` 通过。
62+
- 主仓 `docs-linhay/scripts/verify.sh --local` 通过。

0 commit comments

Comments
 (0)