-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Problem
Given the following stream
Stream<String> foo() {
final controller = StreamController<String>();
controller.onListen = () => controller.add("FOO");
controller.onCancel = () => print("Shutting down");
controller.onPause = () => print("Pause");
controller.onResume = () => print("Resume");
return controller.stream;
}
If we then wrap this in
Stream<String> watch() async* {
final response = foo();
final mapped = response.map((response) {
return response.toUpperCase();
});
yield* mapped;
}
Everything works correctly
Future main() async {
final stream = watch().listen((x) => print(x));
await Future.delayed(Duration(seconds: 1));
await stream.cancel();
print("finished");
}
Prints as expected
FOO
Shutting down
finished
If, however, we change watch to be
Stream<String> watch() async* {
final response = foo();
await for (var response in response) {
yield response.toUpperCase();
}
}
Cancellation hangs indefinitely.
The specification has the following to say on the topic.
The stream associated with an asynchronous generator could be canceled
by any code with a reference to that stream at any point
where the generator was passivated.
Such a cancellation constitutes an irretrievable error for the generator.
At this point, the only plausible action for the generator is
to clean up after itself via its \FINALLY{} clauses.%
I'm not entirely sure what passivated means, but I think hanging indefinitely is at least not an obvious behaviour if it is intentional.
Platform
I have tested this on Linux (flutter), Android (flutter) and DartPad and the behaviour seems to be consistent