-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FCM: Fix crash on iOS 14 when multiple app delegate completion methods are called #6863
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed (or fixed any issues), please reply here with What to do if you already signed the CLAIndividual signers
Corporate signers
ℹ️ Googlers: Go here for more info. |
@googlebot I signed it! |
I am afraid there are some CI errors in GitHub Actions, seems like something unrelated to my change. Should I rebase it on your recent |
@mikeger Thanks for the PR! The CI failure is a result of GitHub Actions changing its default Xcode from 11.7 to 12.0.1 yesterday and being addressed in #6865. You can disregard for now. master is the correct target for this PR. Assigning for review to @maksymmalyhin for the GoogleUtilities changes and @chliangGoogle for the FirebaseMessaging changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea, thank you for your contribution!
I added a couple comments to adjust the logic a bit. Feel free to ask clarification questions as needed.
Also we will need to add unit tests for these changes before merging. Please let us know if you help with it.
@@ -882,17 +883,30 @@ - (void)application:(GULApplication *)application | |||
didReceiveRemoteNotificationWithCompletionIMP = | |||
[didReceiveRemoteNotificationWithCompletionIMPPointer pointerValue]; | |||
|
|||
dispatch_group_t __block callbackGroup = dispatch_group_create(); | |||
UIBackgroundFetchResult __block latestFetchResult = UIBackgroundFetchResultNoData; | |||
dispatch_group_notify(callbackGroup, dispatch_get_main_queue(), ^() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like calling dispatch_group_notify
on an empty group will lead to scheduling the block straight away. I think it should be moved to after line 916.
NSInvocation *invocation = [GULAppDelegateSwizzler | ||
appDelegateInvocationForSelector:methodSelector]; | ||
[invocation setTarget:interceptor]; | ||
[invocation setSelector:methodSelector]; | ||
[invocation setArgument:(void *)(&application) atIndex:2]; | ||
[invocation setArgument:(void *)(&userInfo) atIndex:3]; | ||
[invocation setArgument:(void *)(&completionHandler) atIndex:4]; | ||
[invocation setArgument:(void *)(&localCompletionHandler) | ||
atIndex:4]; | ||
[invocation invoke]; | ||
}]; | ||
// Call the real implementation if the real App Delegate has any. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation doesn't add the originally implemented method to the group. I think we should do the same for it: add enter/leave group calls and wrap the completion. Potentially we may use a single completion handler wrapper to be passed to all interceptors and the original implementations.
dispatch_group_enter(callbackGroup); | ||
void (^localCompletionHandler)(UIBackgroundFetchResult) = | ||
^void(UIBackgroundFetchResult fetchResult) { | ||
latestFetchResult = fetchResult; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can improve combining the fetch results. Perhaps if there is at least on UIBackgroundFetchResultNewData
the combined result should be also UIBackgroundFetchResultNewData
. Also I would pass UIBackgroundFetchResultFailed
only if all calls failed.
@maksymmalyhin thanks for the review, all valid comments. |
#6865 has merged so rebasing or merging master should now enable a green CI run. |
Hello. Are there any updates here? I'm facing exact same issue in Firebase 6.18, 6.31.1, 6.34.0 and even 7.0.0. However I'm not using Braze SDK. According to Crashlytics 80% of such crashes occur on iOS 14.1. Others on other versions of iOS 14. So I'm waiting for this fix in next release version of Firebase. BTW the project I'm supporting now is written on Objective C. |
@mikeger Will you have time to address the comments? We may finish your PR if not. We will make sure your contribution will be visible in Firebase repo. Please let us know. |
@maksymmalyhin hey, I would address the comments today. I got stuck since the tests ( |
@mikeger Ah,
|
@maksymmalyhin thanks so much for the testing tip, this allowed me to progress with the tests. Please re-review the changes, I updated it based on your comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thank you for addressing the comments! It mostly looks good to me. I added a couple minor comments. The comments are mostly optional and I'm fine proceeding with proceeding as is.
@chliangGoogle Could you please take a look at FCM part.
|
||
XCTestExpectation *completionExpectation = | ||
[[XCTestExpectation alloc] initWithDescription:@"Completion called once"]; | ||
completionExpectation.assertForOverFulfill = YES; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think this line is not required as YES
is the default value of the property
fetchCompletionHandler:completion]; | ||
testAppDelegate.remoteNotificationCompletionHandler(UIBackgroundFetchResultNewData); | ||
OCMVerifyAll(interceptor); | ||
[self waitForExpectations:@[ completionExpectation ] timeout:0.1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Looks like these 3 tests are mostly identical except the background fetch result. Could you please extract the test logic to a helper function like assertApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnceWithResult:(UIBackgroundFetchResult)
to reduce code duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it's paid by the number of lines
[self waitForExpectations:@[ completionExpectation ] timeout:0.1]; | ||
} | ||
|
||
- (void) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to add tests for cases with different fetch results or at least add a TODO
to add them.
@maksymmalyhin updated, thanks for the comments! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we've missed on place where we need to call the completion handler after the change: could you please add it to auth as well.
@maksymmalyhin added, to my understanding there it's sufficient to always indicate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM on messaging
All looks good to me, I'm going to merge the PR on CI green. |
@mikeger Thank you again for your contribution! We will try to include the fix into 7.1.0 release. |
…s are called (#6863) * Fix crash on iOS 14 when multiple completion methods are called * Fixed lint * Addressed the PR comments & implemented unit tests * Reverted auto-extracted method * Re-organized tests * Added completion handler for FIRAuth
* FCM: Fix crash on iOS 14 when multiple app delegate completion methods are called (#6863) * Fix crash on iOS 14 when multiple completion methods are called * Fixed lint * Addressed the PR comments & implemented unit tests * Reverted auto-extracted method * Re-organized tests * Added completion handler for FIRAuth * Manifest: GDT 8.0.1 * Update Changelogs for 7.1.0 (#6913) * Changelogs Co-authored-by: Mike Gerasimenko <[email protected]> Co-authored-by: Paul Beusterien <[email protected]>
* Update versions for Release 7.1.0 * 7.1.0 update version of close source podspecs * Release 7.1.0 cherry picks (#6917) * FCM: Fix crash on iOS 14 when multiple app delegate completion methods are called (#6863) * Fix crash on iOS 14 when multiple completion methods are called * Fixed lint * Addressed the PR comments & implemented unit tests * Reverted auto-extracted method * Re-organized tests * Added completion handler for FIRAuth * Manifest: GDT 8.0.1 * Update Changelogs for 7.1.0 (#6913) * Changelogs Co-authored-by: Mike Gerasimenko <[email protected]> Co-authored-by: Paul Beusterien <[email protected]> * Bump GDT podspec version to 8.0.1 * 7.1.0 SPM Analytics (#6933) * Carthage update 7.1.0 (#6943) Co-authored-by: Mike Gerasimenko <[email protected]> Co-authored-by: Paul Beusterien <[email protected]>
Background
Since iOS 7 it is possible to execute the code in the background for iOS apps that receive silent push notifications. Once such push (most likely containing the
content-available
flag) is received, if the application is in the background, it has 30 seconds to complete the background activity and tell iOS that it's done by calling thecompletion
block. More info on application(_:didReceiveRemoteNotification:fetchCompletionHandler:) hereIssue we observed
We discovered that on iOS 14 calling the completion block multiple times is leading to a crash. Based on my limited debugging capabilities, it looks like the API is now using dispatch groups to trace the moment when the completion is called, and in the completion block it invokes
dispatch_group_leave
. Once the completion block is called multiple times, the iOS is going to crash since this behavior is unexpected (callingdispatch_group_leave
more times thandispatch_group_enter
).More people complained about the same issue here invertase/react-native-firebase#3944.
Worth noticing that in our experience the crash is only happening when the app is actually running in the foreground and in case the FCM is used together with the Braze SDK (they are also using
GULAppDelegateSwizzler
).Proposed solution
It is sufficient to wrap the final completion block provided by iOS into yet another
dispatch_group
that would be entered for every observer of theapplication(_:didReceiveRemoteNotification:fetchCompletionHandler:)
and left when the observer is calling to the provided callback.