@@ -108,6 +108,9 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin {
108108
109109 late WebSocketState _currentState;
110110
111+ /// The in-flight reconnection, if any. Ensures at most one runs at a time.
112+ Future <void >? _reconnectOperation;
113+
111114 /// OVERRIDES
112115 ///
113116 ///
@@ -378,12 +381,25 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin {
378381 /// First establishes there is a connection to AppSync
379382 /// Then clears web socket connection and restarts init workflow
380383 /// Sends [NetworkException] when unable to reach AppSync
384+ ///
385+ /// Runs off the event queue so a slow reconnect doesn't block incoming
386+ /// events (e.g. subscription data, keep alives) from being processed.
381387 Stream <WebSocketState > _reconnect () async * {
382388 assert (
383389 _currentState is ReconnectingState ,
384390 'Bloc should be set to connecting before starting reconnection.' ,
385391 );
386- final state = _currentState;
392+ _reconnectOperation ?? = _performReconnect (
393+ _currentState,
394+ ).whenComplete (() => _reconnectOperation = null );
395+
396+ // TODO(dnys1): Yield broken on web debug build.
397+ yield * const Stream .empty ();
398+ }
399+
400+ /// Pings AppSync with retry/back off and reinitializes the connection, or
401+ /// shuts down on failure. Runs off the event queue via [_reconnect] .
402+ Future <void > _performReconnect (WebSocketState state) async {
387403 try {
388404 // Begin reconnection with retry/back off on ping endpoint
389405 final res = await state.options.retryOptions.retry (
@@ -395,16 +411,20 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin {
395411
396412 // **Ping succeeded**
397413
414+ // Bloc may have shut down during the ping, don't act on a closed bloc
415+ if (_isShuttingDown) return ;
416+
398417 // Prep new connection
399418 await state.service.close ();
400419 for (final bloc in state.subscriptionBlocs.values) {
401420 bloc.add (SubscriptionPendingEvent (bloc.currentState.request.id));
402421 }
403422
404423 // Init new connection
405- add (const InitEvent ());
424+ _safeAdd (const InitEvent ());
406425 } on Exception catch (e, st) {
407- // Ping failed, close down
426+ // Ping failed, nothing to do if already shutting down
427+ if (_isShuttingDown) return ;
408428 _shutdownWithException (
409429 NetworkException (
410430 'Unable to recover network connection, web socket will close.' ,
@@ -414,11 +434,15 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin {
414434 st,
415435 );
416436 }
417-
418- // TODO(dnys1): Yield broken on web debug build.
419- yield * const Stream .empty ();
420437 }
421438
439+ /// Whether the bloc is closed or shutting down.
440+ bool get _isShuttingDown =>
441+ _wsEventController.isClosed ||
442+ _currentState is PendingDisconnect ||
443+ _currentState is DisconnectedState ||
444+ _currentState is FailureState ;
445+
422446 /// Sends registration message on ws channel when connected
423447 void _registerSubscriptionRequest (GraphQLRequest <Object ?> request) {
424448 final currentState = _currentState;
0 commit comments