Revert "Refactor _Future."

This reverts commit 69f32d6ad7.

Reason for revert: We seem to have a number of tests failing with timeouts in CBUILD after this change, please see logs at
69f32d6ad7

Original change's description:
> Refactor `_Future`.
>
> This is a major rewrite of the `_Future` class,
> which is the default implementation of the `Future` interface.
>
> The main goal was to reduce the number of expensive type checks
> in the internal passing around of data.
> Expensive type checks are things like
> * `is _Future<T>` (more expensive than just `is _Future`, the latter
>   can be a single class-ID check.
> * Covariant generic parameter checks (using `T` covariantly in a
>   parameter forces a run-time type check).
>
> Also removed some plain unnecessary casts and turned some
> implicit casts from `dynamic` into `unsafeCast`s.
>
> This seems to be an success, at least on very primitive benchmarks, according to Golem:
> FutureCatchErrorTest    41.22% (1.9 noise)
> FutureValueTest         46.51% (2.8 noise)
> EmptyFutureTest         59.15% (3.1 noise)
> FutureWhenCompleteTest  51.10% (3.2 noise)
>
> A secondary goal was to clean up a very old and messy class,
> and make it clearer for other `dart:async` how to interact
> with the future.
>
> The change has a memory cost: The `_FutureListener<S,T>` class,
> which represents a `then`, `catchError` or `whenComplete`
> call on a `_Future`, now contains a reference to its source future,
> the one which provides the inputs to the callbacks,
> as well as the result future returned by the call.
> That's one extra memory slot per listener.
>
> In return, the `_FutureListener` now does not need to
> get its source future as an argument, which needs a covariant
> generic type check, and the methods of `_Future` can be written
> in a way which ignores the type parameters of both `_Future`
> and `_FutureListener`, which reduces complex type checks
> significantly.
>
> In general, typed code is in `_FutureListener`, which knows both
> the source and target types of the listener callbacks, and which
> contains the futures already at that type, so no extra type checking
> is needed.
> The `_Future` class is mostly untyped, except for its "public"
> API, called by other classes, which checks inputs,
> and code interacting with non-native futures.
> Invariants ensure that only correctly typed values
> are stored in the untyped shared `_resultOrListeners` field
> on `_Future`, as determined by its `_state` integer.
> (This was already partially true, and has simply been made
> more consistent.)
>
> Further, we now throw an error in a situation that was previously
> unhandled: When a `_Future` is completed with *itself*.
> That would ensure that the future would never complete
> (it waits for itself to complete before it can complete),
> and may potentially have caused weird loops in the representation.
> In practice, it probably never happens. Now it makes the error
> fail with an error.
> Currently a private `_FutureCyclicDependencyError` which presents
> as an `UnsupportedError`.
> That avoids code like
> ```dart
> import "dart:async";
> void main() {
>   var c = Completer();
>   c.complete(c.future); // bad.
>   print("well!");
>   var d = Completer();
>   d.complete(c.future);
>   print("shucks!");
> }
> ```
> from hanging the runtime by busily searching for the end of a cycle.
>
> See https://github.com/dart-lang/sdk/issues/48225
> Fixes #48225
>
> TEST= refactoring covered by existing tests, few new tests.
>
> Change-Id: Id9fc5af5fe011deb0af3e1e8a4ea3a91799f9da4
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/244241
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Lasse Nielsen <lrn@google.com>

TBR=lrn@google.com,kustermann@google.com,sra@google.com,sigmund@google.com,nshahan@google.com

Change-Id: I455be5a04b4c346df26d4ded0fa7388baccb0f8c
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/247762
Reviewed-by: Siva Annamalai <asiva@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Siva Annamalai 2022-06-09 16:51:55 +00:00 committed by Commit Bot
parent 83b50a70c8
commit 5cb3b37c74
30 changed files with 727 additions and 1205 deletions

View file

@ -125,8 +125,7 @@ const List<LineException> afterExceptions = const [
const LineException('_asyncAwait.<anonymous function>', 'async_patch.dart'), const LineException('_asyncAwait.<anonymous function>', 'async_patch.dart'),
const LineException('_asyncStart.<anonymous function>', 'async_patch.dart'), const LineException('_asyncStart.<anonymous function>', 'async_patch.dart'),
const LineException('_RootZone.runUnary', 'zone.dart'), const LineException('_RootZone.runUnary', 'zone.dart'),
const LineException('_FutureListener.propagate', 'future_impl.dart'), const LineException('_FutureListener.handleValue', 'future_impl.dart'),
const LineException('_FutureListener.propagateResults', 'future_impl.dart'),
const LineException('_Future._completeWithValue', 'future_impl.dart'), const LineException('_Future._completeWithValue', 'future_impl.dart'),
const LineException( const LineException(
'_Future._propagateToListeners.handleValueCallback', 'future_impl.dart'), '_Future._propagateToListeners.handleValueCallback', 'future_impl.dart'),
@ -137,7 +136,6 @@ const List<LineException> afterExceptions = const [
const LineException('_startMicrotaskLoop', 'schedule_microtask.dart'), const LineException('_startMicrotaskLoop', 'schedule_microtask.dart'),
const LineException('_AsyncRun._scheduleImmediateJsOverride.internalCallback', const LineException('_AsyncRun._scheduleImmediateJsOverride.internalCallback',
'async_patch.dart'), 'async_patch.dart'),
const LineException('Closure.cspForwardCall', 'js_helper.dart'),
const LineException('invokeClosure.<anonymous function>', 'js_helper.dart'), const LineException('invokeClosure.<anonymous function>', 'js_helper.dart'),
const LineException('invokeClosure', 'js_helper.dart'), const LineException('invokeClosure', 'js_helper.dart'),
const LineException('convertDartClosureToJS', 'js_helper.dart'), const LineException('convertDartClosureToJS', 'js_helper.dart'),

View file

@ -28,19 +28,19 @@ static method main() → dynamic
Extra constant evaluation status: Extra constant evaluation status:
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:814:13 -> SymbolConstant(#catchError) Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:821:13 -> SymbolConstant(#catchError)
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:814:13 -> ListConstant(const <Type*>[]) Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:821:13 -> ListConstant(const <Type*>[])
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:814:13 -> SymbolConstant(#test) Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:821:13 -> SymbolConstant(#test)
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> SymbolConstant(#whenComplete) Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> SymbolConstant(#whenComplete)
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> ListConstant(const <Type*>[]) Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> ListConstant(const <Type*>[])
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> MapConstant(const <Symbol*, dynamic>{}) Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> MapConstant(const <Symbol*, dynamic>{})
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:909:13 -> SymbolConstant(#timeout) Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:916:13 -> SymbolConstant(#timeout)
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:909:13 -> ListConstant(const <Type*>[]) Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:916:13 -> ListConstant(const <Type*>[])
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:909:13 -> SymbolConstant(#onTimeout) Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:916:13 -> SymbolConstant(#onTimeout)
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:763:13 -> SymbolConstant(#then) Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:770:13 -> SymbolConstant(#then)
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:763:13 -> SymbolConstant(#onError) Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:770:13 -> SymbolConstant(#onError)
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> SymbolConstant(#asStream) Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> SymbolConstant(#asStream)
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> ListConstant(const <Type*>[]) Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> ListConstant(const <Type*>[])
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> ListConstant(const <dynamic>[]) Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> ListConstant(const <dynamic>[])
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> MapConstant(const <Symbol*, dynamic>{}) Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> MapConstant(const <Symbol*, dynamic>{})
Extra constant evaluation: evaluated: 61, effectively constant: 15 Extra constant evaluation: evaluated: 61, effectively constant: 15

View file

@ -474,28 +474,3 @@ Future<void> runVMTests(
pause_on_unhandled_exceptions: pause_on_unhandled_exceptions); pause_on_unhandled_exceptions: pause_on_unhandled_exceptions);
} }
} }
/// Waits until the breakpoint map has been updated with the given
/// [breakpoint].
///
/// The `Isolate.addBreakpoint()` call will do a RPC call to the VM and return
/// the added breakpoint.
///
/// Though the `Isolate.breakpoints` list will *not* reflect this immediately.
/// This list is updated asynchronously by listening for
/// `ServiceEvent.kBreakpointAdded` events from the VM's `kDebugStream` (see
/// [VM] class)
Future waitUntilBreakpointIsReady(
Isolate isolate, Breakpoint breakpoint) async {
for (int i = 0; i < 100; ++i) {
var breakpoints = isolate.breakpoints;
if (breakpoints != null &&
breakpoints
.any((p) => p.breakpointNumber == breakpoint.breakpointNumber)) {
return;
}
await Future.delayed(const Duration(milliseconds: 1));
}
throw TimeoutException(
'The expected breakpoint has not been advertised by the VM in time');
}

View file

@ -60,7 +60,6 @@ var tests = <IsolateTest>[
LINE_A, LINE_A,
); );
isolate = await service.getIsolate(isolateId); isolate = await service.getIsolate(isolateId);
await waitUntilBreakpointIsReady(isolate, bpt);
expect(isolate.breakpoints!.length, 1); expect(isolate.breakpoints!.length, 1);
} }
@ -74,7 +73,6 @@ var tests = <IsolateTest>[
LINE_B, LINE_B,
); );
isolate = await service.getIsolate(isolateId); isolate = await service.getIsolate(isolateId);
await waitUntilBreakpointIsReady(isolate, bpt);
expect(isolate.breakpoints!.length, 2); expect(isolate.breakpoints!.length, 2);
} }
@ -88,7 +86,6 @@ var tests = <IsolateTest>[
LINE_C, LINE_C,
); );
isolate = await service.getIsolate(isolateId); isolate = await service.getIsolate(isolateId);
await waitUntilBreakpointIsReady(isolate, bpt);
expect(isolate.breakpoints!.length, 3); expect(isolate.breakpoints!.length, 3);
} }
// Wait for breakpoint events. // Wait for breakpoint events.

