fix: 用 FetchGate actor 修复并发触发丢失问题
This commit is contained in:
parent
b5ad9c1ec3
commit
55d0f86392
|
|
@ -11,8 +11,8 @@ final class PollerService {
|
|||
private let configService = AppStateService.shared
|
||||
|
||||
private var timer: Timer?
|
||||
private var isFetching = false
|
||||
private var lastFetchTime: Date?
|
||||
private let fetchGate = FetchGate()
|
||||
|
||||
private(set) var isRunning = false
|
||||
|
||||
|
|
@ -85,11 +85,53 @@ final class PollerService {
|
|||
|
||||
// MARK: - Fetch
|
||||
|
||||
private func performFetch() async {
|
||||
guard !isFetching else { return }
|
||||
isFetching = true
|
||||
defer { isFetching = false }
|
||||
/// Serializes fetches and coalesces concurrent triggers.
|
||||
///
|
||||
/// Previously a trigger arriving mid-fetch was silently dropped
|
||||
/// (`guard !isFetching else { return }`). Rapid successive changes —
|
||||
/// e.g. an A→B→A reassignment producing two SSE wake-ups — then
|
||||
/// collapsed into a single fetch, and if that fetch sampled the final
|
||||
/// state the transition was never observed and no notification fired.
|
||||
/// Now a trigger arriving mid-fetch queues exactly one follow-up round,
|
||||
/// so the final state is always observed. The actor also makes the
|
||||
/// check-then-set race-safe (triggers arrive from the timer, the SSE
|
||||
/// handler and the UI on different tasks).
|
||||
private actor FetchGate {
|
||||
private var fetching = false
|
||||
private var pending = false
|
||||
|
||||
/// Returns true if the caller may start fetching; otherwise queues
|
||||
/// a follow-up round and returns false.
|
||||
func tryBegin() -> Bool {
|
||||
if fetching {
|
||||
pending = true
|
||||
return false
|
||||
}
|
||||
fetching = true
|
||||
return true
|
||||
}
|
||||
|
||||
/// Ends one round. Returns true when another round was requested
|
||||
/// while fetching; the gate stays held between rounds so no
|
||||
/// parallel fetch can slip in.
|
||||
func endRound() -> Bool {
|
||||
if pending {
|
||||
pending = false
|
||||
return true
|
||||
}
|
||||
fetching = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func performFetch() async {
|
||||
guard await fetchGate.tryBegin() else { return }
|
||||
repeat {
|
||||
await fetchOnce()
|
||||
} while await fetchGate.endRound()
|
||||
}
|
||||
|
||||
private func fetchOnce() async {
|
||||
lastFetchTime = Date()
|
||||
|
||||
await MainActor.run {
|
||||
|
|
|
|||
Loading…
Reference in New Issue