dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.09k stars 1.56k forks source link

Unexpected endless wait after call to WebSocket.isEmpty #30972

Open alsemenov opened 6 years ago

alsemenov commented 6 years ago

The following sample code demonstrates the problem:

import "dart:io";

main() async {
  HttpServer server = await HttpServer.bind("127.0.0.1", 0);
  server.listen((request) {
      WebSocketTransformer
          .upgrade(request)
          .then((websocket) {
        websocket.add("aaaaaa"); // comment this line and problem disappear
        websocket.close();
        server.close();
      });
  });

  WebSocket client = await WebSocket.connect("ws://127.0.0.1:${server.port}/");
  print(await client.isEmpty);
}

The program is supposed to print false and terminate. However it prints false, but never terminates. If line websocket.add("aaaaaa"); is commented, then program behaves as expected: prints true and terminates.

Dart VM version: 2.0.0-dev.1.0 (Wed Sep 20 14:42:31 2017) on "windows_x64"

alsemenov commented 6 years ago

After some experiments I have found out, that given test terminates normally, if I add line client.close() as last line:

import "dart:io";

main() async {
  HttpServer server = await HttpServer.bind("127.0.0.1", 0);
  server.listen((request) {
      WebSocketTransformer
          .upgrade(request)
          .then((websocket) {
        websocket.add("aaaaaa"); // comment this line and problem disappear
        websocket.close();
        server.close();
      });
  });

  WebSocket client = await WebSocket.connect("ws://127.0.0.1:${server.port}/");
  print(await client.isEmpty);
}

Still it takes several seconds after false is printed to terminate the test. If line websocket.add("aaaaaa"); is commented, then test terminates immediately after true is printed. It looks like a bug in isEmpty. According to isEmpty description, it subscribes to given stream and

Stops listening to the stream after the first element has been received.

Since WebSocket is a single-subscription stream, it should be closed on listener cancelation, but it seems that it is not closed.

The same behavior is observed for property first.

adamlofts commented 4 years ago

Stream.first does the following:

  1. Wait for the first element in the stream. When received:
  2. Cancel the stream
  3. Wait for the stream to complete
  4. Return the first element

This means the following code:

Stream stream() async* {
  print("yield at ${DateTime.now()}");
  yield "First";
  await Future.delayed(Duration(seconds: 5));
}

void main() async {
  print("got ${await stream().first} at ${DateTime.now()}");
}

Will print 2 lines with a 5 second gap between them.

yield at 2020-05-27 11:05:05.910
got First at 2020-05-27 11:05:10.915