View file

@ -94,7 +94,6 @@ var tests = <IsolateTest>[
expect(location.line, 16); expect(location.line, 16);
isolate = await service.getIsolate(isolateId); isolate = await service.getIsolate(isolateId);
await waitUntilBreakpointIsReady(isolate, bpt);
expect(isolate.breakpoints!.length, 1); expect(isolate.breakpoints!.length, 1);
await completer.future; // Wait for breakpoint events. await completer.future; // Wait for breakpoint events.
@ -218,7 +217,6 @@ var tests = <IsolateTest>[
// Refresh isolate state. // Refresh isolate state.
isolate = await service.getIsolate(isolateId); isolate = await service.getIsolate(isolateId);
await waitUntilBreakpointIsReady(isolate, bpt);
expect(isolate.breakpoints!.length, 1); expect(isolate.breakpoints!.length, 1);
await completer.future; // Wait for breakpoint events. await completer.future; // Wait for breakpoint events.

View file

@ -95,7 +95,7 @@ final tests = <IsolateTest>[
(VmService service, IsolateRef isolateRef) async { (VmService service, IsolateRef isolateRef) async {
final result = await service.getStack(isolateRef.id!); final result = await service.getStack(isolateRef.id!);
expect(result.frames, hasLength(8)); // Must match frames below. expect(result.frames, hasLength(10));
expect(result.asyncCausalFrames, hasLength(26)); expect(result.asyncCausalFrames, hasLength(26));
expect(result.awaiterFrames, hasLength(13)); expect(result.awaiterFrames, hasLength(13));
@ -107,6 +107,8 @@ final tests = <IsolateTest>[
[equals('Regular'), anything], [equals('Regular'), anything],
[equals('Regular'), anything], [equals('Regular'), anything],
[equals('Regular'), anything], [equals('Regular'), anything],
[equals('Regular'), anything],
[equals('Regular'), anything],
[equals('Regular'), endsWith(' _RawReceivePortImpl._handleMessage')], [equals('Regular'), endsWith(' _RawReceivePortImpl._handleMessage')],
]); ]);

View file

@ -1793,10 +1793,8 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
case ServiceEvent.kPausePostRequest: case ServiceEvent.kPausePostRequest:
case ServiceEvent.kNone: case ServiceEvent.kNone:
case ServiceEvent.kResume: case ServiceEvent.kResume:
assert( assert((pauseEvent == null) ||
(pauseEvent == null) || !event.timestamp!.isBefore(pauseEvent!.timestamp));
!event.timestamp!.isBefore(pauseEvent!.timestamp),
"${event.timestamp} is before ${pauseEvent!.timestamp}");
pauseEvent = createEventFromServiceEvent(event) as M.DebugEvent; pauseEvent = createEventFromServiceEvent(event) as M.DebugEvent;
_updateRunState(); _updateRunState();
break; break;

View file

@ -54,7 +54,6 @@ var tests = <IsolateTest>[
expect(bpt.location!.script.id, equals(script.id)); expect(bpt.location!.script.id, equals(script.id));
expect(bpt.location!.script.tokenToLine(bpt.location!.tokenPos), expect(bpt.location!.script.tokenToLine(bpt.location!.tokenPos),
equals(LINE_A)); equals(LINE_A));
await waitUntilBreakpointIsReady(isolate.breakpoints, result);
expect(isolate.breakpoints.length, equals(1)); expect(isolate.breakpoints.length, equals(1));
} }
@ -66,7 +65,6 @@ var tests = <IsolateTest>[
expect(bpt.location!.script.id, equals(script.id)); expect(bpt.location!.script.id, equals(script.id));
expect(bpt.location!.script.tokenToLine(bpt.location!.tokenPos), expect(bpt.location!.script.tokenToLine(bpt.location!.tokenPos),
equals(LINE_B)); equals(LINE_B));
await waitUntilBreakpointIsReady(isolate.breakpoints, result);
expect(isolate.breakpoints.length, equals(2)); expect(isolate.breakpoints.length, equals(2));
} }
@ -78,7 +76,6 @@ var tests = <IsolateTest>[
expect(bpt.location!.script.id, equals(script.id)); expect(bpt.location!.script.id, equals(script.id));
expect(bpt.location!.script.tokenToLine(bpt.location!.tokenPos), expect(bpt.location!.script.tokenToLine(bpt.location!.tokenPos),
equals(LINE_C)); equals(LINE_C));
await waitUntilBreakpointIsReady(isolate.breakpoints, result);
expect(isolate.breakpoints.length, equals(3)); expect(isolate.breakpoints.length, equals(3));
} }

View file

@ -80,7 +80,6 @@ var tests = <IsolateTest>[
expect(bpt.location!.script.id, equals(script.id)); expect(bpt.location!.script.id, equals(script.id));
expect( expect(
bpt.location!.script.tokenToLine(bpt.location!.tokenPos), equals(15)); bpt.location!.script.tokenToLine(bpt.location!.tokenPos), equals(15));
await waitUntilBreakpointIsReady(isolate.breakpoints, bpt);
expect(isolate.breakpoints.length, equals(1)); expect(isolate.breakpoints.length, equals(1));
await completer.future; // Wait for breakpoint events. await completer.future; // Wait for breakpoint events.
@ -191,7 +190,6 @@ var tests = <IsolateTest>[
expect(bpt.location!.script.name, equals('debugging_test.dart')); expect(bpt.location!.script.name, equals('debugging_test.dart'));
expect( expect(
bpt.location!.script.tokenToLine(bpt.location!.tokenPos), equals(12)); bpt.location!.script.tokenToLine(bpt.location!.tokenPos), equals(12));
await waitUntilBreakpointIsReady(isolate.breakpoints, bpt);
expect(isolate.breakpoints.length, equals(1)); expect(isolate.breakpoints.length, equals(1));
await completer.future; // Wait for breakpoint events. await completer.future; // Wait for breakpoint events.

View file

@ -53,6 +53,7 @@ var tests = <IsolateTest>[
var frames = stack['frames']; var frames = stack['frames'];
var asyncFrames = stack['asyncCausalFrames']; var asyncFrames = stack['asyncCausalFrames'];
var awaiterFrames = stack['awaiterFrames']; var awaiterFrames = stack['awaiterFrames'];
expect(frames.length, greaterThanOrEqualTo(20));
expect(asyncFrames.length, greaterThan(frames.length)); expect(asyncFrames.length, greaterThan(frames.length));
expect(awaiterFrames.length, greaterThan(frames.length)); expect(awaiterFrames.length, greaterThan(frames.length));
expect(stack['truncated'], false); expect(stack['truncated'], false);

View file

@ -716,23 +716,3 @@ Future runDDSTests(List<String> mainArgs, List<DDSTest> tests,
); );
} }
} }
/// Waits until the breakpoint map has been updated with the given
/// [breakpoint].
///
/// The `Isolate.addBreakpoint()` call will do a RPC call to the VM and return
/// the added breakpoint.
///
/// Though the `Isolate.breakpoints` map will *not* reflect this immediately.
/// This map is updated asynchronously by listening for
/// `ServiceEvent.kBreakpointAdded` events from the VM's `kDebugStream` (see
/// [VM] class)
Future waitUntilBreakpointIsReady(
Map<int, Breakpoint> map, Breakpoint breakpoint) async {
for (int i = 0; i < 100; ++i) {
if (map.containsKey(breakpoint.number)) return;
await Future.delayed(const Duration(milliseconds: 1));
}
throw TimeoutException(
'The expected breakpoint has not been advertised by the VM in time');
}

