-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
StreamController.close returns a Future that never completes #19095
Comments
The future returned by StreamController.close completes when the done event has been delivered. 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. 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. 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. 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. 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 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 |
I think the docs or the implementation is incorrect for broadcast stream controllers. The docs say: A broadcast stream controller will send the done event even if listeners are paused, so some broadcast events may not have been received yet when the returned future completes. import 'dart:async';
void main() async {
final c = StreamController<int>.broadcast();
final stream = c.stream;
final sub = stream.listen(print);
c.add(100);
sub.pause();
await c.close(); //nothing completes this
print('closed');
} |
That is misleading. The done future is completed when the done event has been processed, not just sent. I think it's slightly weird that a broadcast controller cares about its listeners being there or not, but there is a practical reason for it. The reason for the "done future" existing at all, is that the stream controller/owner should not start cleaning up resources required by events until all events have been delivered. Even a broadcast stream can need that. Stream listeners can pause their subscription until they have fully processed an event, that way they can tell the stream source that they're not done yet. When they receive the done event, they report that back, and both sides now know that all events have been fully processed. So there is a reason. It's just not documented very well. |
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.
The text was updated successfully, but these errors were encountered: