Skip to content

Commit 0a1c1ef

Browse files
author
Brennan Stehling
committed
handles race condition and coordinates with tests for handling behavior
1 parent 5910c2e commit 0a1c1ef

File tree

2 files changed

+54
-9
lines changed

2 files changed

+54
-9
lines changed

Diff for: AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageBackgroundEventsRegistry.swift

+16-2
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@
77

88
import Foundation
99

10+
extension Notification.Name {
11+
static let StorageBackgroundEventsRegistryWaiting = Notification.Name("StorageBackgroundEventsRegistryWaiting")
12+
}
13+
1014
/// Background events registry.
1115
///
1216
/// Discussion:
1317
/// Multiple URLSession instances could be running background events with their own unique identifier. Those can be run
1418
/// independently of the Amplify Storage plugin and this function will indiciate if it will handle the given identifier.
15-
class StorageBackgroundEventsRegistry {
19+
actor StorageBackgroundEventsRegistry {
1620
typealias StorageBackgroundEventsContinuation = CheckedContinuation<Bool, Never>
1721
static var identifier: String?
1822
static var continuation: StorageBackgroundEventsContinuation?
1923

24+
// override for use with unit tests
25+
static var notificationCenter: NotificationCenter?
26+
2027
/// Handles background events for URLSession on iOS.
2128
/// - Parameters:
2229
/// - identifier: session identifier
@@ -27,10 +34,17 @@ class StorageBackgroundEventsRegistry {
2734

2835
return await withCheckedContinuation { (continuation: CheckedContinuation<Bool, Never>) in
2936
self.continuation = continuation
37+
notifyWaiting(for: identifier)
3038
}
3139
}
3240

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

3549
// The storage plugin will register the session identifier when it is configured.
3650
static func register(identifier: String) {

Diff for: AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageBackgroundEventsRegistryTests.swift

+38-7
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,49 @@ class StorageBackgroundEventsRegistryTests: XCTestCase {
1818
let otherIdentifier = UUID().uuidString
1919
StorageBackgroundEventsRegistry.register(identifier: identifier)
2020

21-
let done = asyncExpectation(description: "done", expectedFulfillmentCount: 2)
21+
let notificationCenter = NotificationCenter()
22+
StorageBackgroundEventsRegistry.notificationCenter = notificationCenter
23+
defer {
24+
StorageBackgroundEventsRegistry.notificationCenter = nil
25+
}
26+
27+
let done = asyncExpectation(description: "done")
28+
let waiting = asyncExpectation(description: "waiting")
29+
30+
notificationCenter.addObserver(forName: Notification.Name.StorageBackgroundEventsRegistryWaiting, object: nil, queue: nil) { notification in
31+
guard let notificationIdentifier = notification.object as? String else {
32+
XCTFail("Identifier not defined")
33+
return
34+
}
35+
XCTAssertEqual(notificationIdentifier, identifier)
36+
Task {
37+
await waiting.fulfill()
38+
}
39+
}
2240

2341
Task {
2442
let handled = await StorageBackgroundEventsRegistry.handleEventsForBackgroundURLSession(identifier: identifier)
2543
await done.fulfill()
2644
XCTAssertTrue(handled)
2745
}
2846

47+
await waitForExpectations([waiting])
48+
49+
let didContinue = await handleEvents(for: identifier)
50+
XCTAssertTrue(didContinue)
51+
await waitForExpectations([done])
52+
53+
let otherDone = asyncExpectation(description: "other done")
54+
2955
Task {
3056
let otherHandled = await StorageBackgroundEventsRegistry.handleEventsForBackgroundURLSession(identifier: otherIdentifier)
31-
await done.fulfill()
57+
await otherDone.fulfill()
3258
XCTAssertFalse(otherHandled)
3359
}
3460

35-
handleEvents(for: identifier)
36-
handleEvents(for: otherIdentifier)
37-
38-
await waitForExpectations([done])
61+
let didNotContinue = await handleEvents(for: otherIdentifier)
62+
XCTAssertFalse(didNotContinue)
63+
await waitForExpectations([otherDone])
3964
}
4065

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

5782
// Simulates URLSessionDelegate behavior
58-
func handleEvents(for identifier: String) {
83+
func handleEvents(for identifier: String) async -> Bool {
84+
await Task.yield()
85+
5986
if let continuation = StorageBackgroundEventsRegistry.getContinuation(for: identifier) {
6087
continuation.resume(returning: true)
6188
StorageBackgroundEventsRegistry.removeContinuation(for: identifier)
89+
return true
90+
} else {
91+
print("No continuation for identifier: \(identifier)")
92+
return false
6293
}
6394
}
6495

0 commit comments

Comments
 (0)