EventFlux icon indicating copy to clipboard operation
EventFlux copied to clipboard

The connection retry doesn't stop after being disconnected.

Open EternalYouth29 opened this issue 1 year ago • 8 comments

Hi,

Thank you for your effort in building this package!

I am encountering an issue with duplicate connections.

In my code, I disconnect when the app goes inactive or moves to the background, and reconnect when it resumes. This works fine in most cases.

However, while testing the internet connection, I noticed an issue. When the internet is disabled and the reconnect is triggered, if I put the app in the background, it still tries to reconnect. After reconnecting to the internet and resuming the app, the reconnect logic (as I mentioned earlier) is triggered and connects to SSE. At the same time, the retry logic also connects to SSE, resulting in two connections (I am using EventFlux.instance.connect()), I get double data at the same time.

When I put the app back into the background, I get the following error:

I/flutter (12133): ----------------FIREBASE CRASHLYTICS---------------- I/flutter (12133): Bad state: Cannot add event after closing I/flutter (12133): #0 _StreamController.add (dart:async/stream_controller.dart:605:24) I/flutter (12133): #1 EventFlux._start.<anonymous closure>.<anonymous closure> (package:eventflux/client.dart:325:36) I/flutter (12133): #2 _RootZone.runUnaryGuarded (dart:async/zone.dart:1594:10) I/flutter (12133): #3 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:339:11) I/flutter (12133): #4 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:271:7) I/flutter (12133): #5 _SinkTransformerStreamSubscription._add (dart:async/stream_transformers.dart:63:11) I/flutter (12133): #6 _EventSinkWrapper.add (dart:async/stream_transformers.dart:13:11) I/flutter (12133): #7 _StringAdapterSink.add (dart:convert/string_conversion.dart:228:11) I/flutter (12133): #8 _LineSplitterSink._addLines (dart:convert/line_splitter.dart:164:13) I/flutter (12133): #9 _LineSplitterSink.addSlice (dart:convert/line_splitter.dart:131:7) I/flutter (12133): #10 StringConversionSink.add (dart:convert/string_conversion.dart:39:5) I/flutter (12133): #11 _SinkTransformerStreamSubscription._handleData (dart:async/stream_transformers.dart:111:24) I/flutter (12133): #12 _RootZone.runUnaryGuarded (dart:async/zone.dart:1594:10) I/flutter (12133): ---------------------------------------------------- Could you clarify the expected behavior of the reconnect functionality? Should it stop reconnecting when EventFlux.instance.disconnect() is called? Also, how can I prevent multiple connections when using EventFlux.instance.connect()?

Thanks for your time and help!

EternalYouth29 avatar Oct 02 '24 15:10 EternalYouth29

Hi @EternalYouth29,

Thank you for your kind words and for pointing out the issue!

Based on the logs and your explanation, it looks like the core problem is that data is being added to the stream after it has already been closed, which causes the "Bad state: Cannot add event after closing" error. This occurs because the stream isn't fully cleaned up when a disconnection or reconnection is triggered. Additionally, the reconnect logic seems to continue running in the background even after calling disconnect(), leading to multiple connections and duplicate data streams.

I'm working on a fix to:

  • Ensure the stream controller is properly closed and cleaned up before reconnecting.
  • Stop reconnection attempts after disconnect() is called, to avoid multiple active connections.

I'll let you know once the fix is ready. Thanks again for your detailed report and patience!

Imgkl avatar Oct 03 '24 16:10 Imgkl

Hi @Imgkl ,

Thanks for the update! I appreciate your efforts in fixing the issue. Looking forward to the update!

EternalYouth29 avatar Oct 04 '24 12:10 EternalYouth29

Hi @EternalYouth29,

I've pushed a pre-release version with these fixes. Possible to try this version and let me know, if this fixes your problem?

https://pub.dev/packages/eventflux/versions/2.2.2-dev.1

If this issue still persists, reproducible code would helpful.

Imgkl avatar Oct 04 '24 18:10 Imgkl

Hi @Imgkl ,

Thanks for the quick update! I tested the pre-release version, and the Bad state: Cannot add event after closing error has been resolved. However, the second issues till persists -- reconnection attempts continue even after disconnect() is called.

    EventFlux.instance.connect(
      EventFluxConnectionType.post,
      'domain.com',
      header: header,
      body: body,
      onSuccessCallback: (EventFluxResponse? data) {
        if (data?.status == EventFluxStatus.connected) {
          data?.stream?.listen((EventFluxData event) {
            DebugLogger.log("event data: ${event.data}", "SseBloc");
            // logic
          });
        }
      },
      onError: (EventFluxException error) async {
        DebugLogger.log('Error connecting to SSE: ${error.message}, ${error.statusCode}, ${error.reasonPhrase}', 'SseBloc');
      },
      autoReconnect: true,
      reconnectConfig: ReconnectConfig(
        mode: ReconnectMode.linear,
        interval: const Duration(seconds: 5),
        maxAttempts: 5,
        onReconnect: () {
          DebugLogger.log('Reconnecting to SSE', 'SseBloc');
        },
      ),
    );

The SseEvent.closeSse() basically just call EventFlux.instance.disconnect().

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        sseBloc.add(const SseEvent.connectSse());
        break;
      case AppLifecycleState.inactive:
        sseBloc.add(const SseEvent.closeSse());
        break;
      case AppLifecycleState.paused:
        sseBloc.add(const SseEvent.closeSse());
        break;
      case AppLifecycleState.detached:
        sseBloc.add(const SseEvent.closeSse());
        break;
      case AppLifecycleState.hidden:
        sseBloc.add(const SseEvent.closeSse());
        break;
    }
  }

With the above code setup, after the SSE is connected, try disabling the internet connection and putting the app in the background. In the debug console, you'll still see this log: [EventFlux 2024-10-05 13:12:14.980182] 🔄 Trying again in 5 seconds. Then, if you re-enable the internet connection and resume the app, multiple event.data entries are printed in the debug console.

Thanks for your time and effort!

EternalYouth29 avatar Oct 05 '24 05:10 EternalYouth29

@EternalYouth29 Thanks for your patience and your code.

I've zeroed in on the issue which causes the reconnection logic to run even after explicit disconnect.

I've pushed a fix for this on pre-release

https://pub.dev/packages/eventflux/versions/2.2.2-dev.2

I am now clearing the reconnectLogic data on disconnect. Thus ensuring the reconnect logic is not started successfully. You might see the "🔄 Trying again in X seconds" once, depends on the last retry time, but I've tried it multiple times, Upon opening the app, only once connection is established.

Here is the code, I used to test this flow with.

https://gist.github.com/Imgkl/d6b88896118a03a315156ecf178153e7

Hope this fix solves your issue.

Imgkl avatar Oct 06 '24 19:10 Imgkl

Hi @Imgkl ,

I've tested your code example with my API, but I am still receiving duplicate data. Here are the steps to reproduce the issue:

  1. Click Connect to connect to the SSE.
  2. After connecting, turn on Airplane mode.
  3. Return to the app and wait for 5 seconds.
  4. Turn off Airplane mode and go back to the app.

Once reconnected, I start receiving two instances of [log] Stream Data incoming every second (the server sends data every 1 second). https://imgur.com/htLHOex

Additionally, in step 4, if I click Connect a few times before the phone reconnects to the internet, I end up receiving multiple instances of [log] Stream Data incoming every second.

EternalYouth29 avatar Oct 08 '24 12:10 EternalYouth29

Hi @Imgkl When automatic reconnection is enabled, if disconnect() is called before connect() is called after an exception occurs, a problem will occur. For example, if the server closes the connection, an exception will be triggered to start reconnecting. If disconnect() is called before connect() is called during reconnection, a problem will occur. The fundamental problem is that there will be two connection requests.

liugaocomeon avatar Oct 30 '24 11:10 liugaocomeon

Hi @Imgkl ! Thank you for your hard work and such a useful plugin.

Looks like this issue is the only one which stops devs to use it in production, because it leads to leaked resources

befirst avatar Apr 22 '25 13:04 befirst