Skip to content

Commit fc2c46a

Browse files
lrhnCommit Queue
authored andcommitted
Make Zone functions not call register*, but properly catch errors.
Make `Zone.createTimer`, `Zone.createPeriodicTimer` and `Zone.scheduleMicrotask` not call register on the same zone. These are low-level functions that should not build on top of other low-level functions. Instead the function is registered in the `Timer` constructors and top-level `scheduleMicrotask` code, and the root zone's `createTimer` and `scheduleMicrotask` just make sure that the callback will run in its original zone, and that uncaught errors are handled in the correct zone. Avoids a double-registration that timers did. Significant clean-up. - The private `_Zone`, `_ZoneSpecification` and `_ZoneDelegate` subclasses are not necessary now that their superclasses are `final`. - Stopped using type aliases instead of function types and old-style function arguments. - Renamed some parameters to not be, fx, `f`. Avoided some closure allocations for microtasks by putting the zone into the queue entry, instead of wrapping the callback in a closure calling `zone.run`. This is a potentially visible change if any code uses the zone functions directly and expect them to invoke `Zone.register*`. (Suspiciously no test failed with this change. May need to add some.) Fixes #59913. CoreLibraryReviewExempt: Have had all +1s, just not at the same time. Bug: https://dartbug.com/59913 Change-Id: Ie3c87f5f22aedcbe30ab04718df98e1c0f0659c3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/405020 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Lasse Nielsen <lrn@google.com>
1 parent f797e5f commit fc2c46a

File tree

11 files changed

+1252
-728
lines changed

11 files changed

+1252
-728
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@
44

55
### Libraries
66

7+
#### `dart:async`
8+
9+
- The `Timer` and `Timer.periodic` constructors and the top-level
10+
`scheduleMicrotask` function no longer *bind* and *guard* their callback
11+
in the current zone, using `Zone.bindCallbackGuarded` and
12+
`Zone.bindUnaryCallbackGuarded`. They only registers the callback
13+
before calling the zone's `Zone.createTimer`, `Zone.createPeriodicTimer`
14+
or `Zone.scheduleMicrotask` with the registered function.
15+
- The default `Zone.createTimer` and `Zone.createPeriodicTimer`,
16+
no longer registers the callback in the current zone, which would
17+
be a second registration if called through `Timer` or `Timer.periodic`.
18+
- The default (root) zone's `Zone.createTimer`, `Zone.createPeriodicTimer`
19+
and `Zone.scheduleMicrotask` now always run the callback using
20+
`Zone.run`/`Zone.runUnary`, and catch and reports errors from that
21+
to the same zone, rather than relying on the callback
22+
having been bound (and guarded) in the zone.
23+
- Together these changes ensure that if a custom zone's
24+
`Zone.registerCallback` (or similar for a unary or binary function)
25+
returns a new function which can throw, that thrown error is reported
26+
in the correct error zone.
27+
728
#### `dart:core`
829

930
- Added `Iterable.withIterator` constructor.

