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
fix(AWSPinpoint): Fixing a deadlock when submitting events from multiple threads #4558
Conversation
@@ -99,7 +99,7 @@ NS_ASSUME_NONNULL_BEGIN | |||
An AWSPinpointEndpointProfile object with the specified context | |||
@returns AWSPinpointEndpointProfile | |||
*/ | |||
- (instancetype)initWithContext:(AWSPinpointContext *) context; | |||
- (instancetype)initWithContext:(AWSPinpointContext *) context isRegisteredForRemoteNotifications:(BOOL) isRegisteredForRemoteNotifications; |
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.
AWSPinpointContext
is an internal type, this init
is not externally accessible so this is not a breaking change
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.
This is a public header right?
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.
Yeah, but AWSPinpointContext
is defined in the [Internal](https://github.com/aws-amplify/aws-sdk-ios/tree/main/AWSPinpoint/Internal)
folder and those things can't be consumed, right?
At least on my sample app, this init never appeared as an option, and attempting to use AWSPinpointContext
was greyed out because "it was not imported".
But maybe that's a Swift-only thing? If you prefer I can keep and deprecate the old method, and create a new one with the extra parameter that we use.
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.
ah, I see what you are saying. Even here they are marked as private - https://github.com/aws-amplify/aws-sdk-ios/blob/main/AWSPinpoint.podspec#L17, I think we are good with this change.
/// | ||
/// Requests the current endpoint. | ||
/// - Parameter completion: a block that is called with the current endpoint | ||
- (void) currentEndpointProfileWithCompletion:(void (^_Nonnull)(AWSPinpointEndpointProfile *profile))completion; |
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.
AWSTask
instead, to be consistent with all the other async operations that exists in this client (e.g. updateEndpointProfile
).
However I decided against it because AWSTask
s by definition can fail, while this method should always return a valid non-null AWSPinpointEndpointProfile
instance.
So it would just add an unnecessary validation/unwrapping for any customer that needed to use this method.
Issue #, if available:
Description of changes:
When
submitEvents
is called, part of the process is to grab thecurrentEndpointProfile
because thePutEvents
call to Pinpoint requires it as an input.Inside
AWSPinpointTargetingClient.currentEndpointProfile
, there's a call toAWSPinpointNotificationManager.isNotificationEnabled
, which is necessary to determine if the endpoint is currently opted out of notifications or not.isNotificationEnabled
is internally callingUIApplication.isRegisteredForRemoteNotifications
, but because the latter needs to be called from the main queue, it's doing adispatch_sync(dispatch_get_main_queue())
if the current thread is not the main one.This in isolation is not a problem, but it causes trouble when integrated to the whole event submission process, since we do a
@synchronized(self.lock)
right at the beginning. This will result in a deadlock:submitEvents
and gets the locksubmitEvents
and waits to get the lockisNotificationEnabled
code, so it attempts todispatch_sync
into the main queueThe background thread is now blocked because it is waiting on the main queue to unblock, which un turn is waiting for the background thread to release the lock.
In order to fix this, I wanted to be the least disruptive possible since we are dealing with very old implementations.
Since submitting events is already an async operation, I decided to just introduce a new
currentEndpointProfileWithCompletion:
method that won't block the main queue when it needs to grabUIApplication.isRegisteredForRemoteNotifications
, and call this method instead internally.I also decoupled
AWSPinpointEndpointProfile
fromAWSPinpointNotificationManager
, so now theisRegisteredForRemoteNotifications
flag needs to be provided externally when creating the profile.Finally, I added Warning annotations to both
isNotificationEnabled
andcurrentEndpointProfile
docs indicating that calling them from a non-main thread might cause deadlocks if the main queue is blocked.Check points:
Documentation update for the change if requiredBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.