Skip to content

Conversation

JesusRojass
Copy link
Contributor

Replace async notification observer registration with synchronous registration
to eliminate timing-dependent crashes when objects are deallocated quickly.

Fixes #14273 (Link)

Discussion

The FPRTraceBackgroundActivityTracker class had a race condition during initialization where notification observers were registered asynchronously using dispatch_async(dispatch_get_main_queue()). This created a timing window where the tracker object could be deallocated before the async block executed, causing crashes when the block tried to call addObserver: with a deallocated self reference.

The crash occurred when:

  • FPRTraceBackgroundActivityTracker is created
  • Async observer registration is queued to main thread
  • Object is deallocated before async block executes
  • Async block runs with dangling self pointer → EXC_BAD_ACCESS

My solution involves to replace the asynchronous observer registration with synchronous registration to eliminate the race condition entirely

Testing

  • Added testRapidCreationAndDeallocation_noRaceCondition to verify fix
  • Added thread safety tests for background thread initialization
  • Added crash reproduction test class to demonstrate original bug
  • All existing tests continue to pass

API Changes

  • No API Changes

Replace async notification observer registration with synchronous registration
to eliminate timing-dependent crashes when objects are deallocated quickly.

Fixes firebase#14273
@JesusRojass
Copy link
Contributor Author

JesusRojass commented Oct 6, 2025

@visumickey Created PR Here

@JesusRojass JesusRojass marked this pull request as ready for review October 6, 2025 05:52
@JesusRojass
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses a critical race condition in FPRTraceBackgroundActivityTracker by switching from asynchronous to synchronous notification observer registration. The solution is robust, handling initialization from both main and background threads correctly to prevent deadlocks. The accompanying tests are comprehensive, not only verifying the fix under race conditions but also including a clear reproduction case for the original bug, which is excellent for future reference. The code is clean and the changes are well-explained. I have one minor suggestion in the tests to improve clarity.

@JesusRojass
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Excellent work on this pull request. You've correctly identified and fixed a critical race condition in FPRTraceBackgroundActivityTracker by replacing the asynchronous observer registration with a synchronous approach. The use of if ([NSThread isMainThread]) followed by dispatch_sync is a robust pattern that ensures thread safety and prevents the deallocation issue. The new unit tests are thorough and provide great coverage for the fix, including a specific test to reproduce and verify the absence of the race condition. I have one minor suggestion regarding code style for better maintainability.

@JesusRojass
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively resolves a race condition crash in FPRTraceBackgroundActivityTracker by replacing asynchronous notification observer registration with a synchronous approach. The implementation correctly handles object initialization from both main and background threads, and the new unit tests are comprehensive, verifying the fix under various conditions, including rapid allocation/deallocation cycles. I have one minor suggestion to consolidate a redundant test case, but overall this is an excellent fix.

Comment on lines +106 to +116
- (void)testObservers_immediateRegistrationOnMainThread {
XCTAssertTrue([NSThread isMainThread]);

FPRTraceBackgroundActivityTracker *tracker = [[FPRTraceBackgroundActivityTracker alloc] init];

[[NSNotificationCenter defaultCenter]
postNotificationName:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];

XCTAssertEqual(tracker.traceBackgroundState, FPRTraceStateForegroundOnly);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

This test case, testObservers_immediateRegistrationOnMainThread, appears to be redundant. Its logic is already covered in the first half of the testObservers_synchronousRegistrationAddsObserver test (lines 66-73). To improve the conciseness of the test suite and reduce future maintenance, consider removing this test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT:We usually don't want to duplicate code if possible, if possible try to make it a single tests, so we don't have to maintain extra tests.

@JesusRojass
Copy link
Contributor Author

@visumickey you can review from here, I added some of the Gemini suggestions (though idk if they where any good)

lmk if anything!

if ([NSThread isMainThread]) {
[self registerNotificationObservers];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried about a potential deadlock situation here. If we are already in the main thread and doing a dispatch_sync, that is going to cause a deadlock. I think we should protect against that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x2, my understating of this issue is not on the threads but if the observers are still alive when the app transition from states, I was proposing something like

If (observers != nil){
//don't do anything
}
esle {
//Create them
}

WDYT @visumickey ??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@visumickey @eBlender

I have made a new commit addressing this block of code

The approach is to capture strong self during async observer registration so we can avoid messaging deallocated tracker instances when observers are registered asynchronously on the main queue

Just so self lives long enough to prevent another EXC_BAD_ACCESS crash

lmk what you think

Capture strong self during async observer registration
@JesusRojass
Copy link
Contributor Author

@visumickey Made new changes and added commit, I also re tested everything and it seems fine

Screenshot 2025-10-06 at 4 43 59 p m

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Crash at FPRTraceBackgroundActivityTracker.m
3 participants