-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Incorrect semantics for async*
#48695
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
/cc @lrhn Could you confirm that dart2js semantics (i.e. 1-8) is expected here? |
Yes, this is issue #34775 again. It's the root of a number of other issues. |
@alexmarkov Since you're re-working some async/async*/sync* machinery, maybe you can look into fixing the semantics here as well? |
The intended behavior should be achievable by using a If |
We have the same issue also for import 'dart:async';
main() async {
await for (final x in produce()) {
if (x == 1) break;
}
}
Stream produce() async* {
yield* produceInner();
print('should never print this');
}
Stream produceInner() async* {
yield 1;
yield 2;
} |
There's an interesting question what should happen if an Test case: import 'dart:async';
main() async {
final asyncValue = Completer();
final ss = produce().listen((value) {
asyncValue.complete(value);
});
print('Got ${await asyncValue.future}');
await ss.cancel();
print('Main done');
}
Stream produce() async* {
yield 1;
await Future.delayed(const Duration(seconds: 1));
print('Should not be printed!');
} Right now all our implementations would print @lrhn Should the semantics here be similar to the yields, that once the await returns and there's no longer a subscriber we should run finally blocks and exit? |
@mkustermann Yes, I can definitely take a look at this bug after I'm done with the new aync/async*/sync* implementation in the VM. @mkustermann @lrhn Could you please clarify what exactly is broken: |
I have made now a prototype in cl/239421 to prove that this would also fix flutter/flutter#100441 |
…a field is not a Future Issue flutter/flutter#100441 Issue #48695 TEST=vm/dart{,_2}/flutter_regress_100441_test Change-Id: Ifdd331dc3b0a2b825a938132808b4021c0af5f71 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/239311 Reviewed-by: Slava Egorov <[email protected]> Reviewed-by: Alexander Markov <[email protected]> Commit-Queue: Martin Kustermann <[email protected]>
@mkustermann We thought about this when designing Stream<int> foo() async* {
var resource = await allocateResource();
try {
while (something) yield other;
} finally {
resource.dispose();
}
} If the |
@lrhn That makes sense - just wanted to make sure. |
I don't think the VM's The Completer<void>? $pauseCompleter; // somewhere earlier
$c.onResume = $c.onCancel = () { var c = $pauseCompleter; $pauseCompleter = null; c?.complete(); };
// ...
var $v = e; // maybe handle throwing, if necessary
$c.add($v);
if ($c.isPaused) {
await ($pauseCompleter = Completer<void>()).future;
}
if (!$c.hasListener) return; The spec is https://github.com/dart-lang/language/blob/master/specification/dartLangSpec.tex#L18540 I actually think there is a bug in that, in that it doesn't check for pause after delivering the event. Getting an event is the precise point where a callback would pause, to stop more events from arriving. It's precisely where an It does check before sending the event, but that's not enough (and not really necessary if it checks afterwards). The |
👍 That's what my PoC in cl/239421 should do. |
@lrhn wrote:
The specification of |
The goal is to allow a ping-pong between an That's the optimal interaction between I'm sure I wrote this before. Somewhere, but apparently not into the spec. (It's safe to do an |
…active subscription (not paused/cancelled) This fixes an issue where VM would run the async* generator after a `yield` / `yield*` even though the subscription may be paused or cancelled. Furthermore this fixes an issue where `StackTrace.current` used in async* generator crashes VM and/or produces truncated stack trace. This fixes the following existing tests that were failing before: * co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_t08 * co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_t09 * co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_t10 * language/async_star/async_star_cancel_test * language/async_star/pause_test Issue flutter/flutter#100441 Issue #48695 Issue #34775 TEST=vm/dart{,_2}/causal_stacks/flutter_regress_100441_test Change-Id: I73f7d0b70937a3e3766b992740fa6fe6e6d57cec Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/239421 Reviewed-by: Lasse Nielsen <[email protected]> Commit-Queue: Martin Kustermann <[email protected]>
…there's active subscription (not paused/cancelled)" This reverts commit 837ee17. Reason for revert: Please see flutter/flutter#101514 Original change's description: > [vm] Fix some async* semantics issues: Only run generator if there's active subscription (not paused/cancelled) > > This fixes an issue where VM would run the async* generator after a > `yield` / `yield*` even though the subscription may be paused or > cancelled. > > Furthermore this fixes an issue where `StackTrace.current` used > in async* generator crashes VM and/or produces truncated stack > trace. > > This fixes the following existing tests that were failing before: > > * co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_t08 > * co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_t09 > * co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_t10 > * language/async_star/async_star_cancel_test > * language/async_star/pause_test > > Issue flutter/flutter#100441 > Issue #48695 > Issue #34775 > > TEST=vm/dart{,_2}/causal_stacks/flutter_regress_100441_test > > Change-Id: I73f7d0b70937a3e3766b992740fa6fe6e6d57cec > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/239421 > Reviewed-by: Lasse Nielsen <[email protected]> > Commit-Queue: Martin Kustermann <[email protected]> # Not skipping CQ checks because original CL landed > 1 day ago. Change-Id: Ic3d9c0508310a33a2aaee67860c0bb2ec83bab4a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/240506 Reviewed-by: Siva Annamalai <[email protected]> Reviewed-by: Ben Konyi <[email protected]> Commit-Queue: Siva Annamalai <[email protected]>
…there's active subscription (not paused/cancelled)" This fixes an issue where VM would run the async* generator after a `yield` / `yield*` even though the subscription may be paused or cancelled. Furthermore this fixes an issue where `StackTrace.current` used in async* generator crashes VM and/or produces truncated stack trace. This fixes the following existing tests that were failing before: * co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_t08 * co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_t09 * co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_t10 * language/async_star/async_star_cancel_test * language/async_star/pause_test New in reland: Allow the generator to to cause cancelling it's own consumer. This addresses the issue of original revert -> see flutter/flutter#101514 Issue flutter/flutter#100441 Issue #48695 Issue #34775 TEST=vm/dart{,_2}/causal_stacks/flutter_regress_100441_test Change-Id: I091b7159d59ea15fc31162b4b6b17260d523d7cb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/242400 Reviewed-by: Lasse Nielsen <[email protected]> Commit-Queue: Martin Kustermann <[email protected]>
@mkustermann Is this bug fully fixed in efdffab? Is there anything left for me to look at? |
The particular semantics issue I was mainly concerned about (generator continues running even if listener cancelled) should be fixed, yes. Though reading @lrhn 's description there may be other things left to do, which would change semantics a little and increase performance. Mainly I'm thinking about this comment from @lrhn
Right now every @pragma("vm:entry-point", "call")
bool add(T event) {
....
controller.add(event);
if (!controller.hasListener) ...
scheduleGenerator();
isSuspendedAtYield = true;
return false;
} => As can be see here, in the normal circumstance, we always call @lrhn Did your comment above mean semantics would allow avoiding this microtask hop for every |
Yes. The semantics are written to allow (but not require) having no async gap between the My only worry is that such synchronous ping-pong can potentially lead to starvation of other code, if the code is written to assume async gaps. |
See the following example:
One would expect the numbers
1 2 3 4 5 6 7 8
be printed in order.Dart2js does seem to have this semantics.
Though running in the Dart VM yields instead
1 2 3 4 6 5 7 8
.This is due to the fact that after
produceInner
has yielded it's value, it will not actually yield, but rather continue (with small delay due to enqueing in microtask queue). This logic is in our async_patch.dart:The text was updated successfully, but these errors were encountered: