Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/Misc/Concurrency/OperationDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ class OperationDispatcher {
}
}

@discardableResult
func dispatchOnWorkerThread(jitterableDelay delay: JitterableDelay = .none,
block: @escaping @Sendable () async -> Void) {
block: @escaping @Sendable () async -> Void) -> Task<Void, Never> {
Task.detached(priority: .background) {
if delay.hasDelay {
try? await Task.sleep(nanoseconds: DispatchTimeInterval(delay.random()).nanoseconds)
Expand Down
5 changes: 3 additions & 2 deletions Sources/Purchasing/Purchases/Purchases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1442,8 +1442,9 @@ public extension Purchases {
}

/// Used by `RevenueCatUI` to keep track of ``CustomerCenterEvent``s.
@_spi(Internal) func track(customerCenterEvent: any CustomerCenterEventType) {
operationDispatcher.dispatchOnWorkerThread {
@discardableResult
@_spi(Internal) func track(customerCenterEvent: any CustomerCenterEventType) -> Task<Void, Never> {
return operationDispatcher.dispatchOnWorkerThread {
// If we make CustomerCenterEventType implement FeatureEvent, we have to make FeatureEvent public
guard let event = customerCenterEvent as? FeatureEvent else { return }
await self.eventsManager?.track(featureEvent: event)
Expand Down
21 changes: 11 additions & 10 deletions Tests/BackendIntegrationTests/EventsManagerIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ final class EventsManagerIntegrationTests: BaseBackendIntegrationTests {
func testPostingCustomerCenterDoesNotFail() async throws {
let locale = Locale(identifier: "es_ES")

Purchases.shared.track(
let data = Self.customerCenterCreationData
let task0 = Purchases.shared.track(
customerCenterEvent: CustomerCenterEvent.impression(
Self.customerCenterCreationData,
data,
CustomerCenterEvent.Data(
locale: locale,
darkMode: true,
Expand All @@ -60,9 +61,9 @@ final class EventsManagerIntegrationTests: BaseBackendIntegrationTests {
)
)

Purchases.shared.track(
let task1 = Purchases.shared.track(
customerCenterEvent: CustomerCenterAnswerSubmittedEvent.answerSubmitted(
Self.customerCenterCreationData,
data,
CustomerCenterAnswerSubmittedEvent.Data(
locale: locale,
darkMode: true,
Expand All @@ -76,13 +77,13 @@ final class EventsManagerIntegrationTests: BaseBackendIntegrationTests {
)
)
// give background task a chance to run
await Task.yield()
_ = await (task0.value, task1.value)

try await self.logger.verifyMessageIsEventuallyLogged(
"Storing event:",
expectedCount: 2,
timeout: .seconds(3),
pollInterval: .seconds(1)
self.logger.verifyMessageWasLogged(
regexPattern: "Storing event:.*impression.*\(data.id.uuidString)"
)
self.logger.verifyMessageWasLogged(
regexPattern: "Storing event:.*answer_submitted.*\(data.id.uuidString)"
)

try await flushAndVerify(eventsCount: 2)
Expand Down
14 changes: 11 additions & 3 deletions Tests/UnitTests/Mocks/MockOperationDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,26 +80,31 @@ class MockOperationDispatcher: OperationDispatcher {
var invokedDispatchAsyncOnWorkerThreadCount = 0
var invokedDispatchAsyncOnWorkerThreadDelayParam: JitterableDelay?

@discardableResult
override func dispatchOnWorkerThread(
jitterableDelay delay: JitterableDelay = .none,
block: @escaping @Sendable () async -> Void
) {
) -> Task<Void, Never> {
self.invokedDispatchAsyncOnWorkerThreadDelayParam = delay
self.invokedDispatchAsyncOnWorkerThread = true
self.invokedDispatchAsyncOnWorkerThreadCount += 1

if self.forwardToOriginalDispatchOnWorkerThread {
super.dispatchOnWorkerThread(jitterableDelay: delay, block: block)
return super.dispatchOnWorkerThread(jitterableDelay: delay, block: block)
} else if self.shouldInvokeDispatchOnWorkerThreadBlock {
// We want to wait for the async task to finish before leaving this function
// Use a dispatch group to wait for the async task to finish
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()

let task: Task<Void, Never> = Task {
await block()
}

// Execute the async task on a background queue to avoid blocking
DispatchQueue.global(qos: .userInitiated).async {
Task {
await block()
await task.value
dispatchGroup.leave()
}
}
Expand All @@ -110,6 +115,9 @@ class MockOperationDispatcher: OperationDispatcher {
if result == .timedOut {
XCTFail("Dispatch on worker thread timed out")
}
return task
} else {
return super.dispatchOnWorkerThread(jitterableDelay: delay, block: block)
}
}

Expand Down