Skip to content

Commit 9d62ab3

Browse files
authored
Resolve a possible race condition (#8512)
Pass the session details as a part of the session updated notification. This avoids the sessionDetails object missing race condition when trying to update the session details to the traces. Attempt to fix #8495.
1 parent 33a5c20 commit 9d62ab3

File tree

7 files changed

+82
-26
lines changed

7 files changed

+82
-26
lines changed

FirebasePerformance/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Version 8.6.0
22
* Add Firebase Performance support for Swift Package Manager. (#6528)
3+
* Fix a crash due to a race condition. (#8485)
34

45
# Version 8.2.0
56
* Update log messages with proper log levels.

FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
2222
@interface FPRSessionManager ()
2323

2424
/** The current active session managed by the session manager. Modifiable for unit tests */
25-
@property(nonatomic, nullable, readwrite) FPRSessionDetails *sessionDetails;
25+
@property(atomic, nullable, readwrite) FPRSessionDetails *sessionDetails;
2626

2727
/**
2828
* Checks if the currently active session is beyond maximum allowed time. If so renew the session,

FirebasePerformance/Sources/AppActivity/FPRSessionManager.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@
1919
/* Notification name when the session Id gets updated. */
2020
FOUNDATION_EXTERN NSString *_Nonnull const kFPRSessionIdUpdatedNotification;
2121

22+
/* Notification name when the session Id gets updated. */
23+
FOUNDATION_EXTERN NSString *_Nonnull const kFPRSessionIdNotificationKey;
24+
2225
/** This class manages the current active sessionId of the application and provides mechanism for
2326
* propagating the session Id.
2427
*/
2528
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
2629
@interface FPRSessionManager : NSObject
2730

2831
/** The current active session managed by the session manager. */
29-
@property(nonatomic, readonly, nonnull) FPRSessionDetails *sessionDetails;
32+
@property(atomic, readonly, nonnull) FPRSessionDetails *sessionDetails;
3033

3134
/**
3235
* The notification center managed by the session manager. All the notifications by the session

FirebasePerformance/Sources/AppActivity/FPRSessionManager.m

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#import <UIKit/UIKit.h>
2323

2424
NSString *const kFPRSessionIdUpdatedNotification = @"kFPRSessionIdUpdatedNotification";
25+
NSString *const kFPRSessionIdNotificationKey = @"kFPRSessionIdNotificationKey";
2526

2627
@interface FPRSessionManager ()
2728

@@ -104,8 +105,12 @@ - (void)updateSessionId:(NSNotification *)notification {
104105
FPRSessionDetails *sessionInfo = [[FPRSessionDetails alloc] initWithSessionId:sessionIdString
105106
options:sessionOptions];
106107
self.sessionDetails = sessionInfo;
108+
NSMutableDictionary<NSString *, FPRSessionDetails *> *userInfo =
109+
[[NSMutableDictionary alloc] init];
110+
[userInfo setObject:sessionInfo forKey:kFPRSessionIdNotificationKey];
107111
[self.sessionNotificationCenter postNotificationName:kFPRSessionIdUpdatedNotification
108-
object:self];
112+
object:self
113+
userInfo:[userInfo copy]];
109114
}
110115

111116
/**

FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ @interface FPRNetworkTrace ()
5050
/** @brief Serial queue to manage the updation of session Ids. */
5151
@property(nonatomic, readwrite) dispatch_queue_t sessionIdSerialQueue;
5252

53-
/** Updates the current trace with the current session details. */
54-
- (void)updateTraceWithCurrentSession;
53+
/**
54+
* Updates the current trace with the current session details.
55+
* @param sessionDetails Updated session details of the currently active session.
56+
*/
57+
- (void)updateTraceWithCurrentSession:(FPRSessionDetails *)sessionDetails;
5558

5659
@end
5760

@@ -137,14 +140,20 @@ - (NSString *)description {
137140
return [NSString stringWithFormat:@"Request: %@", _URLRequest];
138141
}
139142

140-
- (void)updateTraceWithCurrentSession {
143+
- (void)sessionChanged:(NSNotification *)notification {
141144
if (self.traceStarted && !self.traceCompleted) {
142-
dispatch_async(self.sessionIdSerialQueue, ^{
143-
FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
144-
FPRSessionDetails *sessionDetails = [sessionManager.sessionDetails copy];
145-
if (sessionDetails) {
146-
[self.activeSessions addObject:sessionDetails];
147-
}
145+
NSDictionary<NSString *, FPRSessionDetails *> *userInfo = notification.userInfo;
146+
FPRSessionDetails *sessionDetails = [userInfo valueForKey:kFPRSessionIdNotificationKey];
147+
if (sessionDetails) {
148+
[self updateTraceWithCurrentSession:sessionDetails];
149+
}
150+
}
151+
}
152+
153+
- (void)updateTraceWithCurrentSession:(FPRSessionDetails *)sessionDetails {
154+
if (sessionDetails != nil) {
155+
dispatch_sync(self.sessionIdSerialQueue, ^{
156+
[self.activeSessions addObject:sessionDetails];
148157
});
149158
}
150159
}
@@ -191,9 +200,9 @@ - (void)start {
191200
[self checkpointState:FPRNetworkTraceCheckpointStateInitiated];
192201

193202
FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
194-
[self updateTraceWithCurrentSession];
203+
[self updateTraceWithCurrentSession:[sessionManager.sessionDetails copy]];
195204
[sessionManager.sessionNotificationCenter addObserver:self
196-
selector:@selector(updateTraceWithCurrentSession)
205+
selector:@selector(sessionChanged:)
197206
name:kFPRSessionIdUpdatedNotification
198207
object:sessionManager];
199208
}

FirebasePerformance/Sources/Timer/FIRTrace.m

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ @interface FIRTrace ()
4949
/** Stops an active stage that is currently active. */
5050
- (void)stopActiveStage;
5151

52-
/** Updates the current trace with the session id. */
53-
- (void)updateTraceWithSessionId;
52+
/**
53+
* Updates the current trace with the session id.
54+
* @param sessionDetails Updated session details of the currently active session.
55+
*/
56+
- (void)updateTraceWithSessionId:(FPRSessionDetails *)sessionDetails;
5457

5558
@end
5659

@@ -139,9 +142,9 @@ - (void)start {
139142
self.backgroundActivityTracker = [[FPRTraceBackgroundActivityTracker alloc] init];
140143
FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
141144
if (!self.isStage) {
142-
[self updateTraceWithSessionId];
145+
[self updateTraceWithSessionId:[sessionManager.sessionDetails copy]];
143146
[sessionManager.sessionNotificationCenter addObserver:self
144-
selector:@selector(updateTraceWithSessionId)
147+
selector:@selector(sessionChanged:)
145148
name:kFPRSessionIdUpdatedNotification
146149
object:sessionManager];
147150
}
@@ -400,18 +403,22 @@ - (void)removeAttribute:(NSString *)attribute {
400403

401404
#pragma mark - Utility methods
402405

403-
- (void)updateTraceWithSessionId {
406+
- (void)sessionChanged:(NSNotification *)notification {
404407
if ([self isTraceActive]) {
405-
dispatch_async(self.sessionIdSerialQueue, ^{
406-
FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
407-
FPRSessionDetails *sessionDetails = [sessionManager.sessionDetails copy];
408-
if (sessionDetails) {
409-
[self.activeSessions addObject:sessionDetails];
410-
}
411-
});
408+
NSDictionary<NSString *, FPRSessionDetails *> *userInfo = notification.userInfo;
409+
FPRSessionDetails *sessionDetails = [userInfo valueForKey:kFPRSessionIdNotificationKey];
410+
if (sessionDetails) {
411+
[self updateTraceWithSessionId:sessionDetails];
412+
}
412413
}
413414
}
414415

416+
- (void)updateTraceWithSessionId:(FPRSessionDetails *)sessionDetails {
417+
dispatch_sync(self.sessionIdSerialQueue, ^{
418+
[self.activeSessions addObject:sessionDetails];
419+
});
420+
}
421+
415422
- (BOOL)isTraceStarted {
416423
return self.startTime != nil;
417424
}

FirebasePerformance/Tests/Unit/FPRSessionManagerTest.m

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,35 @@ - (void)testSessionIdUpdationThrowsNotification {
9191
XCTAssertNotEqual(sessionId, instance.sessionDetails.sessionId);
9292
}
9393

94+
/** Validate that sessionId changes sends notifications with the session details. */
95+
- (void)testSessionIdUpdationSendsNotificationWithSessionDetails {
96+
FPRSessionManager *instance = [FPRSessionManager sharedInstance];
97+
[instance startTrackingAppStateChanges];
98+
NSString *sessionId = instance.sessionDetails.sessionId;
99+
100+
__block BOOL containsSessionDetails = NO;
101+
__block FPRSessionDetails *updatedSessionDetails = nil;
102+
[instance.sessionNotificationCenter
103+
addObserverForName:kFPRSessionIdUpdatedNotification
104+
object:instance
105+
queue:[NSOperationQueue mainQueue]
106+
usingBlock:^(NSNotification *note) {
107+
NSDictionary<NSString *, FPRSessionDetails *> *userInfo = note.userInfo;
108+
FPRSessionDetails *sessionDetails =
109+
[userInfo valueForKey:kFPRSessionIdNotificationKey];
110+
if (sessionDetails != nil) {
111+
containsSessionDetails = YES;
112+
updatedSessionDetails = sessionDetails;
113+
}
114+
}];
115+
116+
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
117+
[notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification
118+
object:[UIApplication sharedApplication]];
119+
120+
XCTAssertTrue(containsSessionDetails);
121+
XCTAssertNotEqual(sessionId, instance.sessionDetails.sessionId);
122+
XCTAssertEqual(updatedSessionDetails.sessionId, instance.sessionDetails.sessionId);
123+
}
124+
94125
@end

0 commit comments

Comments
 (0)