Dart: Do I have to cancel Stream subscriptions and close StreamSinks?

I found in my case that if I have code like this:

Stream<String> readHackerNews(String path) {  
  StreamController<String> controller = StreamController<String>();

  ......

  return controller.stream;
}

I see a warning message "Close instance of 'dart.core.Sink'." in the Visual Studio Code. In order to fix this warning I added

controller.close()

to the event handler for the OnCancel event, see below:

Stream<String> readHackerNews(String path) {  
  StreamController<String> controller = StreamController<String>();

  //TODO: your code here

  controller.onCancel = () {
    controller.close();
  };

  return controller.stream;
}

Hope this helps!


Short-answer: no, but you should. Nothing in the contract of either StreamSubscription or StreamSink requires closing the resources, but some use cases can lead to memory leaks if you don't close them, even though in some cases, doing so might be confusing. Part of the confusion around these classes is that they are overloaded, and handle two fairly distinct use cases:

  1. Resource streams (like file I/O, network access)
  2. Event streams (like click handlers)

Let's tackle these subjects one at a time, first, StreamSubscription:

StreamSubscription

When you listen to a Stream, you receive a StreamSubscription. In general, when you are done listening to that Stream, for any reason, you should close the subscription. Not all streams will leak memory if choose not to, but, some will - for example, if you are reading input from a file, not closing the stream means the handle to the file may remain open.

So, while not strictly required, I'd always cancel when done accessing the stream.

StreamSink

The most common implementation of StreamSink is StreamController, which is a programmatic interface to creating a Stream. In general, when your stream is complete (i.e. all data emitted), you should close the controller.

Here is where it gets a little confusing. Let's look at those two cases:

File I/O

Imagine you were creating an API to asynchronously read a File line-by-line:

Stream<String> readLines(String path);

To implement this, you might use a StreamController:

Stream<String> readLines(String path) {
  SomeFileResource someResource;
  StreamController<String> controller;
  controller = new StreamController<String>(
    onListen: () {
      someResource = new SomeFileResource(path);
      // TODO: Implement adding to the controller.
    },
  );
  return controller.stream;
}

In this case, it would make lots of sense to close the controller when the last line has been read. This gives a signal to the user (a done event) that the file has been read, and is meaningful (you can close the File resource at that time, for example).

Events

Imagine you were creating an API to listen to news articles on HackerNews:

Stream<String> readHackerNews();

Here it makes less sense to close the underlying sink/controller. Does HackerNews ever stop? Event streams like this (or click handlers in UI programs) don't traditionally "stop" without the user accessing for it (i.e cancelling the StreamSubscription).

You could close the controller when you are done, but it's not required.


Hope that makes sense and helps you out!

Tags:

Stream

Dart