Skip to content

[gql_websocket_link] TransportWebSocketLink does not reconnect when graphql server disappears #407

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

Closed
bverhagen opened this issue Jun 26, 2023 · 6 comments

Comments

@bverhagen
Copy link

I use the graphql-transport-ws as the connection Link for a Ferry-based grqphl subscription, pretty much as is done in Ferry examples, only with the http link replaced by a ws one:

   final link = Link.from([
      TransportWebSocketLink(
        TransportWsClientOptions(
            socketMaker:
                WebSocketMaker.url(() => _uri)),
      ),
    ]);
    final cache = Cache(possibleTypes: possibleTypesMap);
    final client = Client(link: link, cache: cache);

During development, I regularly restarted the graphql-server. As the README for gql_websocket_link reads: This link support autoReconnect and will resubscribe after reconnecting., I expected the gql_websocket_link-based graphql client to automatically reconnect again after such a restart. However, it does not seem to do that.

The graphql server only supports the graphql-transport-ws protocol. Looking further in the API reference, I am only able to find the autoReconnect configuration option for the WebSocketLink class. Does the TransportWebSocketLink already support a similar autoReconnect feature? If so, can you point me to an example or the relevant configuration options?

@knaeckeKami
Copy link
Collaborator

There are options related to auto reconnects in the TransportWsClientOptions class.
There is also a test that should verify to reconnect on a server restart ( https://github.com/gql-dart/gql/blob/master/links/gql_websocket_link/test/gql_websocket_link_test.dart#L1097 ).

@catapop84
Copy link
Contributor

I can confirm this is not working. Here is the error thrown:

E/flutter (14348): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Null check operator used on a null value
E/flutter (14348): #0      _ConnectionState._startConnecting.<anonymous closure>.<anonymous closure> (package:gql_websocket_link/src/graphql_transport_ws.dart:853:45)
E/flutter (14348): #1      _RootZone.runGuarded (dart:async/zone.dart:1582:10)
E/flutter (14348): #2      _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:392:13)
E/flutter (14348): #3      _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:402:7)
E/flutter (14348): #4      _BufferingStreamSubscription._close (dart:async/stream_impl.dart:291:7)
E/flutter (14348): #5      _ForwardingStream._handleDone (dart:async/stream_pipe.dart:99:10)
E/flutter (14348): #6      _ForwardingStreamSubscription._handleDone (dart:async/stream_pipe.dart:161:13)
E/flutter (14348): #7      _RootZone.runGuarded (dart:async/zone.dart:1582:10)
E/flutter (14348): #8      _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:392:13)
E/flutter (14348): #9      _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:402:7)
E/flutter (14348): #10     _BufferingStreamSubscription._close (dart:async/stream_impl.dart:291:7)
E/flutter (14348): #11     _SyncStreamControllerDispatch._sendDone (dart:async/stream_controller.dart:792:19)
E/flutter (14348): #12     _StreamController._closeUnchecked (dart:async/stream_controller.dart:647:7)
E/flutter (14348): #13     _StreamController.close (dart:async/stream_controller.dart:640:5)
E/flutter (14348): #14     _RootZone.run (dart:async/zone.dart:1655:54)
E/flutter (14348): #15     _FutureListener.handleWhenComplete (dart:async/future_impl.dart:199:18)
E/flutter (14348): #16     Future._propagateToListeners.handleWhenCompleteCallback (dart:async/future_impl.dart:810:39)
E/flutter (14348): #17     Future._propagateToListeners (dart:async/future_impl.dart:866:11)
E/flutter (14348): #18     Future._completeWithValue (dart:async/future_impl.dart:641:5)
E/flutter (14348): #19     Future._asyncCompleteWithValue.<anonymous closure> (dart:async/future_impl.dart:715:7)
E/flutter (14348): #20     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
E/flutter (14348): #21     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)

@catapop84
Copy link
Contributor

I found the error. is in this code:

        onDone: () => onClose?.call(
          socket.closeCode == null
              ? "DONE"
              : LikeCloseEvent(
                  code: socket.closeCode!,
                  reason: socket.closeReason!,
                ),
        ),

It assumes that if socket.closeCode is not null then both socket.closeCode and socket.closeReason are available but that's not the case. If I stop the server program I receive code:1002 and reason = null

to fix this you should either make reason optional in LikeCloseEvent or put an empty string if closeReason is null.
Something like this

          socket.closeCode == null
              ? "DONE"
              : LikeCloseEvent(
                  code: socket.closeCode!, // socket.closeCode is 1002
                  reason: socket.closeReason??'', // socket.closeReason is null
                ),

@bverhagen
Copy link
Author

@knaeckeKami : Will this be taken up?

@catapop84
Copy link
Contributor

@bverhagen I made this PR that fixes the problem: #418 and we already use it in closed beta with some clients. No complains so far.

You'll need to configure it like this

    final link = TransportWebSocketLink(
      TransportWsClientOptions(
        socketMaker: wsMaker,
        lazy: true,
        keepAlive: const Duration(seconds: 50),
        retryAttempts: 20,
        retryWait: (retries) async {
          log("number of retries: $retries");
          await Future.delayed(const Duration(seconds: 5));
        },
        shouldRetry: (errOrCloseEvent) {
          return true;
        },
        isFatalConnectionProblem: (errOrCloseEvent) {
          return false;
        },
      ),
    );

Basically this PR adds shouldRetry that will replace the deprecated isFatalConnectionProblem (If javascript library is a refference)
You can add your login on shouldRetry based on errOrCloseEvent parameter.

@knaeckeKami
Copy link
Collaborator

@knaeckeKami : Will this be taken up?

Yes. Sorry, I have a lot going on at the moment, so triage is slow. Will get better soon.

In the mean time, you can use the fork from the PR.

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

No branches or pull requests

3 participants