Skip to content

Commit f1394aa

Browse files
mralephCommit Queue
authored andcommitted
[vm] Fix unwinding through FutureIterable and FutureRecordN wait
Add awaiter-links leading from callbacks attached to the original Futures to Completer produced by wait. This requires changing representation of _FutureResult slightly because we do not support annotating arbitrary fields as awaiter-links only variables can be annotated Thus we need to capture onReady callback in a the context of closures passed to Future.then. Fixes #59730 TEST=pkg/vm_service/test/pause_on_unhandled_exceptions_future_extensions_test.dart,runtime/tests/vm/dart/awaiter_stacks/async_stacks_test.dart CoreLibraryReviewExempt: No API or significant implementation changes Change-Id: I904b441b27c4e06f08b7ea7ba066a2fb03504ce6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/401062 Commit-Queue: Slava Egorov <[email protected]> Reviewed-by: Lasse Nielsen <[email protected]>
1 parent 5de66cc commit f1394aa

File tree

3 files changed

+213
-29
lines changed

3 files changed

+213
-29
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Regression test for https://github.com/dart-lang/sdk/issues/59730: make
6+
// sure that awaiter stack is correctly reconstructed for `FutureIterable`
7+
// and `FutureRecordN` wait extensions.
8+
9+
import 'common/service_test_common.dart';
10+
import 'common/test_helper.dart';
11+
12+
Future<void> throwAsync() async {
13+
await Future.delayed(const Duration(milliseconds: 100));
14+
throw 'Throw from throwAsync!';
15+
}
16+
17+
Future<void> testeeMain() async {
18+
try {
19+
await [throwAsync()].wait;
20+
} catch (e) {
21+
// Ignore.
22+
}
23+
24+
try {
25+
await (throwAsync(), throwAsync()).wait;
26+
} catch (e) {
27+
// Ignore.
28+
}
29+
30+
await [throwAsync()].wait.catchError((e) {
31+
// Ignore.
32+
return [];
33+
});
34+
35+
await (throwAsync(), throwAsync()).wait.catchError((e) {
36+
// Ignore.
37+
return (null, null);
38+
});
39+
}
40+
41+
final tests = <IsolateTest>[
42+
// We shouldn't get any debugger breaks before exit as all exceptions are
43+
// caught.
44+
hasStoppedAtExit,
45+
];
46+
47+
void main([args = const <String>[]]) => runIsolateTests(
48+
args,
49+
tests,
50+
'pause_on_unhandled_exceptions_future_extensions_test.dart',
51+
pauseOnUnhandledExceptions: true,
52+
pauseOnExit: true,
53+
testeeConcurrent: testeeMain,
54+
);

runtime/tests/vm/dart/awaiter_stacks/async_stacks_test.dart

Lines changed: 132 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,15 @@ Future listenAsyncStarThrowAsync() async {
154154

155155
Future<void> customErrorZone() async {
156156
final completer = Completer<void>();
157-
runZonedGuarded(() async {
158-
await allYield();
159-
completer.complete(null);
160-
}, (e, s) {
161-
completer.completeError(e, s);
162-
});
157+
runZonedGuarded(
158+
() async {
159+
await allYield();
160+
completer.complete(null);
161+
},
162+
(e, s) {
163+
completer.completeError(e, s);
164+
},
165+
);
163166
return completer.future;
164167
}
165168

@@ -176,9 +179,12 @@ Future awaitTimeoutHappens() async {
176179
}
177180

178181
Future awaitTimeoutHappensThrowFromOnTimeout() async {
179-
await neverCompletes().timeout(Duration(milliseconds: 1), onTimeout: () {
180-
throw 'timeout';
181-
});
182+
await neverCompletes().timeout(
183+
Duration(milliseconds: 1),
184+
onTimeout: () {
185+
throw 'timeout';
186+
},
187+
);
182188
}
183189

184190
// ----
@@ -190,10 +196,36 @@ Future awaitWait() async {
190196
throwAsync(),
191197
() async {
192198
await Future.value();
193-
}()
199+
}(),
194200
]);
195201
}
196202

