Skip to content
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

Merged
merged 6 commits into from
Nov 5, 2020

Conversation

mikeger
Copy link
Contributor

@mikeger mikeger commented Oct 29, 2020

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 the completion block. More info on application(_:didReceiveRemoteNotification:fetchCompletionHandler:) here

Issue 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 (calling dispatch_group_leave more times than dispatch_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

Similia similibus curantur

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 the application(_:didReceiveRemoteNotification:fetchCompletionHandler:) and left when the observer is calling to the provided callback.

@google-cla
Copy link

google-cla bot commented Oct 29, 2020

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 @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@google-cla google-cla bot added the cla: no label Oct 29, 2020
@mikeger
Copy link
Contributor Author

mikeger commented Oct 29, 2020

@googlebot I signed it!

@google-cla google-cla bot added cla: yes and removed cla: no labels Oct 29, 2020
@mikeger
Copy link
Contributor Author

mikeger commented Oct 29, 2020

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 release-xxx branch?

@paulb777
Copy link
Member

@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.

Copy link
Contributor

@maksymmalyhin maksymmalyhin left a 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(), ^() {
Copy link
Contributor

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.
Copy link
Contributor

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;
Copy link
Contributor

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.

@mikeger
Copy link
Contributor Author

mikeger commented Oct 29, 2020

@maksymmalyhin thanks for the review, all valid comments.

@paulb777
Copy link
Member

#6865 has merged so rebasing or merging master should now enable a green CI run.

@AlexPritchin
Copy link

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.

@maksymmalyhin
Copy link
Contributor

@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.

@mikeger
Copy link
Contributor Author

mikeger commented Nov 5, 2020

@maksymmalyhin hey, I would address the comments today. I got stuck since the tests (./test.sh) were not running for me (build failed), I will retry rebased on the current master.

@maksymmalyhin
Copy link
Contributor

@mikeger Ah, ./test.sh is an obsolete script that should've been removed. Sorry about that. Currently we mostly use the following ways to run tests:

  1. Follow the development guide and run tests from the generated project. This is the most convenient way for active development.
  2. Running pod lib lint for a podspec. See CI for examples, e.g. for FirebaseMessaging or GoogleUtilities. This is useful to debug CI issues locally.

@mikeger
Copy link
Contributor Author

mikeger commented Nov 5, 2020

@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.

Copy link
Contributor

@maksymmalyhin maksymmalyhin left a 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;
Copy link
Contributor

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];
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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.

@mikeger
Copy link
Contributor Author

mikeger commented Nov 5, 2020

@maksymmalyhin updated, thanks for the comments!

Copy link
Contributor

@maksymmalyhin maksymmalyhin left a comment

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@maksymmalyhin maksymmalyhin left a 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.

@mikeger
Copy link
Contributor Author

mikeger commented Nov 5, 2020

@maksymmalyhin added, to my understanding there it's sufficient to always indicate NoData.

Copy link
Contributor

@charlotteliang charlotteliang left a comment

Choose a reason for hiding this comment

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

LGTM on messaging

@maksymmalyhin
Copy link
Contributor

All looks good to me, I'm going to merge the PR on CI green.

@maksymmalyhin maksymmalyhin merged commit c8de4fc into firebase:master Nov 5, 2020
@maksymmalyhin
Copy link
Contributor

@mikeger Thank you again for your contribution!

We will try to include the fix into 7.1.0 release.

maksymmalyhin pushed a commit that referenced this pull request Nov 6, 2020
…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
maksymmalyhin added a commit that referenced this pull request Nov 6, 2020
* 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]>
maksymmalyhin added a commit that referenced this pull request Nov 11, 2020
* 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]>
@firebase firebase locked and limited conversation to collaborators Dec 6, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants