From 1b0b7855d86ba6f0b2bbf19558fe99751bf1e2bf Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Mon, 22 Apr 2024 16:01:53 +0200 Subject: [PATCH 1/7] add listening for context changes --- Sources/Confidence/Confidence.swift | 22 +++++++-- Sources/Confidence/EventSenderEngine.swift | 3 ++ .../ConfidenceFeatureProvider.swift | 49 ++++++++++++------- .../ConfidenceFeatureProviderTest.swift | 31 ++++++++++++ 4 files changed, 81 insertions(+), 24 deletions(-) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index 0da26064..86875f20 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -1,13 +1,14 @@ import Foundation +import Combine public class Confidence: ConfidenceEventSender { private let parent: ConfidenceContextProvider? - private var context: ConfidenceStruct public let clientSecret: String public var timeout: TimeInterval public var region: ConfidenceRegion let eventSenderEngine: EventSenderEngine public var initializationStrategy: InitializationStrategy + private let contextFlow = CurrentValueSubject([:]) private var removedContextKeys: Set = Set() required init( @@ -24,10 +25,17 @@ public class Confidence: ConfidenceEventSender { self.timeout = timeout self.region = region self.initializationStrategy = initializationStrategy - self.context = context + self.contextFlow.value = context self.parent = parent } + public func contextChanges() -> AnyPublisher { + return contextFlow + .dropFirst() + .removeDuplicates() + .eraseToAnyPublisher() + } + public func send(definition: String, payload: ConfidenceStruct) { eventSenderEngine.emit(definition: definition, payload: payload, context: getContext()) } @@ -38,18 +46,22 @@ public class Confidence: ConfidenceEventSender { var reconciledCtx = parentContext.filter { !removedContextKeys.contains($0.key) } - self.context.forEach { entry in + self.contextFlow.value.forEach { entry in reconciledCtx.updateValue(entry.value, forKey: entry.key) } return reconciledCtx } public func updateContextEntry(key: String, value: ConfidenceValue) { - context[key] = value + var map = contextFlow.value + map[key] = value + contextFlow.value = map } public func removeContextEntry(key: String) { - context.removeValue(forKey: key) + var map = contextFlow.value + map.removeValue(forKey: key) + contextFlow.value = map removedContextKeys.insert(key) } diff --git a/Sources/Confidence/EventSenderEngine.swift b/Sources/Confidence/EventSenderEngine.swift index 7b94f9fe..b0dd0a22 100644 --- a/Sources/Confidence/EventSenderEngine.swift +++ b/Sources/Confidence/EventSenderEngine.swift @@ -89,6 +89,9 @@ final class EventSenderEngineImpl: EventSenderEngine { } func shutdown() { + for cancellable in cancellables { + cancellable.cancel() + } cancellables.removeAll() } } diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 07960fce..a36ca0b1 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -24,6 +24,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { private let storage: Storage private let eventHandler = EventHandler(ProviderEvent.notReady) private let confidence: Confidence? + private var cancellables = Set() /// Should not be called externally, use `ConfidenceFeatureProvider.Builder`or init with `Confidence` instead. init( @@ -48,7 +49,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { } /// Initialize the Provider via a `Confidence` object. - public init(confidence: Confidence) { + public init(confidence: Confidence, client: ConfidenceResolveClient? = nil) { let metadata = ConfidenceMetadata(version: "0.1.4") // x-release-please-version let options = ConfidenceClientOptions( credentials: ConfidenceClientCredentials.clientSecret(secret: confidence.clientSecret), @@ -63,7 +64,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { storage: DefaultStorage.applierFlagsCache(), options: options, metadata: metadata) - self.client = RemoteConfidenceResolveClient( + self.client = client ?? RemoteConfidenceResolveClient( options: options, applyOnResolve: false, flagApplier: flagApplier, @@ -83,13 +84,18 @@ public class ConfidenceFeatureProvider: FeatureProvider { eventHandler.send(.ready) } + resolve(strategy: initializationStrategy, context: initialContext) + self.startListentingForContextChanges() + } + + private func resolve(strategy: InitializationStrategy, context: EvaluationContext) { Task { do { - let resolveResult = try await resolve(context: initialContext) + let resolveResult = try await resolve(context: context) // update cache with stored values try await store( - with: initialContext, + with: context, resolveResult: resolveResult, refreshCache: self.initializationStrategy == .fetchAndActivate ) @@ -105,6 +111,13 @@ public class ConfidenceFeatureProvider: FeatureProvider { } } + func shutdown() { + for cancellable in cancellables { + cancellable.cancel() + } + cancellables.removeAll() + } + private func store( with context: OpenFeature.EvaluationContext, resolveResult result: ResolvesResult, @@ -126,24 +139,22 @@ public class ConfidenceFeatureProvider: FeatureProvider { oldContext: OpenFeature.EvaluationContext?, newContext: OpenFeature.EvaluationContext ) { - guard oldContext?.hash() != newContext.hash() else { - return - } - self.updateConfidenceContext(context: newContext) - Task { - do { - let resolveResult = try await resolve(context: newContext) - - // update the storage - try await store(with: newContext, resolveResult: resolveResult, refreshCache: true) + } - eventHandler.send(ProviderEvent.ready) - } catch { - eventHandler.send(ProviderEvent.ready) - // do nothing - } + private func startListentingForContextChanges() { + guard let confidence = confidence else { + return } + confidence.contextChanges() + .sink { [weak self] _ in + guard let self = self else { + return + } + let context = MutableContext(attributes: [:]) + self.resolve(strategy: self.initializationStrategy, context: context) + } + .store(in: &cancellables) } private func updateConfidenceContext(context: EvaluationContext) { diff --git a/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift b/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift index 1e04878b..3fafc777 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift @@ -952,6 +952,37 @@ class ConfidenceFeatureProviderTest: XCTestCase { XCTAssertEqual(context, expected) } } + + func testConfidenceContextOnContextChangeThroughConfidence() throws { + class FakeClient: ConfidenceResolveClient { + var callCount = 0 + func resolve(ctx: EvaluationContext) async throws -> ResolvesResult { + callCount += 1 + return .init(resolvedValues: [], resolveToken: "") + } + } + + let confidence = Confidence.Builder.init(clientSecret: "").build() + let client = FakeClient() + let provider = ConfidenceFeatureProvider(confidence: confidence, client: client) + + let readyExpectation = self.expectation(description: "Waiting for init and ctx change to complete") + readyExpectation.expectedFulfillmentCount = 2 + + withExtendedLifetime( + provider.observe().sink { event in + if event == .ready { + readyExpectation.fulfill() + } + }) + { + let ctx1 = MutableContext(targetingKey: "user1") + provider.initialize(initialContext: ctx1) + confidence.updateContextEntry(key: "active", value: ConfidenceValue.init(boolean: true)) + wait(for: [readyExpectation], timeout: 5) + XCTAssertEqual(client.callCount, 2) + } + } } final class DispatchQueueFake: DispatchQueueType { From 5ffda6670efafc7a110bbc1460183f02de3a6a50 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 24 Apr 2024 16:40:42 +0200 Subject: [PATCH 2/7] fixup! merge master --- .../ConfidenceProvider/ConfidenceFeatureProvider.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 697a03aa..3ef112be 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -53,9 +53,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { } /// Initialize the Provider via a `Confidence` object. - internal init(confidence: Confidence, - session: URLSession?, - client: ConfidenceResolveClient?) { + internal init(confidence: Confidence, session: URLSession?, client: ConfidenceResolveClient?) { let metadata = ConfidenceMetadata(version: "0.1.4") // x-release-please-version let options = ConfidenceClientOptions( credentials: ConfidenceClientCredentials.clientSecret(secret: confidence.clientSecret), @@ -148,7 +146,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { oldContext: OpenFeature.EvaluationContext?, newContext: OpenFeature.EvaluationContext ) { - guard let confidence = confidence else { + if confidence == nil { self.resolve(strategy: .fetchAndActivate, context: ConfidenceTypeMapper.from(ctx: newContext)) return } @@ -166,7 +164,8 @@ public class ConfidenceFeatureProvider: FeatureProvider { return } self.resolve(strategy: self.initializationStrategy, context: context) - }.store(in: &cancellables) + } + .store(in: &cancellables) } private func updateConfidenceContext(context: EvaluationContext) { From 086c0a56da71f0dee066babce54b9e2932272d7b Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 24 Apr 2024 16:41:24 +0200 Subject: [PATCH 3/7] move comment up --- Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 3ef112be..787299bb 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -48,11 +48,11 @@ public class ConfidenceFeatureProvider: FeatureProvider { self.resolver = LocalStorageResolver(cache: cache) } + /// Initialize the Provider via a `Confidence` object. public convenience init(confidence: Confidence) { self.init(confidence: confidence, session: nil, client: nil) } - /// Initialize the Provider via a `Confidence` object. internal init(confidence: Confidence, session: URLSession?, client: ConfidenceResolveClient?) { let metadata = ConfidenceMetadata(version: "0.1.4") // x-release-please-version let options = ConfidenceClientOptions( From 24f9ef5443ffa93368351a1125373534458f5c44 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Wed, 24 Apr 2024 17:07:16 +0200 Subject: [PATCH 4/7] diff and remove the old keys that are not present --- .../ConfidenceFeatureProvider.swift | 9 ++++++++- .../ConfidenceFeatureProviderTest.swift | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 787299bb..356d4aae 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -146,12 +146,19 @@ public class ConfidenceFeatureProvider: FeatureProvider { oldContext: OpenFeature.EvaluationContext?, newContext: OpenFeature.EvaluationContext ) { - if confidence == nil { + guard let confidence = confidence else { self.resolve(strategy: .fetchAndActivate, context: ConfidenceTypeMapper.from(ctx: newContext)) return } self.updateConfidenceContext(context: newContext) + guard let oldContext = oldContext else { + return + } + let removedKeys = oldContext.asMap().filter{ (key, value) in !newContext.asMap().keys.contains(key) } + for removedKey in removedKeys.keys { + confidence.removeContextEntry(key: removedKey) + } } private func startListentingForContextChanges() { diff --git a/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift b/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift index a5ce16de..f4730625 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift @@ -913,6 +913,21 @@ class ConfidenceFeatureProviderTest: XCTestCase { } } + func testRemovedKeyWillbeRemovedFromConfidenceContext() { + let confidence = Confidence.Builder.init(clientSecret: "").build() + let provider = ConfidenceFeatureProvider(confidence: confidence) + let initialContext = MutableContext(targetingKey: "user1") + .add(key: "hello", value: Value.string("world")) + provider.initialize(initialContext: initialContext) + let expectedInitialContext = ["targeting_key": ConfidenceValue(string: "user1"), "hello": ConfidenceValue(string: "world")] + XCTAssertEqual(confidence.getContext(), expectedInitialContext) + let expectedNewContext = ["targeting_key": ConfidenceValue(string: "user1"), "new": ConfidenceValue(string: "west world")] + let newContext = MutableContext(targetingKey: "user1") + .add(key: "new", value: Value.string("west world")) + provider.onContextSet(oldContext: initialContext, newContext: newContext) + XCTAssertEqual(confidence.getContext(), expectedNewContext) + } + func testOverridingInProvider() throws { let resolve: [String: MockedResolveClientURLProtocol.ResolvedTestFlag] = [ "user1": .init(variant: "control", value: .structure(["size": .integer(3)])) From 0f40d732099db3dad4049e648df4fca69812074c Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Thu, 25 Apr 2024 11:08:03 +0200 Subject: [PATCH 5/7] only 1 call is made after context diffing --- Sources/Confidence/Confidence.swift | 21 +++++++++- Sources/Confidence/Contextual.swift | 2 +- .../ConfidenceFeatureProvider.swift | 23 ++++------- .../ConfidenceFeatureProviderTest.swift | 41 +++++++++++++++++-- Tests/ConfidenceTests/ConfidenceTests.swift | 10 ++--- 5 files changed, 71 insertions(+), 26 deletions(-) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index c4105df5..bd3b338d 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -52,12 +52,31 @@ public class Confidence: ConfidenceEventSender { return reconciledCtx } - public func updateContextEntry(key: String, value: ConfidenceValue) { + public func putContext(key: String, value: ConfidenceValue) { var map = contextFlow.value map[key] = value contextFlow.value = map } + public func putContext(context: ConfidenceStruct) { + var map = contextFlow.value + for entry in context { + map.updateValue(entry.value, forKey: entry.key) + } + contextFlow.value = map + } + + public func putContext(context: ConfidenceStruct, removedKeys: [String] = []) { + var map = contextFlow.value + for removedKey in removedKeys { + map.removeValue(forKey: removedKey) + } + for entry in context { + map.updateValue(entry.value, forKey: entry.key) + } + contextFlow.value = map + } + public func removeContextEntry(key: String) { var map = contextFlow.value map.removeValue(forKey: key) diff --git a/Sources/Confidence/Contextual.swift b/Sources/Confidence/Contextual.swift index 554babd5..52fe6b73 100644 --- a/Sources/Confidence/Contextual.swift +++ b/Sources/Confidence/Contextual.swift @@ -5,7 +5,7 @@ import Foundation /// Each ConfidenceContextProvider returns local data reconciled with parents' data. Local data has precedence public protocol Contextual: ConfidenceContextProvider { /// Adds/override entry to local data - func updateContextEntry(key: String, value: ConfidenceValue) + func putContext(key: String, value: ConfidenceValue) /// Removes entry from local data /// It hides entries with this key from parents' data (without modifying parents' data) func removeContextEntry(key: String) diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 356d4aae..744906cb 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -146,19 +146,17 @@ public class ConfidenceFeatureProvider: FeatureProvider { oldContext: OpenFeature.EvaluationContext?, newContext: OpenFeature.EvaluationContext ) { - guard let confidence = confidence else { + if confidence == nil { self.resolve(strategy: .fetchAndActivate, context: ConfidenceTypeMapper.from(ctx: newContext)) return } - self.updateConfidenceContext(context: newContext) - guard let oldContext = oldContext else { - return - } - let removedKeys = oldContext.asMap().filter{ (key, value) in !newContext.asMap().keys.contains(key) } - for removedKey in removedKeys.keys { - confidence.removeContextEntry(key: removedKey) + var removedKeys: [String] = [] + if let oldContext = oldContext { + removedKeys = Array(oldContext.asMap().filter { key, _ in !newContext.asMap().keys.contains(key) }.keys) } + + self.updateConfidenceContext(context: newContext, removedKeys: removedKeys) } private func startListentingForContextChanges() { @@ -175,13 +173,8 @@ public class ConfidenceFeatureProvider: FeatureProvider { .store(in: &cancellables) } - private func updateConfidenceContext(context: EvaluationContext) { - for entry in ConfidenceTypeMapper.from(ctx: context) { - confidence?.updateContextEntry( - key: entry.key, - value: entry.value - ) - } + private func updateConfidenceContext(context: EvaluationContext, removedKeys: [String] = []) { + confidence?.putContext(context: ConfidenceTypeMapper.from(ctx: context), removedKeys: removedKeys) } public func getBooleanEvaluation(key: String, defaultValue: Bool, context: EvaluationContext?) throws diff --git a/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift b/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift index f4730625..bb4ea94c 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift @@ -914,18 +914,51 @@ class ConfidenceFeatureProviderTest: XCTestCase { } func testRemovedKeyWillbeRemovedFromConfidenceContext() { + let expectationOneCall = expectation(description: "one call is made") + let twoCallsExpectation = expectation(description: "two calls is made") + class FakeClient: ConfidenceResolveClient { + var callCount = 0 + var oneCallExpectation: XCTestExpectation + var twoCallsExpectation: XCTestExpectation + init(oneCallExpectation: XCTestExpectation, twoCallsExpectation: XCTestExpectation) { + self.oneCallExpectation = oneCallExpectation + self.twoCallsExpectation = twoCallsExpectation + } + + func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult { + callCount += 1 + if callCount == 1 { + self.oneCallExpectation.fulfill() + } else if callCount == 2 { + self.twoCallsExpectation.fulfill() + } + return .init(resolvedValues: [], resolveToken: "") + } + } + let confidence = Confidence.Builder.init(clientSecret: "").build() - let provider = ConfidenceFeatureProvider(confidence: confidence) + let client = FakeClient(oneCallExpectation: expectationOneCall, twoCallsExpectation: twoCallsExpectation) + let provider = ConfidenceFeatureProvider(confidence: confidence, session: nil, client: client) let initialContext = MutableContext(targetingKey: "user1") .add(key: "hello", value: Value.string("world")) provider.initialize(initialContext: initialContext) - let expectedInitialContext = ["targeting_key": ConfidenceValue(string: "user1"), "hello": ConfidenceValue(string: "world")] + let expectedInitialContext = [ + "targeting_key": ConfidenceValue(string: "user1"), + "hello": ConfidenceValue(string: "world") + ] XCTAssertEqual(confidence.getContext(), expectedInitialContext) - let expectedNewContext = ["targeting_key": ConfidenceValue(string: "user1"), "new": ConfidenceValue(string: "west world")] + let expectedNewContext = [ + "targeting_key": ConfidenceValue(string: "user1"), + "new": ConfidenceValue(string: "west world") + ] let newContext = MutableContext(targetingKey: "user1") .add(key: "new", value: Value.string("west world")) + wait(for: [expectationOneCall], timeout: 1) + XCTAssertEqual(1, client.callCount) provider.onContextSet(oldContext: initialContext, newContext: newContext) XCTAssertEqual(confidence.getContext(), expectedNewContext) + wait(for: [twoCallsExpectation], timeout: 1) + XCTAssertEqual(2, client.callCount) } func testOverridingInProvider() throws { @@ -1041,7 +1074,7 @@ class ConfidenceFeatureProviderTest: XCTestCase { { let ctx1 = MutableContext(targetingKey: "user1") provider.initialize(initialContext: ctx1) - confidence.updateContextEntry(key: "active", value: ConfidenceValue.init(boolean: true)) + confidence.putContext(key: "active", value: ConfidenceValue.init(boolean: true)) wait(for: [readyExpectation], timeout: 5) XCTAssertEqual(client.callCount, 2) } diff --git a/Tests/ConfidenceTests/ConfidenceTests.swift b/Tests/ConfidenceTests/ConfidenceTests.swift index a5bbaad0..cd0035d6 100644 --- a/Tests/ConfidenceTests/ConfidenceTests.swift +++ b/Tests/ConfidenceTests/ConfidenceTests.swift @@ -34,7 +34,7 @@ final class ConfidenceTests: XCTestCase { let confidenceChild: ConfidenceEventSender = confidenceParent.withContext( ["k2": ConfidenceValue(string: "v2")] ) - confidenceParent.updateContextEntry( + confidenceParent.putContext( key: "k3", value: ConfidenceValue(string: "v3")) let expected = [ @@ -55,7 +55,7 @@ final class ConfidenceTests: XCTestCase { context: ["k1": ConfidenceValue(string: "v1")], parent: nil ) - confidence.updateContextEntry( + confidence.putContext( key: "k1", value: ConfidenceValue(string: "v3")) let expected = [ @@ -77,7 +77,7 @@ final class ConfidenceTests: XCTestCase { let confidenceChild: ConfidenceEventSender = confidenceParent.withContext( ["k2": ConfidenceValue(string: "v2")] ) - confidenceChild.updateContextEntry( + confidenceChild.putContext( key: "k2", value: ConfidenceValue(string: "v4")) let expected = [ @@ -100,7 +100,7 @@ final class ConfidenceTests: XCTestCase { let confidenceChild: ConfidenceEventSender = confidenceParent.withContext( ["k2": ConfidenceValue(string: "v2")] ) - confidenceParent.updateContextEntry( + confidenceParent.putContext( key: "k2", value: ConfidenceValue(string: "v4")) let expected = [ @@ -190,7 +190,7 @@ final class ConfidenceTests: XCTestCase { ] ) confidenceChild.removeContextEntry(key: "k1") - confidenceChild.updateContextEntry(key: "k1", value: ConfidenceValue(string: "v4")) + confidenceChild.putContext(key: "k1", value: ConfidenceValue(string: "v4")) let expected = [ "k2": ConfidenceValue(string: "v2"), "k1": ConfidenceValue(string: "v4"), From 89f3b5407d2fe06a9c7563ab861f4c89b588cfdc Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Thu, 25 Apr 2024 16:40:20 +0200 Subject: [PATCH 6/7] sync context change actions using queue --- Sources/Confidence/Confidence.swift | 55 +++++++++++++++++++---------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index bd3b338d..f11b6783 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -40,6 +40,17 @@ public class Confidence: ConfidenceEventSender { eventSenderEngine.emit(eventName: eventName, message: message, context: getContext()) } + private let confidenceQueue = DispatchQueue(label: "com.confidence.queue") + + private func withLock(callback: @escaping (Confidence) -> Void) { + confidenceQueue.sync { [weak self] in + guard let self = self else { + return + } + callback(self) + } + } + public func getContext() -> ConfidenceStruct { let parentContext = parent?.getContext() ?? [:] @@ -53,35 +64,43 @@ public class Confidence: ConfidenceEventSender { } public func putContext(key: String, value: ConfidenceValue) { - var map = contextFlow.value - map[key] = value - contextFlow.value = map + withLock { confidence in + var map = confidence.contextFlow.value + map[key] = value + confidence.contextFlow.value = map + } } public func putContext(context: ConfidenceStruct) { - var map = contextFlow.value - for entry in context { - map.updateValue(entry.value, forKey: entry.key) + withLock { confidence in + var map = confidence.contextFlow.value + for entry in context { + map.updateValue(entry.value, forKey: entry.key) + } + confidence.contextFlow.value = map } - contextFlow.value = map } public func putContext(context: ConfidenceStruct, removedKeys: [String] = []) { - var map = contextFlow.value - for removedKey in removedKeys { - map.removeValue(forKey: removedKey) - } - for entry in context { - map.updateValue(entry.value, forKey: entry.key) + withLock { confidence in + var map = confidence.contextFlow.value + for removedKey in removedKeys { + map.removeValue(forKey: removedKey) + } + for entry in context { + map.updateValue(entry.value, forKey: entry.key) + } + confidence.contextFlow.value = map } - contextFlow.value = map } public func removeContextEntry(key: String) { - var map = contextFlow.value - map.removeValue(forKey: key) - contextFlow.value = map - removedContextKeys.insert(key) + withLock { confidence in + var map = confidence.contextFlow.value + map.removeValue(forKey: key) + confidence.contextFlow.value = map + confidence.removedContextKeys.insert(key) + } } public func withContext(_ context: ConfidenceStruct) -> Self { From dc906c199e92fe1357b055f8d96e5e3d9992c0bf Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Thu, 25 Apr 2024 16:41:56 +0200 Subject: [PATCH 7/7] fixup! sync context change actions using queue --- Sources/Confidence/Confidence.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index f11b6783..ecf98461 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -10,6 +10,7 @@ public class Confidence: ConfidenceEventSender { public var initializationStrategy: InitializationStrategy private let contextFlow = CurrentValueSubject([:]) private var removedContextKeys: Set = Set() + private let confidenceQueue = DispatchQueue(label: "com.confidence.queue") required init( clientSecret: String, @@ -40,8 +41,6 @@ public class Confidence: ConfidenceEventSender { eventSenderEngine.emit(eventName: eventName, message: message, context: getContext()) } - private let confidenceQueue = DispatchQueue(label: "com.confidence.queue") - private func withLock(callback: @escaping (Confidence) -> Void) { confidenceQueue.sync { [weak self] in guard let self = self else {