Skip to content

Commit f17995d

Browse files
committed
fix: prevent race condition crash in FPRTraceBackgroundActivityTracker
Replace async notification observer registration with synchronous registration to eliminate timing-dependent crashes when objects are deallocated quickly. Fixes #14273
1 parent 962bb60 commit f17995d

File tree

2 files changed

+89
-11
lines changed

2 files changed

+89
-11
lines changed

FirebasePerformance/Sources/AppActivity/FPRTraceBackgroundActivityTracker.m

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,29 @@ - (instancetype)init {
3535
} else {
3636
_traceBackgroundState = FPRTraceStateForegroundOnly;
3737
}
38-
dispatch_async(dispatch_get_main_queue(), ^{
39-
[[NSNotificationCenter defaultCenter] addObserver:self
40-
selector:@selector(applicationDidBecomeActive:)
41-
name:UIApplicationDidBecomeActiveNotification
42-
object:[UIApplication sharedApplication]];
43-
44-
[[NSNotificationCenter defaultCenter] addObserver:self
45-
selector:@selector(applicationDidEnterBackground:)
46-
name:UIApplicationDidEnterBackgroundNotification
47-
object:[UIApplication sharedApplication]];
48-
});
38+
if ([NSThread isMainThread]) {
39+
[self registerNotificationObservers];
40+
} else {
41+
dispatch_sync(dispatch_get_main_queue(), ^{
42+
[self registerNotificationObservers];
43+
});
44+
}
4945
}
5046
return self;
5147
}
5248

49+
- (void)registerNotificationObservers {
50+
[[NSNotificationCenter defaultCenter] addObserver:self
51+
selector:@selector(applicationDidBecomeActive:)
52+
name:UIApplicationDidBecomeActiveNotification
53+
object:[UIApplication sharedApplication]];
54+
55+
[[NSNotificationCenter defaultCenter] addObserver:self
56+
selector:@selector(applicationDidEnterBackground:)
57+
name:UIApplicationDidEnterBackgroundNotification
58+
object:[UIApplication sharedApplication]];
59+
}
60+
5361
- (void)dealloc {
5462
// Remove all the notification observers registered.
5563
[[NSNotificationCenter defaultCenter] removeObserver:self];

FirebasePerformance/Tests/Unit/FPRTraceBackgroundActivityTrackerTest.m

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,74 @@ - (void)testBackgroundTracking {
6161
}];
6262
}
6363

64+
/** Tests that synchronous observer registration works correctly and observers are immediately available. */
65+
- (void)testObservers_synchronousRegistrationAddsObserver {
66+
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
67+
FPRTraceBackgroundActivityTracker *tracker = [[FPRTraceBackgroundActivityTracker alloc] init];
68+
XCTAssertNotNil(tracker);
69+
70+
[notificationCenter postNotificationName:UIApplicationDidBecomeActiveNotification
71+
object:[UIApplication sharedApplication]];
72+
XCTAssertEqual(tracker.traceBackgroundState, FPRTraceStateForegroundOnly);
73+
74+
tracker = nil;
75+
XCTAssertNil(tracker);
76+
XCTAssertNoThrow([notificationCenter postNotificationName:UIApplicationDidBecomeActiveNotification
77+
object:[UIApplication sharedApplication]]);
78+
XCTAssertNoThrow([notificationCenter postNotificationName:UIApplicationDidEnterBackgroundNotification
79+
object:[UIApplication sharedApplication]]);
80+
}
81+
82+
/** Tests rapid creation and deallocation to verify race condition fix. */
83+
- (void)testRapidCreationAndDeallocation_noRaceCondition {
84+
for (int i = 0; i < 100; i++) {
85+
@autoreleasepool {
86+
FPRTraceBackgroundActivityTracker *tracker = [[FPRTraceBackgroundActivityTracker alloc] init];
87+
XCTAssertNotNil(tracker);
88+
89+
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification
90+
object:[UIApplication sharedApplication]];
91+
}
92+
}
93+
94+
XCTAssertNoThrow([[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification
95+
object:[UIApplication sharedApplication]]);
96+
XCTAssertNoThrow([[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification
97+
object:[UIApplication sharedApplication]]);
98+
}
99+
100+
/** Tests that observers are registered immediately after init on main thread. */
101+
- (void)testObservers_immediateRegistrationOnMainThread {
102+
XCTAssertTrue([NSThread isMainThread]);
103+
104+
FPRTraceBackgroundActivityTracker *tracker = [[FPRTraceBackgroundActivityTracker alloc] init];
105+
106+
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification
107+
object:[UIApplication sharedApplication]];
108+
109+
XCTAssertEqual(tracker.traceBackgroundState, FPRTraceStateForegroundOnly);
110+
}
111+
112+
/** Tests observer registration when created from background thread. */
113+
- (void)testObservers_registrationFromBackgroundThread {
114+
XCTestExpectation *expectation = [self expectationWithDescription:@"Background thread creation"];
115+
116+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
117+
FPRTraceBackgroundActivityTracker *tracker = [[FPRTraceBackgroundActivityTracker alloc] init];
118+
XCTAssertNotNil(tracker);
119+
120+
dispatch_async(dispatch_get_main_queue(), ^{
121+
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification
122+
object:[UIApplication sharedApplication]];
123+
124+
XCTAssertEqual(tracker.traceBackgroundState, FPRTraceStateForegroundOnly);
125+
[expectation fulfill];
126+
});
127+
});
128+
129+
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
130+
XCTAssertNil(error, @"Test timed out");
131+
}];
132+
}
133+
64134
@end

0 commit comments

Comments
 (0)