dart-lang / web_socket_channel

StreamChannel wrappers for WebSockets.
https://pub.dev/packages/web_socket_channel
BSD 3-Clause "New" or "Revised" License
412 stars 107 forks source link

HTML: When the WebSocket is in a 'pending' state, calling channel.sink.close does not work. #333

Closed ApophisLee closed 3 months ago

ApophisLee commented 3 months ago

In the testing environment, when launching Flutter web applications on Chrome in debug mode, the command channel.sink.close fails to execute correctly if the WebSocket's status is 'pending'. The code snippet provided demonstrates an attempt to close a WebSocket connection following a timeout, with the intention of subsequently reconnecting:

channel = WebSocketChannel.connect(wsUrl);

try {
  await channel?.ready.timeout(const Duration(seconds: 1));
} catch (e) {
  channel?.sink.close(status.abnormalClosure);
  debugPrint("Failed to connect, error: $e");
  return;
}

However, the WebSocket remains in a 'pending' state until it times out internally. This results in a problem where attempts to reconnect lead to the creation of numerous connections, each waiting for its predecessors to close automatically due to the timeout.

A potential issue lies in the improper use of channel?.sink.close(status.abnormalClosure);. If the connection is 'pending', invoking .close() may not effectively end the connection, thus causing the aforementioned complications.

It's been found that when the status is 'pending', the code within html.dart:155 does not get executed, which is believed to be critical for resolving this issue. Below is the relevant code segment:

_controller.local.stream.listen((obj) => innerWebSocket.send(obj!.jsify()!),
    onDone: () {
  // On Chrome and possibly other browsers, `null` cannot be passed as the
  // default here. The actual number of arguments in the function call must be correct,
  // or it will fail.
  if ((_localCloseCode, _localCloseReason)
      case (final closeCode?, final closeReason?)) {
    innerWebSocket.close(closeCode, closeReason);
  } else if (_localCloseCode case final closeCode?) {
    innerWebSocket.close(closeCode);
  } else {
    innerWebSocket.close();
  }
});

Here is a screenshot from the developer tools: Screenshot 2024-03-22 at 8 30 45 PM

ApophisLee commented 3 months ago

Additional Note: Invoking the original method directly in the developer tools allows for proper closure of the connection:

this.channel.innerWebSocket.close()

Screenshot 2024-03-22 at 10 21 46 PM

ApophisLee commented 3 months ago

This was a misunderstanding on my part, but regardless, the issue has been resolved. Here is the code:

      if (channel is HtmlWebSocketChannel) {
        (channel as HtmlWebSocketChannel).innerWebSocket.close();
      } else if (channel is IOWebSocketChannel) {
        (channel as IOWebSocketChannel).innerWebSocket?.close();
      }