Sdk: StreamController.close returns a Future that never completes

Created on 30 May 2014  路  4Comments  路  Source: dart-lang/sdk

Consider the following code:

    void main() {
      var controller = new StreamController();
      controller.close().then((_) => print("done closing"));
    }

This exits without printing anything. The [controller.close] Future is never completing.

With issues as straightforward as this and issue #18586 going undetected by the library authors, I'm worried that the test coverage for dart:async is woefully insufficient.

area-library library-async type-documentation

Most helpful comment

Reopening since this the documentation is not updated and this is still confusing. The future will also never complete if the subscription was paused before the done even was delivered - this can easily happen in tests that use StreamQueue.

I think it's worth updating the documentation to make it clear that it's generally safe to ignore the returned Future, especially since usage of the unawaited_future lint is becoming more common.

All 4 comments

The future returned by StreamController.close completes when the done event has been delivered.
In this case, there is no listen on the non-broadcast stream, so the done event is never sent, and the future never completes.
If you add a c.stream.listen(null);, the returned future will complete.

I admit the documentation on this future is lacking. The close method that returns a future is inherited from StreamConsumer, but the documentation isn't clear what it means for the close call to be complete. For a controller, it means that the done event has been sent. I'll update the documentation to make this clear.


_Added AsDesigned label._

This is extremely confusing behavior, and contrary to the behavior of other [close] methods. Methods like [Socket.close] close when the underlying resources have been released, and methods like [StreamSubscription.cancel] return "null" if there's no additional work to wait on (which is worse than returning a completed Future, but still better than this).

Not only is returning a Future that you know will never complete confusing, it's extremely likely to cause deadlocks in unsuspecting programs. Please either follow [StreamSubscription.cancel]'s behavior or return a completed future if there are no events to deliver.


_Added Triaged label._

There is an event to deliver: The done event.
And the future will complete as soon as someone starts listening on the stream.

Having a non-broadcast controller and stream that never gets a listener is not recommended. If you add errors to the stream, they are also never reported anywhere.
If you wait for the done event to be delivered, it won't be. There is nothing magic about that.

The returned future waits for the done event to be delivered, whether there is a listener or not. At that point it assumes that the listener has cleaned up and completed what it needs to complete.
There is no difference between a controller that has delivered all its events and then you call 'close', and one that you call 'close' on as the first thing, and it shouldn't change behavior depending on when you listen.

The 'close' method with a future return is inherited from StreamSink. That was added as a request from dart:io, because they needed to know when a StreamSink was done processing its data - when the done event had been delivered.
I admit that the future returned by close can mostly be ignored on a StreamController (and it was a mistake to make StreamController be a StreamSink, we should have had a wrapper instead of making all StreamControllers more complex), but when it is used, it should act the way it currently does.


_Added AsDesigned label._

Reopening since this the documentation is not updated and this is still confusing. The future will also never complete if the subscription was paused before the done even was delivered - this can easily happen in tests that use StreamQueue.

I think it's worth updating the documentation to make it clear that it's generally safe to ignore the returned Future, especially since usage of the unawaited_future lint is becoming more common.

Was this page helpful?
0 / 5 - 0 ratings