-
Notifications
You must be signed in to change notification settings - Fork 1.7k
yield*
desugaring doesn't handle errors correctly
#52464
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
Comments
I'm testing this in https://dart-review.googlesource.com/c/sdk/+/354224. |
Catching an error is not enough. You need to forward the event and continue streaming. The desugaring should not use A better approach may be await controller.addAll(Stream<int>.error("error")); // cancelOnError: false is default.
if (!controller.hasListener) return; Consider this example: // Emits: value 1, error StateError, value 2
Stream<int> embeddedError() => Stream.multi((c) {
c.add(1);
c.addError(StateError("banana"));
c.add(2);
c.close();
});
// Should emit: value -1, value 1, error StateERror, value 2, value -1
Stream<int> wrap(Stream<int> source) async* {
yield -1;
yield* source;
yield -1;
}
// Should print: "value -1", "value 1", "error Bad state: banana", "value 2", "value -1", "done"
void main() {
wrap(embeddedError()).listen((v) {
print("value $v");
}, onError: (e, s) {
print("error $e");
}, onDone: () {
print("done");
});
} It should print all six lines. The desugaring I usually recommend for an Stream<T> name(args) {
var controller = StreamController<T>(sync: true);
void _body() async {
Completer<void>? _$paused;
controller.onResume = controller.onCancel = () {
_$paused?.complete(null);
_$paused = null;
};
$body:
try {
// Insert *body* with the following changes:
yield* e; // ->
await controller.addStream(e);
if (!controller.hasListener) break $body;
yield e; // ->
controller.add(e);
if (controller.isPaused) {
await (_$paused = Completer()).future;
}
if (!controller.hasListener) break $body;
return; ->
break $body;
// end *body*.
} catch (e, s) {
controller.addError(e, s);
}
controller.close();
}
controller.onListen = () {
scheduleMicrotask(_body); // Async* bodies start asynchronously.
// Could also insert an `await null;` at the start of the compiled body instead
// and use `controller.onListen = _body;`.
};
return controller.stream;
} |
I'm implementing the suggested desugaring above in https://dart-review.googlesource.com/c/sdk/+/355360. |
@lrhn I implemented the desugaring in https://dart-review.googlesource.com/c/sdk/+/355360, and I think it's missing a yield after import "dart:async";
Stream<int> s() async* {
for (int i = 0; i < 2; i++) {
print("$i 0");
yield i;
print("$i 3");
}
}
void main() async {
await for (int i in s()) {
print("$i 1");
await Future.microtask(() {});
print("$i 2");
}
} This prints
If I desugar import "dart:async";
Stream<int> s() {
StreamController<int> controller = StreamController<int>();
var body = () async {
Completer<void>? paused;
dynamic onCancelCallback = () {
if (paused != null) {
paused!.complete(null);
}
paused = null;
};
controller.onResume = onCancelCallback;
controller.onCancel = onCancelCallback;
try {
try {
for (int i = 0; i < 2; i = i + 1) {
print("$i 0");
controller.add(i);
if (controller.isPaused) {
await (paused = Completer<void>());
}
if (!controller.hasListener) {
return;
}
print("$i 3");
}
} catch (err, stack) {
controller.addError(err, stack);
}
} finally {
controller.close();
}
};
controller.onListen = () {
scheduleMicrotask(body);
};
return controller.stream;
}
void main() async {
await for (int i in s()) {
print("$i 1");
await Future.microtask(() {});
print("$i 2");
}
} This version prints
We need to yield after the line |
Hm, I think yielding after an element is not enough, we should pause the generator until the generated element is consumed, right? The current desugaring yields a sdk/pkg/dart2wasm/lib/transformers.dart Lines 344 to 347 in 3f1d6a9
|
Yes, the desugaring only works if the controller is If the controller is not
That does assume that the microtask queue is a queue. It's possible for zones to intercept the microtask queue and make it, fx, a stack. They probably won't. |
Uh oh!
There was an error while loading. Please reload this page.
This is covered in test
language/async_star/async_star_test
. Minimal repro:This should print "error" and then "1", but currently it prints "error" and then crashes with uncaught exception
undefined:0: [object WebAssembly.Exception]
. https://dart-review.googlesource.com/c/sdk/+/304501 somewhat improves this (it no longer crashes), but it still doesn't print "1".With the linked CL, desugared
testtt
looks like this:I think we need a
catch
somewhere in the desugaredawait* ...
to callcontroller.addError(...)
.The text was updated successfully, but these errors were encountered: