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

feat(api): Subscription Reconnection #2074

Merged

Conversation

Equartey
Copy link
Contributor

Issue #, if available:

Description of changes:
Provides GraphQL subscription reconnection and associated quality of life additions.

  • Introduces a reconnection strategy for GraphQL Subscription web sockets.
  • Monitors hardware level network changes to trigger reconnect on resume (wifi on/off, wifi to mobile, airplane mode, etc).
  • Utilizes an exponential back off strategy when attempting to reconnect.
  • Exposes configuration options for this retry strategy at plugin init time.
  • Introduces Hub Events to allow tracking of connection status. Statuses include:
    -- Connected The network is connected and has active subscriptions.
    -- Connecting - The network is disconnected and has active subscriptions. Triggers for initial connect and reconnect events.
    -- PendingDisconnect - The network is connected, but has no active subscriptions.
    -- Disconnected - The network has been disconnected and no active subscriptions.
    -- Failed - The network could not be connected.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@Equartey Equartey requested a review from a team as a code owner August 30, 2022 18:25
@Equartey Equartey force-pushed the feat/graphql-sub-reconnect branch from e6bb82b to b786902 Compare September 1, 2022 23:42
Copy link
Contributor

@ragingsquirrel3 ragingsquirrel3 left a comment

Choose a reason for hiding this comment

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

Still reviewing details, but left a few comments for now, plus will DM with some offline comments that I think need to be addressed.


