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

[gql_websocket_link] Crash events on host lookup #423

Open
bverhagen opened this issue Oct 25, 2023 · 3 comments
Open

[gql_websocket_link] Crash events on host lookup #423

bverhagen opened this issue Oct 25, 2023 · 3 comments

Comments

@bverhagen
Copy link

bverhagen commented Oct 25, 2023

I switched to gql_websocket_link to connect to my GraphQL backend (before I used hasura_connect). Since this switch, Firebase Crashlytics reports:

WebSocketChannelException: WebSocketChannelException: SocketException: Failed host lookup: '<myhost>' (OS Error: No address associated with hostname, errno = 7)

I think this is due to the connection being interrupted by me switching off mobile and wifi connectivity. So, obviously, the actual error makes sense. However, I don't think the app should crash nor be reported on Crashlytics when I do.

Currently, the connection is implemented as:

final link = WebSocketLink(
      "wss://<myhost>",
      autoReconnect: true,
      inactivityTimeout: const Duration(seconds: 5),
      initialPayload: () async {
        final authHeaders = await getAuthHeaders();
        return {
          'headers': {
            'x-hasura-role': role.id,
            ...authHeaders,
          }
        };
      },
    );

I thought switching to the channelGenerator argument (with a null url), I would be able to wrap the WebSocketChannelException in a try/catch clause to avoid this. However, somehow, the SocketException keeps on escaping my try/catch and hence keeps on crashing the app:

final link = WebSocketLink(
      null,
      channelGenerator: () {
        while(true) {
          try {
            return WebSocketChannel.connect(Uri.parse("wss://<myhost>"));
          } catch(_) {

          }
        }
      },
      autoReconnect: true,
      inactivityTimeout: const Duration(seconds: 5),
      initialPayload: () async {
        final authHeaders = await getAuthHeaders();
        return {
          'headers': {
            'x-hasura-role': role.id,
            ...authHeaders,
          }
        };
      },
    );

My main/AndroidManifest.xml contains the android.permission.INTERNET permission:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="eco.futureproof.app">
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <application ...>
      <my application stuff>
    </application>
 </manifest>

Even though I really doubt I am the first one to see this issue, I do not find any related issues. Can you point me to an example or the typical solution to fix this?

@bverhagen
Copy link
Author

I just realized that, around the same time, I switched on fatal and async error recording for Firebase Crashlytics (which may explain why I did not see this happen with hasura_connect):

FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;

PlatformDispatcher.instance.onError = (error, stack) {
  FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
  return true;
};

I assume it is the async error recording that is the culprit here. Still, is there a way to catch it?

@bverhagen
Copy link
Author

In the meantime I found this: dart-lang/web_socket_channel#38.

I am going to test whether the suggested solution of awaiting socket.ready will allow me to catch the error in the channelGenerator callback.

@bverhagen
Copy link
Author

Preliminary testing seems to indicate this fixes it for initial connection errors:

channelGenerator: () async {
        while (true) {
          final socket = WebSocketChannel.connect(
              Uri.parse("wss://$apiDomain/v1/graphql"));
          try {
            await socket.ready;
            developer
                .log("Successfully connected with websocket to '$apiDomain'!");
            return socket;
          } catch (e, stacktrace) {
            developer.log("Error connecting to websocket: $e");
            await Future.delayed(const Duration(seconds: 5));
          }
        }
      }

Shouldn't the default channelGenerator be robust against this to?

Additionally, returning the errored socket, also results in an uncaught async error later on in its usage.

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

1 participant