Skip to content

Commit ec5f9a4

Browse files
refactor: simplify the semaphore
Signed-off-by: Fabrizio Demaria <fabrizio.f.demaria@gmail.com>
1 parent b8c20fb commit ec5f9a4

File tree

1 file changed

+31
-43
lines changed

1 file changed

+31
-43
lines changed

Sources/OpenFeature/OpenFeatureAPI.swift

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,19 @@
11
import Combine
22
import Foundation
33

4-
/// Simple async semaphore for serializing operations
5-
private actor AsyncSemaphore {
6-
private var isAvailable = true
7-
private var waiters: [CheckedContinuation<Void, Never>] = []
8-
9-
func wait() async {
10-
if isAvailable {
11-
isAvailable = false
12-
return
13-
}
14-
15-
await withCheckedContinuation { continuation in
16-
waiters.append(continuation)
17-
}
18-
}
19-
20-
func signal() {
21-
if let waiter = waiters.first {
22-
waiters.removeFirst()
23-
waiter.resume()
24-
} else {
25-
isAvailable = true
4+
/// Simple serial async task queue for serializing operations
5+
private actor AsyncSerialQueue {
6+
private var last: Task<Void, Never>? = nil
7+
8+
/// Runs the given operation after previously enqueued work completes.
9+
func run(_ operation: @Sendable @escaping () async -> Void) async {
10+
let previous = last
11+
let task = Task {
12+
_ = await previous?.result
13+
await operation()
2614
}
15+
last = task
16+
await task.value
2717
}
2818
}
2919

@@ -32,7 +22,7 @@ private actor AsyncSemaphore {
3222
public class OpenFeatureAPI {
3323
private let eventHandler = EventHandler()
3424
private let queue = DispatchQueue(label: "com.openfeature.providerDescriptor.queue")
35-
private let contextUpdateSemaphore = AsyncSemaphore()
25+
private let contextUpdateQueue = AsyncSerialQueue()
3626

3727
private(set) var providerSubject = CurrentValueSubject<FeatureProvider?, Never>(nil)
3828
private(set) var evaluationContext: EvaluationContext?
@@ -196,26 +186,24 @@ public class OpenFeatureAPI {
196186
}
197187

198188
private func updateContext(evaluationContext: EvaluationContext) async {
199-
// Ensure only ONE updateContext operation runs at a time
200-
await contextUpdateSemaphore.wait()
201-
defer { Task { await contextUpdateSemaphore.signal() } }
202-
203-
do {
204-
let oldContext = self.evaluationContext
205-
self.evaluationContext = evaluationContext
206-
self.providerStatus = .reconciling
207-
eventHandler.send(.reconciling(nil))
208-
209-
try await self.providerSubject.value?.onContextSet(
210-
oldContext: oldContext,
211-
newContext: evaluationContext
212-
)
213-
214-
self.providerStatus = .ready
215-
eventHandler.send(.contextChanged(nil))
216-
} catch {
217-
self.providerStatus = .error
218-
eventHandler.send(.error(ProviderEventDetails(message: error.localizedDescription)))
189+
await contextUpdateQueue.run { [self] in
190+
do {
191+
let oldContext = self.evaluationContext
192+
self.evaluationContext = evaluationContext
193+
self.providerStatus = .reconciling
194+
eventHandler.send(.reconciling(nil))
195+
196+
try await self.providerSubject.value?.onContextSet(
197+
oldContext: oldContext,
198+
newContext: evaluationContext
199+
)
200+
201+
self.providerStatus = .ready
202+
eventHandler.send(.contextChanged(nil))
203+
} catch {
204+
self.providerStatus = .error
205+
eventHandler.send(.error(ProviderEventDetails(message: error.localizedDescription)))
206+
}
219207
}
220208
}
221209

0 commit comments

Comments
 (0)