/// Confiuration options for GraphQL Subscriptions and their websockets.
class GraphQLSubscriptionOptions {
/// Configure the ping interval for AppSync polling for subscription connections.
Copy link
Contributor

Choose a reason for hiding this comment

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

question: Would it make sense to state the default here? I'm guessing customers will see these comments when seeing what options they can provide and when deciding to provide a value or not it helps to know what the default is.

packages/api/amplify_api/test/util.dart Outdated Show resolved Hide resolved
@@ -52,11 +52,12 @@ class WebSocketSubscriptionStreamTransformer<T>

/// executes when start_ack message received
final void Function()? onEstablished;
final Set<String> _establishedRequests = {};
Copy link
Contributor

Choose a reason for hiding this comment

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

question: Is the intention here to ensure onEstablished not called again for same sub when reconnect occurs? Seems like a fine design decision if so.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, was seeing some scenarios that it was firing more than once.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should probably be static, no? Otherwise, each set will only ever contain one object since an instance of this is created for each request.

Copy link
Contributor

Choose a reason for hiding this comment

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

Or if it's just for this request, then a boolean will do

@@ -5,6 +5,8 @@
import FlutterMacOS
import Foundation

import connectivity_plus_macos
Copy link
Contributor

Choose a reason for hiding this comment

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

when I run aft bs I get similar file modifications in packages/api/amplify_api/example/macos/Flutter/GeneratedPluginRegistrant.swift. Should that be added as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I noticed some extra generated files didn't committed. Will include them next push 👍

Copy link
Contributor

@dnys1 dnys1 left a comment

Choose a reason for hiding this comment

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

Looking good! Some minor comments mostly

packages/amplify_core/lib/src/types/api/api_types.dart Outdated Show resolved Hide resolved
packages/api/amplify_api/example/lib/main.dart Outdated Show resolved Hide resolved
_hubEventsController?.add(SubscriptionHubEvent.failed());

if (_hasNetwork &&
_hasConnectionBroken &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure we want to check _hasConnectionBroken here since that will only be true if _hasNetwork is false really.

Actually, in what situation would we get here with network and not already be retrying?

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 what you're saying. _hasConnectionBroken indicates that a connection existed at one point, but was interrupted & a retry attempt hasn't been made yet. If it's false, then we've already tried to recover.

That being said, since the called out code lives in the onError handler I agree _hasConnectionBroken doesn't need to be checked. Network related interruptions are being handled else where. The goal here is to recover from errors.

Good catch.

@@ -203,12 +391,15 @@ class WebSocketConnection implements Closeable {

/// Times out the connection (usually if a keep alive has not been received in time).
void _timeout(Duration timeoutDuration) {
_timeoutTimer?.cancel();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the timeout timer still running when we lose network? Should it be?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently no, the timer gets canceled during network interruptions. I think this is how we want it, do you think otherwise?

Copy link
Contributor

@ragingsquirrel3 ragingsquirrel3 left a comment

Choose a reason for hiding this comment

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

just left some comments about possible rebase errors here

packages/amplify_core/pubspec.yaml Outdated Show resolved Hide resolved
packages/api/amplify_api/lib/src/amplify_api_config.dart Outdated Show resolved Hide resolved
packages/api/amplify_api/lib/src/util.dart Outdated Show resolved Hide resolved
packages/api/amplify_api/test/util_test.dart Outdated Show resolved Hide resolved
Copy link
Contributor

@ragingsquirrel3 ragingsquirrel3 left a comment

Choose a reason for hiding this comment

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

Getting closer! Liking the change in tests, nice job there. Generally would try to get rid of all the lint warnings and get tests passing so CI on PR is green and flutter analyze from amplify_api dir has no warnings.

return MockWebSocketChannel();
}

MockWebSocketConnection getWebSocketConnection() {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion/question: Could this get moved to test util so you can use in dart_graphql_test.dart too?

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 moved some of the logic, but can't completely reuse this logic as each test is implemented slightly different.

packages/api/amplify_api/example/lib/rest_api_view.dart Outdated Show resolved Hide resolved
packages/api/amplify_api/example/lib/graphql_api_view.dart Outdated Show resolved Hide resolved
packages/api/amplify_api/example/amplify/README.md Outdated Show resolved Hide resolved
Copy link
Contributor

@ragingsquirrel3 ragingsquirrel3 left a comment

Choose a reason for hiding this comment

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

Still gonna test again but left a few small comments.

@@ -85,6 +85,10 @@ class AmplifyHubImpl extends AmplifyHub {
Future.wait<void>([
for (final stream in _availableStreams.values) stream.close(),
]).ignore();
Future.wait<void>([
Copy link
Contributor

Choose a reason for hiding this comment

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

Missed this from before, but what does this block do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries, this was just added. @dnys1 helped debug this one. It cleans up hub event subscriptions on cancel, an issue I was seeing in testing.

Copy link
Contributor

Choose a reason for hiding this comment

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

cool, thanks for info. Might be worth a small comment here to explain.

packages/api/amplify_api/test/util.dart Outdated Show resolved Hide resolved
@codecov-commenter
Copy link

codecov-commenter commented Sep 28, 2022

Codecov Report

Merging #2074 (dc00c44) into feat/api-next (2809cc6) will increase coverage by 0.45%.
The diff coverage is 53.10%.

@@                Coverage Diff                @@
##           feat/api-next    #2074      +/-   ##
=================================================
+ Coverage          39.23%   39.69%   +0.45%     
=================================================
  Files                120      120              
  Lines               6915     7039     +124     
=================================================
+ Hits                2713     2794      +81     
- Misses              4202     4245      +43     
Flag Coverage Δ
android-unit-tests ∅ <ø> (∅)
flutter-unit-tests 27.87% <53.10%> (+0.81%) ⬆️
ios-unit-tests 94.92% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
packages/api/amplify_api/lib/amplify_api.dart 100.00% <ø> (ø)
...kages/api/amplify_api/lib/src/api_plugin_impl.dart 77.39% <0.00%> (-1.37%) ⬇️
..._api/lib/src/graphql/ws/web_socket_connection.dart 63.46% <52.89%> (+3.23%) ⬆️
...phql/ws/web_socket_message_stream_transformer.dart 90.90% <100.00%> (+0.90%) ⬆️

Copy link
Contributor

@dnys1 dnys1 left a comment

Choose a reason for hiding this comment

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

LGTM 🚀

packages/api/amplify_api/example/lib/main.dart Outdated Show resolved Hide resolved
WebSocketConnection getWebSocketConnection({String? apiName}) =>
MockWebSocketConnection(testApiKeyConfig, getTestAuthProviderRepo());
WebSocketConnection getWebSocketConnection({String? apiName}) {
return mockWebSocketConnection;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it be the same for every test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I believe this is okay given the tests implementations.

packages/api/amplify_api/test/util.dart Outdated Show resolved Hide resolved
Copy link
Contributor

@ragingsquirrel3 ragingsquirrel3 left a comment

Choose a reason for hiding this comment

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

🚢 🔥 , love it! nice work

@Equartey Equartey merged commit 9d1319c into aws-amplify:feat/api-next Sep 30, 2022
@Equartey Equartey deleted the feat/graphql-sub-reconnect branch September 30, 2022 18:41
HuiSF pushed a commit to HuiSF/amplify-flutter that referenced this pull request Nov 10, 2022
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.

4 participants