View file

@ -54,7 +54,6 @@ var tests = <IsolateTest>[
expect(bpt.location.script.id, equals(script.id)); expect(bpt.location.script.id, equals(script.id));
expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), expect(bpt.location.script.tokenToLine(bpt.location.tokenPos),
equals(LINE_A)); equals(LINE_A));
await waitUntilBreakpointIsReady(isolate.breakpoints, result);
expect(isolate.breakpoints.length, equals(1)); expect(isolate.breakpoints.length, equals(1));
} }
@ -66,7 +65,6 @@ var tests = <IsolateTest>[
expect(bpt.location.script.id, equals(script.id)); expect(bpt.location.script.id, equals(script.id));
expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), expect(bpt.location.script.tokenToLine(bpt.location.tokenPos),
equals(LINE_B)); equals(LINE_B));
await waitUntilBreakpointIsReady(isolate.breakpoints, result);
expect(isolate.breakpoints.length, equals(2)); expect(isolate.breakpoints.length, equals(2));
} }
@ -78,7 +76,6 @@ var tests = <IsolateTest>[
expect(bpt.location.script.id, equals(script.id)); expect(bpt.location.script.id, equals(script.id));
expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), expect(bpt.location.script.tokenToLine(bpt.location.tokenPos),
equals(LINE_C)); equals(LINE_C));
await waitUntilBreakpointIsReady(isolate.breakpoints, result);
expect(isolate.breakpoints.length, equals(3)); expect(isolate.breakpoints.length, equals(3));
} }

View file

@ -79,7 +79,6 @@ var tests = <IsolateTest>[
expect(bpt.type, equals('Breakpoint')); expect(bpt.type, equals('Breakpoint'));
expect(bpt.location.script.id, equals(script.id)); expect(bpt.location.script.id, equals(script.id));
expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), equals(15)); expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), equals(15));
await waitUntilBreakpointIsReady(isolate.breakpoints, bpt);
expect(isolate.breakpoints.length, equals(1)); expect(isolate.breakpoints.length, equals(1));
await completer.future; // Wait for breakpoint events. await completer.future; // Wait for breakpoint events.
@ -189,7 +188,6 @@ var tests = <IsolateTest>[
expect(bpt.type, equals('Breakpoint')); expect(bpt.type, equals('Breakpoint'));
expect(bpt.location.script.name, equals('debugging_test.dart')); expect(bpt.location.script.name, equals('debugging_test.dart'));
expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), equals(12)); expect(bpt.location.script.tokenToLine(bpt.location.tokenPos), equals(12));
await waitUntilBreakpointIsReady(isolate.breakpoints, bpt);
expect(isolate.breakpoints.length, equals(1)); expect(isolate.breakpoints.length, equals(1));
await completer.future; // Wait for breakpoint events. await completer.future; // Wait for breakpoint events.

