Skip to content

Commit

Permalink
handles race condition and coordinates with tests for handling behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
Brennan Stehling committed Oct 19, 2022
1 parent 5910c2e commit 0a1c1ef
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@

import Foundation

extension Notification.Name {
static let StorageBackgroundEventsRegistryWaiting = Notification.Name("StorageBackgroundEventsRegistryWaiting")
}

/// Background events registry.
///
/// Discussion:
/// Multiple URLSession instances could be running background events with their own unique identifier. Those can be run
/// independently of the Amplify Storage plugin and this function will indiciate if it will handle the given identifier.
class StorageBackgroundEventsRegistry {
actor StorageBackgroundEventsRegistry {
typealias StorageBackgroundEventsContinuation = CheckedContinuation<Bool, Never>
static var identifier: String?
static var continuation: StorageBackgroundEventsContinuation?

// override for use with unit tests
static var notificationCenter: NotificationCenter?

/// Handles background events for URLSession on iOS.
/// - Parameters:
/// - identifier: session identifier
Expand All @@ -27,10 +34,17 @@ class StorageBackgroundEventsRegistry {

return await withCheckedContinuation { (continuation: CheckedContinuation<Bool, Never>) in
self.continuation = continuation
notifyWaiting(for: identifier)
}
}

// MARK: - Internal -
/// Notifies observes when waiting for continuation to be resumed.
/// - Parameters:
/// - identifier: session identifier
private static func notifyWaiting(for identifier: String) {
let notificationCenter = notificationCenter ?? NotificationCenter.default
notificationCenter.post(name: Notification.Name.StorageBackgroundEventsRegistryWaiting, object: identifier)
}

// The storage plugin will register the session identifier when it is configured.
static func register(identifier: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,49 @@ class StorageBackgroundEventsRegistryTests: XCTestCase {
let otherIdentifier = UUID().uuidString
StorageBackgroundEventsRegistry.register(identifier: identifier)

let done = asyncExpectation(description: "done", expectedFulfillmentCount: 2)
let notificationCenter = NotificationCenter()
StorageBackgroundEventsRegistry.notificationCenter = notificationCenter
defer {
StorageBackgroundEventsRegistry.notificationCenter = nil
}

let done = asyncExpectation(description: "done")
let waiting = asyncExpectation(description: "waiting")

notificationCenter.addObserver(forName: Notification.Name.StorageBackgroundEventsRegistryWaiting, object: nil, queue: nil) { notification in
guard let notificationIdentifier = notification.object as? String else {
XCTFail("Identifier not defined")
return
}
XCTAssertEqual(notificationIdentifier, identifier)
Task {
await waiting.fulfill()
}
}

Task {
let handled = await StorageBackgroundEventsRegistry.handleEventsForBackgroundURLSession(identifier: identifier)
await done.fulfill()
XCTAssertTrue(handled)
}

await waitForExpectations([waiting])

let didContinue = await handleEvents(for: identifier)
XCTAssertTrue(didContinue)
await waitForExpectations([done])

let otherDone = asyncExpectation(description: "other done")

Task {
let otherHandled = await StorageBackgroundEventsRegistry.handleEventsForBackgroundURLSession(identifier: otherIdentifier)
await done.fulfill()
await otherDone.fulfill()
XCTAssertFalse(otherHandled)
}

handleEvents(for: identifier)
handleEvents(for: otherIdentifier)

await waitForExpectations([done])
let didNotContinue = await handleEvents(for: otherIdentifier)
XCTAssertFalse(didNotContinue)
await waitForExpectations([otherDone])
}

func testHandlingUnregisteredIdentifier() async throws {
Expand All @@ -55,10 +80,16 @@ class StorageBackgroundEventsRegistryTests: XCTestCase {
}

// Simulates URLSessionDelegate behavior
func handleEvents(for identifier: String) {
func handleEvents(for identifier: String) async -> Bool {
await Task.yield()

if let continuation = StorageBackgroundEventsRegistry.getContinuation(for: identifier) {
continuation.resume(returning: true)
StorageBackgroundEventsRegistry.removeContinuation(for: identifier)
return true
} else {
print("No continuation for identifier: \(identifier)")
return false
}
}

Expand Down

0 comments on commit 0a1c1ef

Please sign in to comment.