Skip to content

Commit ccd0545

Browse files
authored
fix(Analytics): Fixing crash when attempting to submit events while a previous submission is in progress (#3331)
1 parent 44009a9 commit ccd0545

File tree

5 files changed

+45
-33
lines changed

5 files changed

+45
-33
lines changed

AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/AnalyticsClient.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public protocol AnalyticsClientBehaviour: Actor {
2323
func removeGlobalAttribute(forKey key: String, forEventType eventType: String)
2424
func removeGlobalMetric(forKey key: String)
2525
func removeGlobalMetric(forKey key: String, forEventType eventType: String)
26-
func record(_ event: PinpointEvent) throws
26+
func record(_ event: PinpointEvent) async throws
2727

2828
func setRemoteGlobalAttributes(_ attributes: [String: String])
2929
func removeAllRemoteGlobalAttributes()
@@ -231,7 +231,7 @@ actor AnalyticsClient: AnalyticsClientBehaviour {
231231
session: sessionProvider())
232232
}
233233

234-
func record(_ event: PinpointEvent) throws {
234+
func record(_ event: PinpointEvent) async throws {
235235
// Add event type attributes
236236
if let eventAttributes = eventTypeAttributes[event.eventType] {
237237
for (key, attribute) in eventAttributes {
@@ -256,7 +256,7 @@ actor AnalyticsClient: AnalyticsClientBehaviour {
256256
event.addMetric(metric, forKey: key)
257257
}
258258

259-
try eventRecorder.save(event)
259+
try await eventRecorder.save(event)
260260
}
261261

262262
@discardableResult

AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/EventRecorder.swift

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import enum AwsCommonRuntimeKit.CommonRunTimeError
1313
import Foundation
1414

1515
/// AnalyticsEventRecording saves and submits pinpoint events
16-
protocol AnalyticsEventRecording {
17-
var pinpointClient: PinpointClientProtocol { get }
16+
protocol AnalyticsEventRecording: Actor {
17+
nonisolated var pinpointClient: PinpointClientProtocol { get }
1818

1919
/// Saves a pinpoint event to storage
2020
/// - Parameter event: A PinpointEvent
@@ -35,12 +35,13 @@ protocol AnalyticsEventRecording {
3535
}
3636

3737
/// An AnalyticsEventRecording implementation that stores and submits pinpoint events
38-
class EventRecorder: AnalyticsEventRecording {
39-
let appId: String
40-
let storage: AnalyticsEventStorage
41-
let pinpointClient: PinpointClientProtocol
42-
let endpointClient: EndpointClientBehaviour
38+
actor EventRecorder: AnalyticsEventRecording {
39+
private let appId: String
40+
private let storage: AnalyticsEventStorage
4341
private var submittedEvents: [PinpointEvent] = []
42+
private var submissionTask: Task<[PinpointEvent], Error>?
43+
nonisolated let endpointClient: EndpointClientBehaviour
44+
nonisolated let pinpointClient: PinpointClientProtocol
4445

4546
/// Initializer for Event Recorder
4647
/// - Parameters:
@@ -66,31 +67,37 @@ class EventRecorder: AnalyticsEventRecording {
6667
func save(_ event: PinpointEvent) throws {
6768
log.verbose("saveEvent: \(event)")
6869
try storage.saveEvent(event)
69-
try self.storage.checkDiskSize(limit: Constants.pinpointClientByteLimitDefault)
70+
try storage.checkDiskSize(limit: Constants.pinpointClientByteLimitDefault)
7071
}
7172

7273
func updateAttributesOfEvents(ofType eventType: String,
7374
withSessionId sessionId: PinpointSession.SessionId,
7475
setAttributes attributes: [String: String]) throws {
75-
try self.storage.updateEvents(ofType: eventType,
76+
try storage.updateEvents(ofType: eventType,
7677
withSessionId: sessionId,
7778
setAttributes: attributes)
7879
}
7980

80-
/// Submit all locally stored events in batches
81-
/// If event submission fails, the event retry count is increment otherwise event is marked dirty and available for deletion in the local storage if retry count exceeds 3
82-
/// If event submission succeeds, the event is removed from local storage
81+
/// Submit all locally stored events in batches. If a previous submission is in progress, it waits until it's completed before proceeding.
82+
/// When the submission for an event is accepted, the event is removed from local storage
83+
/// When the submission for an event is rejected, the event retry count is incremented in the local storage. Events that exceed the maximum retry count (3) are purged.
8384
/// - Returns: A collection of events submitted to Pinpoint
8485
func submitAllEvents() async throws -> [PinpointEvent] {
85-
submittedEvents = []
86-
let eventsBatch = try getBatchRecords()
87-
if eventsBatch.count > 0 {
88-
let endpointProfile = await endpointClient.currentEndpointProfile()
89-
try await processBatch(eventsBatch, endpointProfile: endpointProfile)
90-
} else {
91-
log.verbose("No events to submit")
86+
let task = Task { [submissionTask] in
87+
// Wait for the previous submission to complete, regardless of its result
88+
_ = try? await submissionTask?.value
89+
submittedEvents = []
90+
let eventsBatch = try getBatchRecords()
91+
if eventsBatch.count > 0 {
92+
let endpointProfile = await endpointClient.currentEndpointProfile()
93+
try await processBatch(eventsBatch, endpointProfile: endpointProfile)
94+
} else {
95+
log.verbose("No events to submit")
96+
}
97+
return submittedEvents
9298
}
93-
return submittedEvents
99+
submissionTask = task
100+
return try await task.value
94101
}
95102

96103
private func getBatchRecords() throws -> [PinpointEvent] {
@@ -343,7 +350,7 @@ extension EventRecorder: DefaultLogger {
343350
public static var log: Logger {
344351
Amplify.Logging.logger(forCategory: CategoryType.analytics.displayName, forNamespace: String(describing: self))
345352
}
346-
public var log: Logger {
353+
nonisolated public var log: Logger {
347354
Self.log
348355
}
349356
}

AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/AnalyticsClientTests.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ class AnalyticsClientTests: XCTestCase {
8585

8686
do {
8787
try await analyticsClient.record(event)
88-
XCTAssertEqual(eventRecorder.saveCount, 1)
89-
guard let savedEvent = eventRecorder.lastSavedEvent else {
88+
let saveCount = await eventRecorder.saveCount
89+
XCTAssertEqual(saveCount, 1)
90+
guard let savedEvent = await eventRecorder.lastSavedEvent else {
9091
XCTFail("Expected saved event")
9192
return
9293
}
@@ -118,8 +119,9 @@ class AnalyticsClientTests: XCTestCase {
118119

119120
do {
120121
try await analyticsClient.record(event)
121-
XCTAssertEqual(eventRecorder.saveCount, 1)
122-
guard let savedEvent = eventRecorder.lastSavedEvent else {
122+
let saveCount = await eventRecorder.saveCount
123+
XCTAssertEqual(saveCount, 1)
124+
guard let savedEvent = await eventRecorder.lastSavedEvent else {
123125
XCTFail("Expected saved event")
124126
return
125127
}
@@ -142,7 +144,8 @@ class AnalyticsClientTests: XCTestCase {
142144
func testSubmit() async {
143145
do {
144146
try await analyticsClient.submitEvents()
145-
XCTAssertEqual(eventRecorder.submitCount, 1)
147+
let submitCount = await eventRecorder.submitCount
148+
XCTAssertEqual(submitCount, 1)
146149
} catch {
147150
XCTFail("Unexpected exception while attempting to submit events")
148151
}

AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/EventRecorderTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ class EventRecorderTests: XCTestCase {
4848
/// - Given: a event recorder
4949
/// - When: a new pinpoint event is aved
5050
/// - Then: the event is saved to storage followed by a disk size check
51-
func testSaveEvent() {
51+
func testSaveEvent() async {
5252
let session = PinpointSession(sessionId: "1", startTime: Date(), stopTime: nil)
5353
let event = PinpointEvent(id: "1", eventType: "eventType", eventDate: Date(), session: session)
5454

5555
XCTAssertEqual(storage.events.count, 0)
5656
XCTAssertEqual(storage.checkDiskSizeCallCount, 1)
5757

5858
do {
59-
try recorder.save(event)
59+
try await recorder.save(event)
6060
} catch {
6161
XCTFail("Failed to save events")
6262
}

AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/Mocks/MockEventRecorder.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import AWSPinpoint
1010
@_spi(InternalAWSPinpoint) @testable import InternalAWSPinpoint
1111

12-
class MockEventRecorder: AnalyticsEventRecording {
13-
var pinpointClient: PinpointClientProtocol = MockPinpointClient()
12+
actor MockEventRecorder: AnalyticsEventRecording {
13+
nonisolated var pinpointClient: PinpointClientProtocol {
14+
MockPinpointClient()
15+
}
1416

1517
var saveCount = 0
1618
var lastSavedEvent: PinpointEvent?

0 commit comments

Comments
 (0)