View file

@ -53,6 +53,7 @@ var tests = <IsolateTest>[
var frames = stack['frames']; var frames = stack['frames'];
var asyncFrames = stack['asyncCausalFrames']; var asyncFrames = stack['asyncCausalFrames'];
var awaiterFrames = stack['awaiterFrames']; var awaiterFrames = stack['awaiterFrames'];
expect(frames.length, greaterThanOrEqualTo(20));
expect(asyncFrames.length, greaterThan(frames.length)); expect(asyncFrames.length, greaterThan(frames.length));
expect(awaiterFrames.length, greaterThan(frames.length)); expect(awaiterFrames.length, greaterThan(frames.length));
expect(stack['truncated'], false); expect(stack['truncated'], false);

View file

@ -715,23 +715,3 @@ Future runDDSTests(List<String> mainArgs, List<DDSTest> tests,
); );
} }
} }
/// Waits until the breakpoint map has been updated with the given
/// [breakpoint].
///
/// The `Isolate.addBreakpoint()` call will do a RPC call to the VM and return
/// the added breakpoint.
///
/// Though the `Isolate.breakpoints` map will *not* reflect this immediately.
/// This map is updated asynchronously by listening for
/// `ServiceEvent.kBreakpointAdded` events from the VM's `kDebugStream` (see
/// [VM] class)
Future waitUntilBreakpointIsReady(
Map<int, Breakpoint> map, Breakpoint breakpoint) async {
for (int i = 0; i < 100; ++i) {
if (map.containsKey(breakpoint.number)) return;
await Future.delayed(const Duration(milliseconds: 1));
}
throw TimeoutException(
'The expected breakpoint has not been advertised by the VM in time');
}

View file

