Skip to content

Commit

Permalink
fix cancel current resolve task in new context
Browse files Browse the repository at this point in the history
  • Loading branch information
vahidlazio committed Apr 26, 2024
1 parent eafe8bf commit 56287cb
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 34 deletions.
49 changes: 29 additions & 20 deletions Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class ConfidenceFeatureProvider: FeatureProvider {
private let eventHandler = EventHandler(ProviderEvent.notReady)
private let confidence: Confidence?
private var cancellables = Set<AnyCancellable>()
private var currentResolveTask: Task<Void, Never>?

/// Should not be called externally, use `ConfidenceFeatureProvider.Builder`or init with `Confidence` instead.
init(
Expand Down Expand Up @@ -91,30 +92,30 @@ public class ConfidenceFeatureProvider: FeatureProvider {

let context = confidence?.getContext() ?? ConfidenceTypeMapper.from(ctx: initialContext)

resolve(strategy: initializationStrategy, context: context)
Task {
await resolve(strategy: initializationStrategy, context: context)
}
self.startListentingForContextChanges()
}

private func resolve(strategy: InitializationStrategy, context: ConfidenceStruct) {
Task {
do {
let resolveResult = try await client.resolve(ctx: context)

// update cache with stored values
try await store(
with: context,
resolveResult: resolveResult,
refreshCache: strategy == .fetchAndActivate
)
private func resolve(strategy: InitializationStrategy, context: ConfidenceStruct) async {
do {
let resolveResult = try await client.resolve(ctx: context)

// signal the provider is ready after the network request is done
if strategy == .fetchAndActivate {
eventHandler.send(.ready)
}
} catch {
// We emit a ready event as the provider is ready, but is using default / cache values.
// update cache with stored values
try await store(
with: context,
resolveResult: resolveResult,
refreshCache: strategy == .fetchAndActivate
)

// signal the provider is ready after the network request is done
if strategy == .fetchAndActivate {
eventHandler.send(.ready)
}
} catch {
// We emit a ready event as the provider is ready, but is using default / cache values.
eventHandler.send(.ready)
}
}

Expand All @@ -123,6 +124,7 @@ public class ConfidenceFeatureProvider: FeatureProvider {
cancellable.cancel()
}
cancellables.removeAll()
currentResolveTask?.cancel()
}

private func store(
Expand All @@ -147,7 +149,9 @@ public class ConfidenceFeatureProvider: FeatureProvider {
newContext: OpenFeature.EvaluationContext
) {
if confidence == nil {
self.resolve(strategy: .fetchAndActivate, context: ConfidenceTypeMapper.from(ctx: newContext))
Task {
await resolve(strategy: .fetchAndActivate, context: ConfidenceTypeMapper.from(ctx: newContext))
}
return
}

Expand All @@ -163,12 +167,17 @@ public class ConfidenceFeatureProvider: FeatureProvider {
guard let confidence = confidence else {
return
}

confidence.contextChanges()
.sink { [weak self] context in
guard let self = self else {
return
}
self.resolve(strategy: self.initializationStrategy, context: context)

currentResolveTask?.cancel()
currentResolveTask = Task {
await self.resolve(strategy: .fetchAndActivate, context: context)
}
}
.store(in: &cancellables)
}
Expand Down
50 changes: 36 additions & 14 deletions Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Foundation
import Confidence
import OpenFeature
import Combine
import XCTest

@testable import ConfidenceProvider
Expand All @@ -24,6 +25,41 @@ class ConfidenceFeatureProviderTest: XCTestCase {
super.setUp()
}

func testSlowFirstResolveWillbeCancelledOnSecondResolve() async throws {
class FakeClient: ConfidenceResolveClient {
var callCount = 0
var returnCount = 0

func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult {
callCount += 1
if callCount == 1 {
// wait 2 seconds
try await Task.sleep(nanoseconds: UInt64(1 * Double(NSEC_PER_MSEC)))
returnCount += 1
} else {
returnCount += 1
}

return .init(resolvedValues: [], resolveToken: "")
}
}

let confidence = Confidence.Builder.init(clientSecret: "").build()
let client = FakeClient()
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)
try await Task.sleep(nanoseconds: UInt64(2 * Double(NSEC_PER_MSEC)))
client.callCount = 0
client.returnCount = 0
confidence.putContext(key: "new", value: ConfidenceValue(string: "value"))
confidence.putContext(key: "new2", value: ConfidenceValue(string: "value2"))
try await Task.sleep(nanoseconds: UInt64(2 * Double(NSEC_PER_MSEC)))
XCTAssertEqual(2, client.callCount)
XCTAssertEqual(1, client.returnCount)
}

func testRefresh() throws {
var session = MockedResolveClientURLProtocol.mockedSession(flags: [:])
let provider =
Expand Down Expand Up @@ -1089,18 +1125,4 @@ final class DispatchQueueFake: DispatchQueueType {
work()
}
}

final class DispatchQueueFakeSlow: DispatchQueueType {
var expectation: XCTestExpectation
init(expectation: XCTestExpectation) {
self.expectation = expectation
}
func async(execute work: @escaping @convention(block) () -> Void) {
Task {
try await Task.sleep(nanoseconds: 1 * 1_000_000_000)
work()
expectation.fulfill()
}
}
}
// swiftlint:enable type_body_length

0 comments on commit 56287cb

Please sign in to comment.