-
Notifications
You must be signed in to change notification settings - Fork 202
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
Integrate retryability for outgoing mutation queue #266
Conversation
...SDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/MutationRetryNotifier.swift
Outdated
Show resolved
Hide resolved
...SDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/MutationRetryNotifier.swift
Outdated
Show resolved
Hide resolved
AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/NetworkReachabilityNotifier.swift
Outdated
Show resolved
Hide resolved
retryOperationQueue.addOperation { | ||
// Use of MutationRetryNotifier assumes that retriable has been set to true | ||
// and a retryInterval has already been calculated | ||
let deadline = DispatchTime.now() + advice.retryInterval! |
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.
Let's add a default interval to make this more foolproof rather than force-unwrapping; or make the retryInterval non-optional so we know it's always set.
...SDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/MutationRetryNotifier.swift
Show resolved
Hide resolved
...SDataStoreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/MutationRetryNotifier.swift
Outdated
Show resolved
Hide resolved
...oreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift
Outdated
Show resolved
Hide resolved
...oreCategoryPlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift
Show resolved
Hide resolved
self.sendMutationToCloud() | ||
self.mutationRetryNotifier = nil | ||
} | ||
currentAttemptNumber += 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.
I know these mutation operations are enqueued on a serial Operations queue; is any of the internal work being done in a way that makes this not-thread-safe? My quick scan makes me think everything is pretty well serialized, but wanted you to confirm since you're closer to this code.
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.
Just to double check, your question revolves around executing multiple SyncMutationToCloudOperations running at the same time such that we could run into a situation where our code is not thread safe? (not that we'd ever want to do this, because we want to drain the mutation queue one at a time in case any of the data changes)
But... I looked at the code and thought about implications of instance variables if we were to create multiple operations and run them at the same time, and didn't see a situation where we would be overwriting data.
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.
No, I'm actually concerned about multiple operations happening on a single operation--e.g., a network callback being invoked at the same time as we're trying to make the outgoing call b/c of a retry.
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 see... hmmm.. I suppose it is possible that our timer could wake up at the same exact time that we have a receive() through combine (e.g. a network event).
In this case, I think a pragmatic solution could be to introduce something (atomic write to a variable?) in our notifyCallback
function which allows the "first event" to win.
@@ -16,14 +16,41 @@ struct ReachabilityUpdate { | |||
|
|||
@available(iOS 13.0, *) | |||
class NetworkReachabilityNotifier { | |||
private(set) static var shared: NetworkReachabilityNotifier? |
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'm just now realizing that this is singleton pattern isn't going to work if we want reachability notifiers for different APIs (e.g., one each for a GraphQL and a REST endpoint). I think we're going to need these to be straight up instances. We could consider a Factory that maintains cached reachability notifiers per host and creates or returns those instances at need.
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.
Thanks for pointing this out.. I will revert this.
Hmm, i think we still want the API Category to own the NetworkReachability stuff since it's closest to knowing what the endpoint is... but this means we'll either need to do something like expose a Combine publisher to the Datastore category so that our MutationRetryNotifier can subscribe (or something to that effect)
I'll dig into this.
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.
In the short term, I'm going to make the NetworkReachabilityNotifier as an optional -- until we figure out out how to plumb this between API and Datastore.
Assuming circle ci tests pass, we should be able to merge these changes into master. With the following outstanding items to handle:
|
a41f8a2
to
3391f2f
Compare
Squashed commits. |
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.
- Clarified of my multi-threading request, but not a ship blocker
- I assume that the "resolve notifier" is the next thing you're working on, or else filed in the issue backlog. Assuming so, ship it!
3391f2f
to
0678bee
Compare
if let apiRequest = createAPIRequest(mutationType: mutationType) { | ||
makeAPIRequest(apiRequest) | ||
} |
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 where the bug got introduced. Based on the comment of "bubbling up errors" i had introduced (unwittingly not noticing that we were already bubbling up the error):
if let apiRequest = createAPIRequest(mutationType: mutationType) {
makeAPIRequest(apiRequest)
+ } else {
+ finish(result: .failed(...)
+ }
This basically was causing us to call finish twice, which caused the unit tests to crash(because expectation's fulfill()
calls were being called more than expected.
So, to remedy this, i made createAPI request simpler by moving out the checking of the graphQLMutation type, so we can easily see that the function is indeed handling the bubbling up of those errors.
Work-in-progress. need to find a way to plumb through the hostname from the client config
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.