-
Notifications
You must be signed in to change notification settings - Fork 281
fix(api): Don't block subscription events during WebSocket reconnect #7282
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
base: main
Are you sure you want to change the base?
Changes from 6 commits
eb67e68
e014c5a
6467498
db1cff5
016bd8c
204d187
6dfbba8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,6 +108,9 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { | |
|
|
||
| late WebSocketState _currentState; | ||
|
|
||
| /// The in-flight reconnection, if any. Ensures at most one runs at a time. | ||
| Future<void>? _reconnectOperation; | ||
|
|
||
| /// OVERRIDES | ||
| /// | ||
| /// | ||
|
|
@@ -378,12 +381,25 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { | |
| /// First establishes there is a connection to AppSync | ||
| /// Then clears web socket connection and restarts init workflow | ||
| /// Sends [NetworkException] when unable to reach AppSync | ||
| /// | ||
| /// Runs off the event queue so a slow reconnect doesn't block incoming | ||
| /// events (e.g. subscription data, keep alives) from being processed. | ||
| Stream<WebSocketState> _reconnect() async* { | ||
| assert( | ||
| _currentState is ReconnectingState, | ||
| 'Bloc should be set to connecting before starting reconnection.', | ||
| ); | ||
| final state = _currentState; | ||
| _reconnectOperation ??= _performReconnect( | ||
| _currentState, | ||
| ).whenComplete(() => _reconnectOperation = null); | ||
|
Comment on lines
+392
to
+394
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we handle errors that aren't
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| // TODO(dnys1): Yield broken on web debug build. | ||
| yield* const Stream.empty(); | ||
| } | ||
|
|
||
| /// Pings AppSync with retry/back off and reinitializes the connection, or | ||
| /// shuts down on failure. Runs off the event queue via [_reconnect]. | ||
| Future<void> _performReconnect(WebSocketState state) async { | ||
| try { | ||
| // Begin reconnection with retry/back off on ping endpoint | ||
| final res = await state.options.retryOptions.retry( | ||
|
|
@@ -395,16 +411,20 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { | |
|
|
||
| // **Ping succeeded** | ||
|
|
||
| // Bloc may have shut down during the ping, don't act on a closed bloc | ||
| if (_isShuttingDown) return; | ||
|
|
||
| // Prep new connection | ||
| await state.service.close(); | ||
| for (final bloc in state.subscriptionBlocs.values) { | ||
| bloc.add(SubscriptionPendingEvent(bloc.currentState.request.id)); | ||
| } | ||
|
|
||
| // Init new connection | ||
| add(const InitEvent()); | ||
| _safeAdd(const InitEvent()); | ||
| } on Exception catch (e, st) { | ||
| // Ping failed, close down | ||
| // Ping failed, nothing to do if already shutting down | ||
| if (_isShuttingDown) return; | ||
| _shutdownWithException( | ||
| NetworkException( | ||
| 'Unable to recover network connection, web socket will close.', | ||
|
|
@@ -414,11 +434,15 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { | |
| st, | ||
| ); | ||
| } | ||
|
|
||
| // TODO(dnys1): Yield broken on web debug build. | ||
| yield* const Stream.empty(); | ||
| } | ||
|
|
||
| /// Whether the bloc is closed or shutting down. | ||
| bool get _isShuttingDown => | ||
| _wsEventController.isClosed || | ||
| _currentState is PendingDisconnect || | ||
| _currentState is DisconnectedState || | ||
| _currentState is FailureState; | ||
|
|
||
| /// Sends registration message on ws channel when connected | ||
| void _registerSubscriptionRequest(GraphQLRequest<Object?> request) { | ||
| final currentState = _currentState; | ||
|
|
||
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.
Don't we need to cancel or await this? E.g. in
_close?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.
Tested awaiting it in
_close()and it hangs shutdown (breaks the shutdown-during-reconnect test), and a Future can't be cancelled. Instead,_isShuttingDownmakes the reconnect a no-op once the bloc closes.