@@ -30,10 +30,10 @@ class _InternalDisposable implements _Disposable {
30
30
31
31
@override
32
32
Future <Null > dispose () {
33
- var disposeFuture = _disposer () ;
33
+ var disposeFuture = _disposer != null ? _disposer () : null ;
34
34
_disposer = null ;
35
35
if (disposeFuture == null ) {
36
- return new Future (() => null );
36
+ return new Future . value ( );
37
37
}
38
38
return disposeFuture.then ((_) => null );
39
39
}
@@ -300,12 +300,27 @@ class Disposable implements _Disposable, DisposableManagerV3 {
300
300
@override
301
301
void manageStreamController (StreamController controller) {
302
302
_throwOnInvalidCall ('manageStreamController' , 'controller' , controller);
303
- _internalDisposables.add (new _InternalDisposable (() {
304
- if (! controller.hasListener) {
303
+ // If a single-subscription stream has a subscription and that
304
+ // subscription is subsequently canceled, the `done` future will
305
+ // complete, but there is no other way for us to tell that this
306
+ // is what has happened. If we then listen to the stream (since
307
+ // closing a stream that was never listened to never completes) we'll
308
+ // get an exception. This workaround allows us to "know" when a
309
+ // subscription has been canceled so we don't bother trying to
310
+ // listen to the stream before closing it.
311
+ bool isDone = false ;
312
+ var disposable = new _InternalDisposable (() {
313
+ if (! controller.hasListener && ! controller.isClosed && ! isDone) {
305
314
controller.stream.listen ((_) {});
306
315
}
307
316
return controller.close ();
308
- }));
317
+ });
318
+ controller.done.then ((_) {
319
+ isDone = true ;
320
+ _internalDisposables.remove (disposable);
321
+ disposable.dispose ();
322
+ });
323
+ _internalDisposables.add (disposable);
309
324
}
310
325
311
326
@mustCallSuper
0 commit comments