pkg/front_end/testcases/nnbd/return_async.dart.strong.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ static method allYield3() → asy::Future<void> async /* emittedValueType= void
2121
self::throwSync();
2222
}
2323
static method customErrorZone() → asy::Future<void> async /* emittedValueType= void */ {
24-
final asy::Completer<void> completer = asy::Completer::•<void>();
24+
final asy::Completer<void> completer = new asy::_AsyncCompleter::•<void>();
2525
asy::runZonedGuarded<asy::Future<Null>>(() → asy::Future<Null> async /* emittedValueType= Null */ {
2626
await self::allYield();
2727
completer.{asy::Completer::complete}(null){([FutureOr<void>?]) → void};

pkg/front_end/testcases/nnbd/return_async.dart.strong.modular.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ static method allYield3() → asy::Future<void> async /* emittedValueType= void
2121
self::throwSync();
2222
}
2323
static method customErrorZone() → asy::Future<void> async /* emittedValueType= void */ {
24-
final asy::Completer<void> completer = asy::Completer::•<void>();
24+
final asy::Completer<void> completer = new asy::_AsyncCompleter::•<void>();
2525
asy::runZonedGuarded<asy::Future<Null>>(() → asy::Future<Null> async /* emittedValueType= Null */ {
2626
await self::allYield();
2727
completer.{asy::Completer::complete}(null){([FutureOr<void>?]) → void};

pkg/front_end/testcases/nnbd/return_async.dart.strong.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ static method allYield3() → asy::Future<void> async /* emittedValueType= void
2121
self::throwSync();
2222
}
2323
static method customErrorZone() → asy::Future<void> async /* emittedValueType= void */ {
24-
final asy::Completer<void> completer = asy::Completer::•<void>();
24+
final asy::Completer<void> completer = new asy::_AsyncCompleter::•<void>();
2525
asy::runZonedGuarded<asy::Future<Null>>(() → asy::Future<Null> async /* emittedValueType= Null */ {
2626
await self::allYield();
2727
completer.{asy::Completer::complete}(null){([FutureOr<void>?]) → void};

sdk/lib/async/future.dart

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ abstract interface class Future<T> {
235235
static final _Future<void> _nullFuture = nullFuture as _Future<void>;
236236

237237
/// A `Future<bool>` completed with `false`.
238-
static final _Future<bool> _falseFuture = new _Future<bool>.zoneValue(
238+
static final _Future<bool> _falseFuture = _Future<bool>.zoneValue(
239239
false,
240240
_rootZone,
241241
);
@@ -253,7 +253,7 @@ abstract interface class Future<T> {
253253
/// If a non-future value is returned, the returned future is completed
254254
/// with that value.
255255
factory Future(FutureOr<T> computation()) {
256-
_Future<T> result = new _Future<T>();
256+
_Future<T> result = _Future<T>();
257257
Timer.run(() {
258258
FutureOr<T> computationResult;
259259
try {
@@ -280,7 +280,7 @@ abstract interface class Future<T> {
280280
/// If calling [computation] returns a non-future value,
281281
/// the returned future is completed with that value.
282282
factory Future.microtask(FutureOr<T> computation()) {
283-
_Future<T> result = new _Future<T>();
283+
_Future<T> result = _Future<T>();
284284
scheduleMicrotask(() {
285285
FutureOr<T> computationResult;
286286
try {
@@ -314,7 +314,7 @@ abstract interface class Future<T> {
314314
try {
315315
result = computation();
316316
} catch (error, stackTrace) {
317-
var future = new _Future<T>();
317+
var future = _Future<T>();
318318
_asyncCompleteWithErrorCallback(future, error, stackTrace);
319319
return future;
320320
}
@@ -349,7 +349,7 @@ abstract interface class Future<T> {
349349
@pragma("vm:entry-point")
350350
@pragma("vm:prefer-inline")
351351
factory Future.value([FutureOr<T>? value]) {
352-
return new _Future<T>.immediate(value == null ? value as T : value);
352+
return _Future<T>.immediate(value == null ? value as T : value);
353353
}
354354

355355
/// Creates a future that completes with an error.
@@ -371,7 +371,7 @@ abstract interface class Future<T> {
371371
/// ```
372372
factory Future.error(Object error, [StackTrace? stackTrace]) {
373373
AsyncError(:error, :stackTrace) = _interceptUserError(error, stackTrace);
374-
return new _Future<T>.immediateError(error, stackTrace);
374+
return _Future<T>.immediateError(error, stackTrace);
375375
}
376376

377377
/// Creates a future that runs its computation after a delay.
@@ -390,7 +390,7 @@ abstract interface class Future<T> {
390390
/// If [computation] is omitted,
391391
/// it will be treated as if [computation] was `() => null`,
392392
/// and the future will eventually complete with the `null` value.
393-
/// In that case, [T] must be nullable.
393+
/// In that case, [T] **must** be nullable.
394394
///
395395
/// If calling [computation] throws, the created future will complete with the
396396
/// error.
@@ -405,28 +405,32 @@ abstract interface class Future<T> {
405405
/// });
406406
/// ```
407407
factory Future.delayed(Duration duration, [FutureOr<T> computation()?]) {
408-
if (computation == null && !typeAcceptsNull<T>()) {
409-
throw ArgumentError.value(
410-
null,
411-
"computation",
412-
"The type parameter is not nullable",
413-
);
414-
}
415408
_Future<T> result = _Future<T>();
416-
new Timer(duration, () {
417-
if (computation == null) {
418-
result._complete(null as T);
409+
if (computation == null) {
410+
const T? defaultResult = null;
411+
if (defaultResult is T) {
412+
result._zone.createTimer(duration, () {
413+
result._complete(defaultResult);
414+
});
419415
} else {
416+
throw ArgumentError(
417+
"Required when the type parameter is not nullable",
418+
"computation",
419+
);
420+
}
421+
} else {
422+
var registeredComputation = result._zone.registerCallback(computation);
423+
result._zone.createTimer(duration, () {
420424
FutureOr<T> computationResult;
421425
try {
422-
computationResult = computation();
426+
computationResult = result._zone.run(registeredComputation);
423427
} catch (e, s) {
424428
_completeWithErrorCallback(result, e, s);
425429
return;
426430
}
427431
result._complete(computationResult);
428-
}
429-
});
432+
});
433+
}
430434
return result;
431435
}
432436

@@ -704,31 +708,63 @@ abstract interface class Future<T> {
704708
/// // Outputs: 'Finished with 3'
705709
/// ```
706710
static Future<void> doWhile(FutureOr<bool> action()) {
707-
_Future<void> doneSignal = new _Future<void>();
708-
late void Function(bool) nextIteration;
709-
// Bind this callback explicitly so that each iteration isn't bound in the
710-
// context of all the previous iterations' callbacks.
711-
// This avoids, e.g., deeply nested stack traces from the stack trace
711+
var zone = Zone.current;
712+
_Future<void> doneSignal = _Future<void>.zone(zone);
713+
714+
/// As a function instead of a tear-off because some compilers
715+
/// prefer that. (Same overhead for creating, but tear-offs have
716+
/// more complicated equality, which isn't used anyway.)
717+
void completeError(Object e, StackTrace s) {
718+
doneSignal._completeError(e, s);
719+
}
720+
721+
// Bind/register the callbacks only once, and reuse them on every iteration.
722+
// This avoids, e.g., deeply nested stack traces from the `stack_trace`
712723
// package.
713-
nextIteration = Zone.current.bindUnaryCallbackGuarded((bool keepGoing) {
724+
void Function(bool)? registeredNextIteration;
725+
void Function(Object, StackTrace)? registeredCompleteError;
726+
727+
/// True until the first asynchronous gap.
728+
bool sync = true;
729+
void nextIteration(bool keepGoing) {
714730
while (keepGoing) {
715731
FutureOr<bool> result;
716732
try {
717733
result = action();
718734
} catch (error, stackTrace) {
719-
// Cannot use _completeWithErrorCallback because it completes
720-
// the future synchronously.
721-
_asyncCompleteWithErrorCallback(doneSignal, error, stackTrace);
735+
if (sync) {
736+
// Complete asynchronously if still running synchronously.
737+
_asyncCompleteWithErrorCallback(doneSignal, error, stackTrace);
738+
} else {
739+
_completeWithErrorCallback(doneSignal, error, stackTrace);
740+
}
722741
return;
723742
}
724743
if (result is Future<bool>) {
725-
result.then(nextIteration, onError: doneSignal._completeError);
744+
if (result is _Future<bool>) {
745+
var onValue =
746+
registeredNextIteration ??= zone.registerUnaryCallback(
747+
nextIteration,
748+
);
749+
var onError =
750+
registeredCompleteError ??= zone.registerBinaryCallback(
751+
completeError,
752+
);
753+
// Reuse registered callbacks when it's a `_Future`.
754+
result._addThenListener(zone, onValue, onError);
755+
} else {
756+
// Let `then` do callback registration for non-native futures.
757+
// It will do that anyway.
758+
result.then(nextIteration, onError: completeError);
759+
}
760+
sync = false;
726761
return;
727762
}
728763
keepGoing = result;
729764
}
730765
doneSignal._complete(null);
731-
});
766+
}
767+
732768
nextIteration(true);
733769
return doneSignal;
734770
}
@@ -898,7 +934,7 @@ abstract interface class Future<T> {
898934

899935
/// Stop waiting for this future after [timeLimit] has passed.
900936
///
901-
/// Creates a new _timeout future_ that completes
937+
/// Creates a _timeout future_ that completes
902938
/// with the same result as this future, the _source future_,
903939
/// *if* the source future completes in time.
904940
///
@@ -1159,7 +1195,7 @@ class TimeoutException implements Exception {
11591195
/// one — you can use a Completer as follows:
11601196
/// ```dart
11611197
/// class AsyncOperation {
1162-
/// final Completer _completer = new Completer();
1198+
/// final Completer _completer = Completer();
11631199
///
11641200
/// Future<T> doOperation() {
11651201
/// _startOperation();
@@ -1198,7 +1234,7 @@ abstract interface class Completer<T> {
11981234
/// completer.complete('completion value');
11991235
/// }
12001236
/// ```
1201-
factory Completer() => new _AsyncCompleter<T>();
1237+
factory Completer() = _AsyncCompleter<T>;
12021238

12031239
/// Completes the future synchronously.
12041240
///
@@ -1249,7 +1285,7 @@ abstract interface class Completer<T> {
12491285
/// foo(); // In this case, foo() runs after bar().
12501286
/// });
12511287
/// ```
1252-
factory Completer.sync() => new _SyncCompleter<T>();
1288+
factory Completer.sync() = _SyncCompleter<T>;
12531289

12541290
/// The future that is completed by this completer.
12551291
///
@@ -1331,7 +1367,7 @@ abstract interface class Completer<T> {
13311367
bool get isCompleted;
13321368
}
13331369

1334-
// Helper function completing a _Future with error, but checking the zone
1370+
// Helper function completing a `_Future` with error, but checking the zone
13351371
// for error replacement and missing stack trace first.
13361372
// Only used for errors that are *caught*.
13371373
// A user provided error object should use `_interceptUserError` which

0 commit comments

Comments
 (0)