203+
// ----
204+
// Scenario: FutureIterable.wait:
205+
// ----
206+
207+
Future awaitIterableWaitExtension() async {
208+
await [
209+
throwAsync(),
210+
() async {
211+
await Future.value();
212+
}(),
213+
].wait;
214+
}
215+
216+
// ----
217+
// Scenario: FutureRecord.wait:
218+
// ----
219+
220+
Future awaitRecordWaitExtension() async {
221+
await (
222+
throwAsync(),
223+
() async {
224+
await Future.value();
225+
}(),
226+
).wait;
227+
}
228+
197229
// ----
198230
// Scenario: Future.whenComplete:
199231
// ----
@@ -207,9 +239,11 @@ Future futureSyncWhenComplete() {
207239
// ----
208240

209241
Future futureThen() {
210-
return Future.value(0).then((value) {
211-
throwSync();
212-
}).then(_doSomething);
242+
return Future.value(0)
243+
.then((value) {
244+
throwSync();
245+
})
246+
.then(_doSomething);
213247
}
214248

215249
void _doSomething(_) {
@@ -259,6 +293,8 @@ Future<void> main(List<String> args) async {
259293
awaitTimeoutHappens,
260294
awaitTimeoutHappensThrowFromOnTimeout,
261295
awaitWait,
296+
awaitIterableWaitExtension,
297+
awaitRecordWaitExtension,
262298
futureSyncWhenComplete,
263299
futureThen,
264300
];
@@ -762,6 +798,88 @@ final currentExpectations = [
762798
"""
763799
#0 throwAsync (%test%)
764800
<asynchronous suspension>
801+
#1 FutureIterable.wait.<anonymous closure> (future_extensions.dart)
802+
<asynchronous suspension>
803+
#2 awaitIterableWaitExtension (%test%)
804+
<asynchronous suspension>
805+
#3 doTestAwait (%test%)
806+
<asynchronous suspension>
807+
#4 runTest (harness.dart)
808+
<asynchronous suspension>
809+
#5 main (%test%)
810+
<asynchronous suspension>""",
811+
"""
812+
#0 throwAsync (%test%)
813+
<asynchronous suspension>
814+
#1 FutureIterable.wait.<anonymous closure> (future_extensions.dart)
815+
<asynchronous suspension>
816+
#2 awaitIterableWaitExtension (%test%)
817+
<asynchronous suspension>
818+
#3 doTestAwaitThen.<anonymous closure> (%test%)
819+
<asynchronous suspension>
820+
#4 doTestAwaitThen (%test%)
821+
<asynchronous suspension>
822+
#5 runTest (harness.dart)
823+
<asynchronous suspension>
824+
#6 main (%test%)
825+
<asynchronous suspension>""",
826+
"""
827+
#0 throwAsync (%test%)
828+
<asynchronous suspension>
829+
#1 FutureIterable.wait.<anonymous closure> (future_extensions.dart)
830+
<asynchronous suspension>
831+
#2 awaitIterableWaitExtension (%test%)
832+
<asynchronous suspension>
833+
#3 doTestAwaitCatchError (%test%)
834+
<asynchronous suspension>
835+
#4 runTest (harness.dart)
836+
<asynchronous suspension>
837+
#5 main (%test%)
838+
<asynchronous suspension>""",
839+
"""
840+
#0 throwAsync (%test%)
841+
<asynchronous suspension>
842+
#1 FutureRecord2.wait.<anonymous closure> (future_extensions.dart)
843+
<asynchronous suspension>
844+
#2 awaitRecordWaitExtension (%test%)
845+
<asynchronous suspension>
846+
#3 doTestAwait (%test%)
847+
<asynchronous suspension>
848+
#4 runTest (harness.dart)
849+
<asynchronous suspension>
850+
#5 main (%test%)
851+
<asynchronous suspension>""",
852+
"""
853+
#0 throwAsync (%test%)
854+
<asynchronous suspension>
855+
#1 FutureRecord2.wait.<anonymous closure> (future_extensions.dart)
856+
<asynchronous suspension>
857+
#2 awaitRecordWaitExtension (%test%)
858+
<asynchronous suspension>
859+
#3 doTestAwaitThen.<anonymous closure> (%test%)
860+
<asynchronous suspension>
861+
#4 doTestAwaitThen (%test%)
862+
<asynchronous suspension>
863+
#5 runTest (harness.dart)
864+
<asynchronous suspension>
865+
#6 main (%test%)
866+
<asynchronous suspension>""",
867+
"""
868+
#0 throwAsync (%test%)
869+
<asynchronous suspension>
870+
#1 FutureRecord2.wait.<anonymous closure> (future_extensions.dart)
871+
<asynchronous suspension>
872+
#2 awaitRecordWaitExtension (%test%)
873+
<asynchronous suspension>
874+
#3 doTestAwaitCatchError (%test%)
875+
<asynchronous suspension>
876+
#4 runTest (harness.dart)
877+
<asynchronous suspension>
878+
#5 main (%test%)
879+
<asynchronous suspension>""",
880+
"""
881+
#0 throwAsync (%test%)
882+
<asynchronous suspension>
765883
#1 futureSyncWhenComplete.<anonymous closure> (%test%)
766884
<asynchronous suspension>
767885
#2 doTestAwait (%test%)
@@ -831,6 +949,6 @@ final currentExpectations = [
831949
#4 runTest (harness.dart)
832950
<asynchronous suspension>
833951
#5 main (%test%)
834-
<asynchronous suspension>"""
952+
<asynchronous suspension>""",
835953
];
836954
// CURRENT EXPECTATIONS END

sdk/lib/async/future_extensions.dart

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ extension FutureIterable<T> on Iterable<Future<T>> {
3131
Future<List<T>> get wait {
3232
var results = [for (var f in this) _FutureResult<T>(f)];
3333
if (results.isEmpty) return Future<List<T>>.value(<T>[]);
34+
35+
@pragma('vm:awaiter-link')
3436
final c = Completer<List<T>>.sync();
37+
3538
_FutureResult._waitAll(results, (errors) {
3639
if (errors == 0) {
3740
c.complete([for (var r in results) r.value]);
@@ -77,6 +80,7 @@ bool _notNull(Object? object) => object != null;
7780
extension FutureRecord2<T1, T2> on (Future<T1>, Future<T2>) {
7881
/// {@macro record-parallel-wait}
7982
Future<(T1, T2)> get wait {
83+
@pragma('vm:awaiter-link')
8084
final c = Completer<(T1, T2)>.sync();
8185
final v1 = _FutureResult<T1>($1);
8286
final v2 = _FutureResult<T2>($2);
@@ -104,6 +108,7 @@ extension FutureRecord2<T1, T2> on (Future<T1>, Future<T2>) {
104108
extension FutureRecord3<T1, T2, T3> on (Future<T1>, Future<T2>, Future<T3>) {
105109
/// {@macro record-parallel-wait}
106110
Future<(T1, T2, T3)> get wait {
111+
@pragma('vm:awaiter-link')
107112
final c = Completer<(T1, T2, T3)>.sync();
108113
final v1 = _FutureResult<T1>($1);
109114
final v2 = _FutureResult<T2>($2);
@@ -133,6 +138,7 @@ extension FutureRecord4<T1, T2, T3, T4>
133138
on (Future<T1>, Future<T2>, Future<T3>, Future<T4>) {
134139
/// {@macro record-parallel-wait}
135140
Future<(T1, T2, T3, T4)> get wait {
141+
@pragma('vm:awaiter-link')
136142
final c = Completer<(T1, T2, T3, T4)>.sync();
137143
final v1 = _FutureResult<T1>($1);
138144
final v2 = _FutureResult<T2>($2);
@@ -167,6 +173,7 @@ extension FutureRecord5<T1, T2, T3, T4, T5>
167173
on (Future<T1>, Future<T2>, Future<T3>, Future<T4>, Future<T5>) {
168174
/// {@macro record-parallel-wait}
169175
Future<(T1, T2, T3, T4, T5)> get wait {
176+
@pragma('vm:awaiter-link')
170177
final c = Completer<(T1, T2, T3, T4, T5)>.sync();
171178
final v1 = _FutureResult<T1>($1);
172179
final v2 = _FutureResult<T2>($2);
@@ -223,6 +230,7 @@ extension FutureRecord6<T1, T2, T3, T4, T5, T6>
223230
) {
224231
/// {@macro record-parallel-wait}
225232
Future<(T1, T2, T3, T4, T5, T6)> get wait {
233+
@pragma('vm:awaiter-link')
226234
final c = Completer<(T1, T2, T3, T4, T5, T6)>.sync();
227235
final v1 = _FutureResult<T1>($1);
228236
final v2 = _FutureResult<T2>($2);
@@ -291,6 +299,7 @@ extension FutureRecord7<T1, T2, T3, T4, T5, T6, T7>
291299
) {
292300
/// {@macro record-parallel-wait}
293301
Future<(T1, T2, T3, T4, T5, T6, T7)> get wait {
302+
@pragma('vm:awaiter-link')
294303
final c = Completer<(T1, T2, T3, T4, T5, T6, T7)>.sync();
295304
final v1 = _FutureResult<T1>($1);
296305
final v2 = _FutureResult<T2>($2);
@@ -365,6 +374,7 @@ extension FutureRecord8<T1, T2, T3, T4, T5, T6, T7, T8>
365374
) {
366375
/// {@macro record-parallel-wait}
367376
Future<(T1, T2, T3, T4, T5, T6, T7, T8)> get wait {
377+
@pragma('vm:awaiter-link')
368378
final c = Completer<(T1, T2, T3, T4, T5, T6, T7, T8)>.sync();
369379
final v1 = _FutureResult<T1>($1);
370380
final v2 = _FutureResult<T2>($2);
@@ -445,6 +455,7 @@ extension FutureRecord9<T1, T2, T3, T4, T5, T6, T7, T8, T9>
445455
) {
446456
/// {@macro record-parallel-wait}
447457
Future<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> get wait {
458+
@pragma('vm:awaiter-link')
448459
final c = Completer<(T1, T2, T3, T4, T5, T6, T7, T8, T9)>.sync();
449460
final v1 = _FutureResult<T1>($1);
450461
final v2 = _FutureResult<T2>($2);
@@ -596,6 +607,8 @@ class _FutureResult<T> {
596607
// Consider integrating directly into `_Future` as a `_FutureListener`
597608
// to avoid creating as many function tear-offs.
598609

610+
final Future<T> source;
611+
599612
/// The value or `null`.
600613
///
601614
/// Set when the future completes with a value.
@@ -604,34 +617,33 @@ class _FutureResult<T> {
604617
/// Set when the future completes with an error or value.
605618
AsyncError? errorOrNull;
606619

607-
void Function(int errors) onReady = _noop;
608-
609-
_FutureResult(Future<T> future) {
610-
future.then(_onValue, onError: _onError);
611-
}
620+
_FutureResult(this.source);
612621

613622
/// The value.
614623
///
615624
/// Should only be used when the future is known to have completed with
616625
/// a value.
617626
T get value => valueOrNull ?? valueOrNull as T;
618627

619-
void _onValue(T value) {
620-
valueOrNull = value;
621-
onReady(0);
622-
}
623-
624-
void _onError(Object error, StackTrace stack) {
625-
this.errorOrNull = AsyncError(error, stack);
626-
onReady(1);
628+
void _wait(@pragma('vm:awaiter-link') void Function(int) whenReady) {
629+
source.then(
630+
(T value) {
631+
valueOrNull = value;
632+
whenReady(0);
633+
},
634+
onError: (Object error, StackTrace stack) {
635+
errorOrNull = AsyncError(error, stack);
636+
whenReady(1);
637+
},
638+
);
627639
}
628640

629641
/// Waits for a number of [_FutureResult]s to all have completed.
630642
///
631643
/// List must not be empty.
632644
static void _waitAll(
633645
List<_FutureResult> results,
634-
void Function(int) whenReady,
646+
@pragma('vm:awaiter-link') void Function(int) whenReady,
635647
) {
636648
assert(results.isNotEmpty);
637649
var ready = 0;
@@ -644,7 +656,7 @@ class _FutureResult<T> {
644656
}
645657

646658
for (var r in results) {
647-
r.onReady = onReady;
659+
r._wait(onReady);
648660
}
649661
}
650662

0 commit comments

Comments
 (0)