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

Integrate retryability for outgoing mutation queue #266

Merged
merged 1 commit into from
Dec 18, 2019
Merged

Conversation

wooj2
Copy link
Contributor

@wooj2 wooj2 commented Dec 17, 2019

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.

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!
Copy link
Member

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.

self.sendMutationToCloud()
self.mutationRetryNotifier = nil
}
currentAttemptNumber += 1
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

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 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?
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@wooj2
Copy link
Contributor Author

wooj2 commented Dec 17, 2019

Assuming circle ci tests pass, we should be able to merge these changes into master. With the following outstanding items to handle:

  • Update model to not wake up on 429 errors
  • Add tests around deinit for mutation retry notifier
  • Dig into errors for reachability
  • Migrate reachability into API category and figure out a way to expose the publisher to datastore.

@wooj2 wooj2 requested a review from palpatim December 17, 2019 23:25
@wooj2 wooj2 changed the title WIP: Integrate retryability for outgoing mutation queue Integrate retryability for outgoing mutation queue Dec 17, 2019
@wooj2 wooj2 force-pushed the wooj/networkRetry branch from a41f8a2 to 3391f2f Compare December 17, 2019 23:26
@wooj2
Copy link
Contributor Author

wooj2 commented Dec 17, 2019

Squashed commits.

Copy link
Member

@palpatim palpatim left a comment

Choose a reason for hiding this comment

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

  1. Clarified of my multi-threading request, but not a ship blocker
  2. 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!

@wooj2 wooj2 force-pushed the wooj/networkRetry branch from 3391f2f to 0678bee Compare December 18, 2019 01:03
Comment on lines +87 to +89
if let apiRequest = createAPIRequest(mutationType: mutationType) {
makeAPIRequest(apiRequest)
}
Copy link
Contributor Author

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.

@wooj2 wooj2 merged commit fdd09aa into master Dec 18, 2019
@palpatim palpatim deleted the wooj/networkRetry branch December 24, 2019 14:33
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.

2 participants