@ -281,10 +281,10 @@ namespace dart {
V(::, reachabilityFence, ReachabilityFence, 0x730f2b7f) \ V(::, reachabilityFence, ReachabilityFence, 0x730f2b7f) \
V(::, _asyncThenWrapperHelper, AsyncThenWrapperHelper, 0x0c17f838) \ V(::, _asyncThenWrapperHelper, AsyncThenWrapperHelper, 0x0c17f838) \
V(_Utf8Decoder, _scan, Utf8DecoderScan, 0xf296c901) \ V(_Utf8Decoder, _scan, Utf8DecoderScan, 0xf296c901) \
V(_Future, timeout, FutureTimeout, 0x8d415c97) \ V(_Future, timeout, FutureTimeout, 0xa7cb3294) \
V(Future, wait, FutureWait, 0xb0b596bd) \ V(Future, wait, FutureWait, 0xb0b596bd) \
V(_RootZone, runUnary, RootZoneRunUnary, 0xb607f8bf) \ V(_RootZone, runUnary, RootZoneRunUnary, 0xb607f8bf) \
V(_FutureListener, propagate, FutureListenerPropagate, 0x8a29635e) \ V(_FutureListener, handleValue, FutureListenerHandleValue, 0x438115a8) \
V(::, has63BitSmis, Has63BitSmis, 0xf61b56f1) \ V(::, has63BitSmis, Has63BitSmis, 0xf61b56f1) \
V(::, get:extensionStreamHasListener, ExtensionStreamHasListener, 0xfab46343)\ V(::, get:extensionStreamHasListener, ExtensionStreamHasListener, 0xfab46343)\

View file

@ -21,8 +21,8 @@ const intptr_t k_FutureListener_stateCatchError = 2;
// - sdk/lib/async/future_impl.dart:_FutureListener.stateWhenComplete. // - sdk/lib/async/future_impl.dart:_FutureListener.stateWhenComplete.
const intptr_t k_FutureListener_stateWhenComplete = 8; const intptr_t k_FutureListener_stateWhenComplete = 8;
// Keep in sync with sdk/lib/async/future_impl.dart:_FutureListener.propagate. // Keep in sync with sdk/lib/async/future_impl.dart:_FutureListener.handleValue.
const intptr_t kNumArgsFutureListenerPropagate = 0; const intptr_t kNumArgsFutureListenerHandleValue = 1;
// Find current yield index from async closure. // Find current yield index from async closure.
// Async closures contains a variable, :await_jump_var that holds the index into // Async closures contains a variable, :await_jump_var that holds the index into
@ -538,19 +538,19 @@ ClosurePtr StackTraceUtils::ClosureFromFrameFunction(
return caller_closure_finder->FindCaller(closure); return caller_closure_finder->FindCaller(closure);
} }
// May have been called from `_FutureListener.propagate`, which means its // May have been called from `_FutureListener.handleValue`, which means its
// receiver holds the Future chain. // receiver holds the Future chain.
DartFrameIterator future_frames(frames); DartFrameIterator future_frames(frames);
if (function.recognized_kind() == MethodRecognizer::kRootZoneRunUnary) { if (function.recognized_kind() == MethodRecognizer::kRootZoneRunUnary) {
frame = future_frames.NextFrame(); frame = future_frames.NextFrame();
function = frame->LookupDartFunction(); function = frame->LookupDartFunction();
if (function.recognized_kind() != if (function.recognized_kind() !=
MethodRecognizer::kFutureListenerPropagate) { MethodRecognizer::kFutureListenerHandleValue) {
return Closure::null(); return Closure::null();
} }
} }
if (function.recognized_kind() == if (function.recognized_kind() ==
MethodRecognizer::kFutureListenerPropagate) { MethodRecognizer::kFutureListenerHandleValue) {
*is_async = true; *is_async = true;
*skip_frame = true; *skip_frame = true;
@ -558,9 +558,9 @@ ClosurePtr StackTraceUtils::ClosureFromFrameFunction(
// before the arguments to the call. // before the arguments to the call.
Object& receiver = Object& receiver =
Object::Handle(*(reinterpret_cast<ObjectPtr*>(frame->GetCallerSp()) + Object::Handle(*(reinterpret_cast<ObjectPtr*>(frame->GetCallerSp()) +
kNumArgsFutureListenerPropagate)); kNumArgsFutureListenerHandleValue));
if (receiver.ptr() == Symbols::OptimizedOut().ptr()) { if (receiver.ptr() == Symbols::OptimizedOut().ptr()) {
// In the very rare case that _FutureListener.propagate has deoptimized // In the very rare case that _FutureListener.handleValue has deoptimized
// it may override the receiver slot in the caller frame with "<optimized // it may override the receiver slot in the caller frame with "<optimized
// out>" due to the `this` no longer being needed. // out>" due to the `this` no longer being needed.
return Closure::null(); return Closure::null();

View file

@ -27,7 +27,7 @@ _async<T>(Function() initGenerator) {
late Object? Function(Object?) onValue; late Object? Function(Object?) onValue;
late Object Function(Object, StackTrace?) onError; late Object Function(Object, StackTrace?) onError;
_Future<Object?> onAwait(Object? value) { onAwait(Object? value) {
_Future<Object?> f; _Future<Object?> f;
if (value is _Future) { if (value is _Future) {
f = value; f = value;
@ -84,15 +84,31 @@ _async<T>(Function() initGenerator) {
var iteratorValue = JS('', '#.next(null)', iter); var iteratorValue = JS('', '#.next(null)', iter);
var value = JS('', '#.value', iteratorValue); var value = JS('', '#.value', iteratorValue);
if (JS<bool>('!', '#.done', iteratorValue)) { if (JS<bool>('!', '#.done', iteratorValue)) {
if (isRunningAsEvent) { // TODO(jmesserly): this is a workaround for ignored cast failures.
asyncFuture._completeUnchecked(value); // Remove it once we've fixed those. We should be able to call:
//
// if (isRunningAsEvent) {
// asyncFuture._complete(value);
// } else {
// asyncFuture._asyncComplete(value);
// }
//
// But if the user code returns `Future<dynamic>` instead of
// `Future<T>`, that function won't recognize it as a future and will
// instead treat it as a completed value.
if (value is Future) {
if (value is _Future) {
_Future._chainCoreFuture(value, asyncFuture);
} else {
asyncFuture._chainForeignFuture(value);
}
} else if (isRunningAsEvent) {
asyncFuture._completeWithValue(JS('', '#', value));
} else { } else {
asyncFuture._asyncCompleteUnchecked(value); asyncFuture._asyncComplete(JS('', '#', value));
} }
} else { } else {
_Future<dynamic> awaitedFuture = onAwait(value); // _Future<Object?>. _Future._chainCoreFuture(onAwait(value), asyncFuture);
awaitedFuture.then(asyncFuture._completeWithValueUnchecked,
onError: asyncFuture._completeError);
} }
} catch (e, s) { } catch (e, s) {
if (isRunningAsEvent) { if (isRunningAsEvent) {

View file

@ -198,20 +198,27 @@ class _AsyncAwaitCompleter<T> implements Completer<T> {
void complete([FutureOr<T>? value]) { void complete([FutureOr<T>? value]) {
// All paths require that if value is null, null as T succeeds. // All paths require that if value is null, null as T succeeds.
value ??= value as T; value = (value == null) ? value as T : value;
if (!isSync) { if (!isSync) {
_future._asyncCompleteUnchecked(value); _future._asyncComplete(value);
} else if (value is Future<T>) {
assert(!_future._isComplete);
_future._chainFuture(value);
} else { } else {
_future._completeUnchecked(value); // TODO(40014): Remove cast when type promotion works.
// This would normally be `as T` but we use `as dynamic` to make the
// unneeded check be implicit to match dart2js unsound optimizations in
// the user code.
_future._completeWithValue(value as dynamic);
} }
} }
void completeError(Object e, [StackTrace? st]) { void completeError(Object e, [StackTrace? st]) {
var error = AsyncError(e, st); st ??= AsyncError.defaultStackTrace(e);
if (isSync) { if (isSync) {
_future._completeErrorObject(error); _future._completeError(e, st);
} else { } else {
_future._asyncCompleteErrorObject(error); _future._asyncCompleteError(e, st);
} }
} }

View file

@ -236,7 +236,7 @@ class _AsyncStarStreamController<T> {
if ((future != null) && future._mayComplete) { if ((future != null) && future._mayComplete) {
// If the stream has been cancelled, complete the cancellation future // If the stream has been cancelled, complete the cancellation future
// with the error. // with the error.
future._completeWithValueUnchecked(null); future._completeWithValue(null);
} }
controller.close(); controller.close();
} }
@ -292,7 +292,7 @@ void _completeOnAsyncReturn(_Future _future, Object? value, bool is_sync) {
if (!is_sync || value is Future) { if (!is_sync || value is Future) {
_future._asyncCompleteUnchecked(value); _future._asyncCompleteUnchecked(value);
} else { } else {
_future._completeWithValueUnchecked(value); _future._completeWithValue(value);
} }
} }
@ -303,9 +303,9 @@ void _completeWithNoFutureOnAsyncReturn(
// allow then and error handlers to be attached. // allow then and error handlers to be attached.
// async_jump_var=0 is prior to first await, =1 is first await. // async_jump_var=0 is prior to first await, =1 is first await.
if (!is_sync) { if (!is_sync) {
_future._asyncCompleteWithValueUnchecked(value); _future._asyncCompleteUncheckedNoFuture(value);
} else { } else {
_future._completeWithValueUnchecked(value); _future._completeWithValue(value);
} }
} }

View file

@ -108,7 +108,6 @@ import "dart:_internal"
CastStream, CastStream,
CastStreamTransformer, CastStreamTransformer,
checkNotNullable, checkNotNullable,
checkUnsoundType,
EmptyIterator, EmptyIterator,
IterableElementError, IterableElementError,
nullFuture, nullFuture,

View file

@ -303,13 +303,18 @@ abstract class Future<T> {
if (result is Future<T>) { if (result is Future<T>) {
return result; return result;
} else { } else {
return _Future<T>().._setValue(result); // TODO(40014): Remove cast when type promotion works.
return new _Future<T>.value(result as dynamic);
} }
} catch (error, stackTrace) { } catch (error, stackTrace) {
var currentZone = Zone._current; var future = new _Future<T>();
return _Future<T>.zone(currentZone) AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
.._asyncCompleteErrorObject( if (replacement != null) {
_interceptSyncError(currentZone, error, stackTrace)); future._asyncCompleteError(replacement.error, replacement.stackTrace);
} else {
future._asyncCompleteError(error, stackTrace);
}
return future;
} }
} }
@ -341,10 +346,7 @@ abstract class Future<T> {
@pragma("vm:entry-point") @pragma("vm:entry-point")
@pragma("vm:prefer-inline") @pragma("vm:prefer-inline")
factory Future.value([FutureOr<T>? value]) { factory Future.value([FutureOr<T>? value]) {
if (value != null) { return new _Future<T>.immediate(value == null ? value as T : value);
return _Future<T>().._asyncCompleteUnchecked(value);
}
return _Future<T>().._setValue(value as T);
} }
/// Creates a future that completes with an error. /// Creates a future that completes with an error.
@ -367,10 +369,15 @@ abstract class Future<T> {
factory Future.error(Object error, [StackTrace? stackTrace]) { factory Future.error(Object error, [StackTrace? stackTrace]) {
// TODO(40614): Remove once non-nullability is sound. // TODO(40614): Remove once non-nullability is sound.
checkNotNullable(error, "error"); checkNotNullable(error, "error");
var currentZone = Zone._current; if (!identical(Zone.current, _rootZone)) {
return _Future<T>.zone(currentZone) AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
.._asyncCompleteErrorObject( if (replacement != null) {
_interceptSyncError(currentZone, error, stackTrace)); error = replacement.error;
stackTrace = replacement.stackTrace;
}
}
stackTrace ??= AsyncError.defaultStackTrace(error);
return new _Future<T>.immediateError(error, stackTrace);
} }
/// Creates a future that runs its computation after a delay. /// Creates a future that runs its computation after a delay.
@ -1223,13 +1230,28 @@ abstract class Completer<T> {
// for error replacement and missing stack trace first. // for error replacement and missing stack trace first.
void _completeWithErrorCallback( void _completeWithErrorCallback(
_Future result, Object error, StackTrace? stackTrace) { _Future result, Object error, StackTrace? stackTrace) {
result._completeErrorObject( AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
_interceptSyncError(result._zone, error, stackTrace)); if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
} else {
stackTrace ??= AsyncError.defaultStackTrace(error);
}
result._completeError(error, stackTrace);
} }
// Like [_completeWithErrorCallback] but completes asynchronously. // Like [_completeWithErrorCallback] but completes asynchronously.
void _asyncCompleteWithErrorCallback( void _asyncCompleteWithErrorCallback(
_Future result, Object error, StackTrace? stackTrace) { _Future result, Object error, StackTrace? stackTrace) {
result._asyncCompleteErrorObject( AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
_interceptSyncError(result._zone, error, stackTrace)); if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
} else {
stackTrace ??= AsyncError.defaultStackTrace(error);
}
if (stackTrace == null) {
throw "unreachable"; // TODO(lrn): Remove when type promotion works.
}
result._asyncCompleteError(error, stackTrace);
} }

File diff suppressed because it is too large Load diff

View file

@ -1079,15 +1079,14 @@ class _StreamIterator<T> implements StreamIterator<T> {
void _onDone() { void _onDone() {
var subscription = _subscription; var subscription = _subscription;
_Future moveNextFuture = _stateData as dynamic; _Future<bool> moveNextFuture = _stateData as dynamic;
assert(moveNextFuture is _Future<bool>);
_subscription = null; _subscription = null;
_stateData = null; _stateData = null;
if (subscription != null) { if (subscription != null) {
moveNextFuture._completeWithValue(false); moveNextFuture._completeWithValue(false);
} else { } else {
// Event delivered during `listen` call. // Event delivered during `listen` call.
moveNextFuture._asyncCompleteWithValueUnchecked(false); moveNextFuture._asyncCompleteWithValue(false);
} }
} }
} }

View file

@ -5,7 +5,7 @@
part of dart.async; part of dart.async;
/// Runs user code and takes actions depending on success or failure. /// Runs user code and takes actions depending on success or failure.
void _runUserCode<T>(T userCode(), onSuccess(T value), _runUserCode<T>(T userCode(), onSuccess(T value),
onError(Object error, StackTrace stackTrace)) { onError(Object error, StackTrace stackTrace)) {
try { try {
onSuccess(userCode()); onSuccess(userCode());

View file

@ -762,17 +762,6 @@ T checkNotNullable<T extends Object>(T value, String name) {
return value; return value;
} }
/// Used in asserts to check that [object] has *unsound* type [T].
///
/// Throws if [object] does not have type [T] in sound null safe mode,
/// or it does not have type `T?` in unsound null safe mode.
///
/// Returns `true` if it doesn't throw, so it can be used in an `assert`.
bool checkUnsoundType<T>(Object? object) {
object as T;
return true;
}
/// A [TypeError] thrown by [checkNotNullable]. /// A [TypeError] thrown by [checkNotNullable].
class NotNullableError<T> extends Error implements TypeError { class NotNullableError<T> extends Error implements TypeError {
final String _name; final String _name;

View file

@ -4981,6 +4981,57 @@ Value:
] ]
] ]
], ],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[ [
"li", "li",
{ {

View file

@ -4981,6 +4981,57 @@ Value:
] ]
] ]
], ],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[ [
"li", "li",
{ {

View file

@ -1,63 +0,0 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:async_helper/async_helper.dart';
import "package:expect/expect.dart";
void main() async {
asyncStart();
var completer = Completer<int>();
// Should complete with error, but not synchronously.
completer.complete(completer.future);
await completer.future.then((value) {
Expect.fail("Completed with value $value.");
}, onError: (e, _) {
Expect.type<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
// Also if going through indirections.
var completer1 = Completer<int>();
var completer2 = Completer<int>();
// Should complete with error, but not synchronously.
completer1.complete(completer2.future);
completer2.complete(completer1.future);
completer1.future.ignore();
completer2.future.ignore();
await completer1.future.then((value) {
Expect.fail("Completed with value $value.");
}, onError: (e, _) {
Expect.type<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
await completer2.future.then((value) {
Expect.fail("Completed with value $value.");
}, onError: (e, _) {
Expect.type<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
// Also if coming from a callback.
completer1 = Completer<int>();
completer2 = Completer<int>();
completer2.complete(completer1.future.then((_) => completer2.future));
completer1.complete(1);
await completer2.future.then((value) {
Expect.fail("Completed with value $value.");
}, onError: (e, _) {
Expect.type<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
asyncEnd();
}

View file

@ -1,63 +0,0 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:async_helper/async_helper.dart';
import "package:expect/expect.dart";
void main() async {
asyncStart();
var completer = Completer<int>();
// Should complete with error, but not synchronously.
completer.complete(completer.future);
await completer.future.then((value) {
Expect.fail("Completed with value $value.");
}, onError: (e, _) {
Expect.type<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
// Also if going through indirections.
var completer1 = Completer<int>();
var completer2 = Completer<int>();
// Should complete with error, but not synchronously.
completer1.complete(completer2.future);
completer2.complete(completer1.future);
completer1.future.ignore();
completer2.future.ignore();
await completer1.future.then((value) {
Expect.fail("Completed with value $value.");
}, onError: (e, _) {
Expect.type<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
await completer2.future.then((value) {
Expect.fail("Completed with value $value.");
}, onError: (e, _) {
Expect.type<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
// Also if coming from a callback.
completer1 = Completer<int>();
completer2 = Completer<int>();
completer2.complete(completer1.future.then((_) => completer2.future));
completer1.complete(1);
await completer2.future.then((value) {
Expect.fail("Completed with value $value.");
}, onError: (e, _) {
Expect.type<UnsupportedError>(e);
}).timeout(const Duration(milliseconds: 1), onTimeout: () {
Expect.fail("Did not complete");
});
asyncEnd();
}