Skip to content

Commit c97f7b7

Browse files
lrhnCommit Bot
authored and
Commit Bot
committed
Make nullFuture be per-zone.
We introduced a `nullFuture` during the null-safety migration where we changed some methods to no longer allow returning `null`, and they therefore had to return a `Future`. That affected timing, because returning `null` was processed synchronously, and that change in timing made some tests fail. Rather that fix the fragile tests, we made the function return a recognizable future, a canonical `Future<Null>.value(null)`, and then recognized it and took a synchronous path for it. That caused other issues, because the future was created in the root zone. (Well, originally, it was created in the first zone which needed one, that was worse. Now it's created in the root zone.) Some code tries to contain asynchrony inside a custom zone, and then the get a `nullFuture` and calls `then` on it, and that schedules a microtask in the root zone. (It should probably have used the listener's zone, and not store a zone in the future at all, but that's how it was first done, and now people rely on that behavior too.) This change creates a `null` future *per zone* (lazily initialized when asked for). That should be sufficient because the code recognizing a returned `null` future is generally running in the same zone, but if any other code gets the `nullFuture`, it will be in the expected zone for where it was requested. This is a reland of commit a247b15 Change-Id: Ieec74d6f93c57175c357ec18889144635f5bdca6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/249490 Commit-Queue: Lasse Nielsen <[email protected]> Reviewed-by: Erik Ernst <[email protected]>
1 parent db6b267 commit c97f7b7

File tree

10 files changed

+70
-45
lines changed

10 files changed

+70
-45
lines changed

pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,19 @@ static method main() → dynamic
2828

2929

3030
Extra constant evaluation status:
31-
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:821:13 -> SymbolConstant(#catchError)
32-
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:821:13 -> ListConstant(const <Type*>[])
33-
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:821:13 -> SymbolConstant(#test)
34-
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> SymbolConstant(#whenComplete)
35-
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> ListConstant(const <Type*>[])
36-
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:872:13 -> MapConstant(const <Symbol*, dynamic>{})
37-
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:916:13 -> SymbolConstant(#timeout)
38-
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:916:13 -> ListConstant(const <Type*>[])
39-
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:916:13 -> SymbolConstant(#onTimeout)
40-
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:770:13 -> SymbolConstant(#then)
41-
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:770:13 -> SymbolConstant(#onError)
42-
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> SymbolConstant(#asStream)
43-
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> ListConstant(const <Type*>[])
44-
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> ListConstant(const <dynamic>[])
45-
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:881:13 -> MapConstant(const <Symbol*, dynamic>{})
31+
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:814:13 -> SymbolConstant(#catchError)
32+
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:814:13 -> ListConstant(const <Type*>[])
33+
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:814:13 -> SymbolConstant(#test)
34+
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> SymbolConstant(#whenComplete)
35+
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> ListConstant(const <Type*>[])
36+
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:865:13 -> MapConstant(const <Symbol*, dynamic>{})
37+
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:909:13 -> SymbolConstant(#timeout)
38+
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:909:13 -> ListConstant(const <Type*>[])
39+
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:909:13 -> SymbolConstant(#onTimeout)
40+
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:763:13 -> SymbolConstant(#then)
41+
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:763:13 -> SymbolConstant(#onError)
42+
Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> SymbolConstant(#asStream)
43+
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> ListConstant(const <Type*>[])
44+
Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> ListConstant(const <dynamic>[])
45+
Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:874:13 -> MapConstant(const <Symbol*, dynamic>{})
4646
Extra constant evaluation: evaluated: 61, effectively constant: 15

sdk/lib/async/async.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import "dart:_internal"
109109
CastStreamTransformer,
110110
checkNotNullable,
111111
EmptyIterator,
112+
isNullFuture,
112113
IterableElementError,
113114
nullFuture,
114115
printToZone,

sdk/lib/async/future.dart

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,6 @@ abstract class FutureOr<T> {
223223
/// it's very clearly documented.
224224
@pragma("wasm:entry-point")
225225
abstract class Future<T> {
226-
/// A `Future<Null>` completed with `null`.
227-
///
228-
/// Currently shared with `dart:internal`.
229-
/// If that future can be removed, then change this back to
230-
/// `_Future<Null>.zoneValue(null, _rootZone);`
231-
static final _Future<Null> _nullFuture = nullFuture as _Future<Null>;
232-
233226
/// A `Future<bool>` completed with `false`.
234227
static final _Future<bool> _falseFuture =
235228
new _Future<bool>.zoneValue(false, _rootZone);

sdk/lib/async/stream.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ abstract class Stream<T> {
515515
controller
516516
..onCancel = () {
517517
timer.cancel();
518-
return Future._nullFuture;
518+
return nullFuture;
519519
}
520520
..onPause = () {
521521
watch.stop();

sdk/lib/async/stream_controller.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,10 @@ abstract class _StreamController<T> implements _StreamControllerBase<T> {
588588
Future<void> get done => _ensureDoneFuture();
589589

590590
Future<void> _ensureDoneFuture() =>
591-
_doneFuture ??= _isCanceled ? Future._nullFuture : _Future<void>();
591+
_doneFuture ??
592+
(_isCanceled
593+
? nullFuture as Future<void>
594+
: _doneFuture = _Future<void>());
592595

593596
/// Send or enqueue a data event.
594597
void add(T value) {
@@ -919,7 +922,7 @@ class _AddStreamState<T> {
919922
var cancel = addSubscription.cancel();
920923
if (cancel == null) {
921924
addStreamFuture._asyncComplete(null);
922-
return Future._nullFuture;
925+
return nullFuture;
923926
}
924927
return cancel.whenComplete(() {
925928
addStreamFuture._asyncComplete(null);

sdk/lib/async/stream_impl.dart

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ class _BufferingStreamSubscription<T>
197197
if (!_isCanceled) {
198198
_cancel();
199199
}
200-
return _cancelFuture ?? Future._nullFuture;
200+
return _cancelFuture ?? nullFuture;
201201
}
202202

203203
Future<E> asFuture<E>([E? futureValue]) {
@@ -217,7 +217,7 @@ class _BufferingStreamSubscription<T>
217217
};
218218
_onError = (Object error, StackTrace stackTrace) {
219219
Future cancelFuture = cancel();
220-
if (!identical(cancelFuture, Future._nullFuture)) {
220+
if (!isNullFuture(Zone._current, cancelFuture)) {
221221
cancelFuture.whenComplete(() {
222222
result._completeError(error, stackTrace);
223223
});
@@ -297,7 +297,7 @@ class _BufferingStreamSubscription<T>
297297
// Hooks called when the input is paused, unpaused or canceled.
298298
// These must not throw. If overwritten to call user code, include suitable
299299
// try/catch wrapping and send any errors to
300-
// [_Zone.current.handleUncaughtError].
300+
// [Zone._current.handleUncaughtError].
301301
void _onPause() {
302302
assert(_isInputPaused);
303303
}
@@ -352,7 +352,6 @@ class _BufferingStreamSubscription<T>
352352
// future to finish we must not report the error.
353353
if (_isCanceled && !_waitsForCancel) return;
354354
_state |= _STATE_IN_CALLBACK;
355-
// TODO(floitsch): this dynamic should be 'void'.
356355
var onError = _onError;
357356
if (onError is void Function(Object, StackTrace)) {
358357
_zone.runBinaryGuarded<Object, StackTrace>(onError, error, stackTrace);
@@ -366,8 +365,7 @@ class _BufferingStreamSubscription<T>
366365
_state |= _STATE_WAIT_FOR_CANCEL;
367366
_cancel();
368367
var cancelFuture = _cancelFuture;
369-
if (cancelFuture != null &&
370-
!identical(cancelFuture, Future._nullFuture)) {
368+
if (cancelFuture != null && !isNullFuture(Zone._current, cancelFuture)) {
371369
cancelFuture.whenComplete(sendError);
372370
} else {
373371
sendError();
@@ -396,7 +394,7 @@ class _BufferingStreamSubscription<T>
396394
_cancel();
397395
_state |= _STATE_WAIT_FOR_CANCEL;
398396
var cancelFuture = _cancelFuture;
399-
if (cancelFuture != null && !identical(cancelFuture, Future._nullFuture)) {
397+
if (cancelFuture != null && !isNullFuture(Zone._current, cancelFuture)) {
400398
cancelFuture.whenComplete(sendDone);
401399
} else {
402400
sendDone();
@@ -672,7 +670,7 @@ class _DoneStreamSubscription<T> implements StreamSubscription<T> {
672670
}
673671
}
674672

675-
Future cancel() => Future._nullFuture;
673+
Future cancel() => nullFuture;
676674

677675
Future<E> asFuture<E>([E? futureValue]) {
678676
E resultValue;
@@ -819,7 +817,7 @@ class _BroadcastSubscriptionWrapper<T> implements StreamSubscription<T> {
819817

820818
Future cancel() {
821819
_stream._cancelSubscription();
822-
return Future._nullFuture;
820+
return nullFuture;
823821
}
824822

825823
bool get isPaused {
@@ -963,7 +961,7 @@ class _StreamIterator<T> implements StreamIterator<T> {
963961
}
964962
return subscription.cancel();
965963
}
966-
return Future._nullFuture;
964+
return nullFuture;
967965
}
968966

969967
void _onData(T data) {

sdk/lib/async/stream_pipe.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ _runUserCode<T>(T userCode(), onSuccess(T value),
2626
void _cancelAndError(StreamSubscription subscription, _Future future,
2727
Object error, StackTrace stackTrace) {
2828
var cancelFuture = subscription.cancel();
29-
if (cancelFuture != null && !identical(cancelFuture, Future._nullFuture)) {
29+
if (cancelFuture != null && !isNullFuture(Zone._current, cancelFuture)) {
3030
cancelFuture.whenComplete(() => future._completeError(error, stackTrace));
3131
} else {
3232
future._completeError(error, stackTrace);
@@ -55,7 +55,7 @@ void Function(Object error, StackTrace stackTrace) _cancelAndErrorClosure(
5555
before completing with a value. */
5656
void _cancelAndValue(StreamSubscription subscription, _Future future, value) {
5757
var cancelFuture = subscription.cancel();
58-
if (cancelFuture != null && !identical(cancelFuture, Future._nullFuture)) {
58+
if (cancelFuture != null && !isNullFuture(Zone._current, cancelFuture)) {
5959
cancelFuture.whenComplete(() => future._complete(value));
6060
} else {
6161
future._complete(value);

sdk/lib/internal/internal.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ int parseHexByte(String source, int index) {
136136
return digit1 * 16 + digit2 - (digit2 & 256);
137137
}
138138

139-
/// A reusable `null`-valued future used by `dart:async`.
139+
/// A reusable `null`-valued future per zone used by `dart:async`.
140140
///
141141
/// **DO NOT USE.**
142142
///
@@ -156,7 +156,14 @@ int parseHexByte(String source, int index) {
156156
/// This future will be removed again if we can ever do so.
157157
/// Do not use it for anything other than preserving timing
158158
/// during the null safety migration.
159-
final Future<Null> nullFuture = Zone.root.run(() => Future<Null>.value(null));
159+
Future<Null> get nullFuture =>
160+
_nullFutures[Zone.current] ??= Future<Null>.value(null);
161+
162+
/// Whether [future] is the null future of the current zone.
163+
bool isNullFuture(Zone zone, Future future) =>
164+
identical(_nullFutures[zone], future);
165+
166+
final Expando<Future<Null>> _nullFutures = Expando<Future<Null>>();
160167

161168
/// A default hash function used by the platform in various places.
162169
///

tests/lib/async/null_future_zone_test.dart

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,33 @@ main() {
1313
Expect.isFalse(await it.moveNext());
1414

1515
late Future nullFuture;
16-
late Future falseFuture;
1716

17+
bool nullFutureZoneUsed = false;
1818
runZoned(() {
19+
// Known code that exposes the special "nullFuture".
1920
nullFuture = (new StreamController()..stream.listen(null).cancel()).done;
20-
falseFuture = it.moveNext();
2121
}, zoneSpecification: new ZoneSpecification(scheduleMicrotask:
2222
(Zone self, ZoneDelegate parent, Zone zone, void f()) {
23-
Expect.fail("Should not be called");
23+
Expect.identical(zone, self);
24+
nullFutureZoneUsed = true;
25+
parent.scheduleMicrotask(zone, f);
2426
}));
2527

2628
nullFuture.then((value) {
2729
Expect.isNull(value);
30+
Expect.isTrue(nullFutureZoneUsed);
2831
asyncEnd();
2932
});
3033

34+
late Future falseFuture;
35+
36+
runZoned(() {
37+
falseFuture = it.moveNext();
38+
}, zoneSpecification: new ZoneSpecification(scheduleMicrotask:
39+
(Zone self, ZoneDelegate parent, Zone zone, void f()) {
40+
Expect.fail("Should not be called");
41+
}));
42+
3143
falseFuture.then((value) {
3244
Expect.isFalse(value);
3345
asyncEnd();

tests/lib_2/async/null_future_zone_test.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,32 @@ main() {
1515
Expect.isFalse(await it.moveNext());
1616

1717
Future nullFuture;
18-
Future falseFuture;
1918

19+
bool nullFutureZoneUsed = false;
2020
runZoned(() {
2121
nullFuture = (new StreamController()..stream.listen(null).cancel()).done;
22-
falseFuture = it.moveNext();
2322
}, zoneSpecification: new ZoneSpecification(scheduleMicrotask:
2423
(Zone self, ZoneDelegate parent, Zone zone, void f()) {
25-
Expect.fail("Should not be called");
24+
Expect.identical(zone, self);
25+
nullFutureZoneUsed = true;
26+
parent.scheduleMicrotask(zone, f);
2627
}));
2728

2829
nullFuture.then((value) {
2930
Expect.isNull(value);
31+
Expect.isTrue(nullFutureZoneUsed);
3032
asyncEnd();
3133
});
3234

35+
Future falseFuture;
36+
37+
runZoned(() {
38+
falseFuture = it.moveNext();
39+
}, zoneSpecification: new ZoneSpecification(scheduleMicrotask:
40+
(Zone self, ZoneDelegate parent, Zone zone, void f()) {
41+
Expect.fail("Should not be called");
42+
}));
43+
3344
falseFuture.then((value) {
3445
Expect.isFalse(value);
3546
asyncEnd();

0 commit comments

Comments
 (0)