Skip to content

Commit d9d7d30

Browse files
emmanuel-pCommit Bot
authored and
Commit Bot
committed
Revert "Make nullFuture be per-zone."
This reverts commit 4d750a8. Reason for revert: breaks google3 (b/236665701) Original change's description: > 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: Ia113756de1f6d50af4b1abfec219d6b4dcd5d59b > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/249488 > Reviewed-by: Nate Bosch <[email protected]> > Commit-Queue: Lasse Nielsen <[email protected]> [email protected],[email protected] Change-Id: I02d62d58bae33d6a606a80eb3eee2e8e721a8e20 No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/249620 Commit-Queue: Emmanuel Pellereau <[email protected]> Reviewed-by: Emmanuel Pellereau <[email protected]> Reviewed-by: Nate Bosch <[email protected]>
1 parent 9cc3b14 commit d9d7d30

13 files changed

+78
-118
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: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>{})
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>{})
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
@@ -110,6 +110,7 @@ import "dart:_internal"
110110
checkNotNullable,
111111
EmptyIterator,
112112
IterableElementError,
113+
nullFuture,
113114
printToZone,
114115
printToConsole,
115116
Since,

sdk/lib/async/future.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@ 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+
226233
/// A `Future<bool>` completed with `false`.
227234
static final _Future<bool> _falseFuture =
228235
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 Zone._current._nullFuture;
518+
return Future._nullFuture;
519519
}
520520
..onPause = () {
521521
watch.stop();

sdk/lib/async/stream_controller.dart

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

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

596593
/// Send or enqueue a data event.
597594
void add(T value) {
@@ -922,7 +919,7 @@ class _AddStreamState<T> {
922919
var cancel = addSubscription.cancel();
923920
if (cancel == null) {
924921
addStreamFuture._asyncComplete(null);
925-
return Zone._current._nullFuture;
922+
return Future._nullFuture;
926923
}
927924
return cancel.whenComplete(() {
928925
addStreamFuture._asyncComplete(null);

sdk/lib/async/stream_impl.dart

Lines changed: 9 additions & 9 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 ?? Zone._current._nullFuture;
200+
return _cancelFuture ?? Future._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(Zone._current._nullFuture, cancelFuture)) {
220+
if (!identical(cancelFuture, Future._nullFuture)) {
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,6 +352,7 @@ 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'.
355356
var onError = _onError;
356357
if (onError is void Function(Object, StackTrace)) {
357358
_zone.runBinaryGuarded<Object, StackTrace>(onError, error, stackTrace);
@@ -366,7 +367,7 @@ class _BufferingStreamSubscription<T>
366367
_cancel();
367368
var cancelFuture = _cancelFuture;
368369
if (cancelFuture != null &&
369-
!identical(Zone._current._nullFuture, cancelFuture)) {
370+
!identical(cancelFuture, Future._nullFuture)) {
370371
cancelFuture.whenComplete(sendError);
371372
} else {
372373
sendError();
@@ -395,8 +396,7 @@ class _BufferingStreamSubscription<T>
395396
_cancel();
396397
_state |= _STATE_WAIT_FOR_CANCEL;
397398
var cancelFuture = _cancelFuture;
398-
if (cancelFuture != null &&
399-
!identical(Zone._current._nullFuture, cancelFuture)) {
399+
if (cancelFuture != null && !identical(cancelFuture, Future._nullFuture)) {
400400
cancelFuture.whenComplete(sendDone);
401401
} else {
402402
sendDone();
@@ -672,7 +672,7 @@ class _DoneStreamSubscription<T> implements StreamSubscription<T> {
672672
}
673673
}
674674

675-
Future cancel() => Zone._current._nullFuture;
675+
Future cancel() => Future._nullFuture;
676676

677677
Future<E> asFuture<E>([E? futureValue]) {
678678
E resultValue;
@@ -819,7 +819,7 @@ class _BroadcastSubscriptionWrapper<T> implements StreamSubscription<T> {
819819

820820
Future cancel() {
821821
_stream._cancelSubscription();
822-
return Zone._current._nullFuture;
822+
return Future._nullFuture;
823823
}
824824

825825
bool get isPaused {
@@ -963,7 +963,7 @@ class _StreamIterator<T> implements StreamIterator<T> {
963963
}
964964
return subscription.cancel();
965965
}
966-
return Zone._current._nullFuture;
966+
return Future._nullFuture;
967967
}
968968

969969
void _onData(T data) {

sdk/lib/async/stream_pipe.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +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 &&
30-
!identical(Zone._current._nullFuture, cancelFuture)) {
29+
if (cancelFuture != null && !identical(cancelFuture, Future._nullFuture)) {
3130
cancelFuture.whenComplete(() => future._completeError(error, stackTrace));
3231
} else {
3332
future._completeError(error, stackTrace);
@@ -56,8 +55,7 @@ void Function(Object error, StackTrace stackTrace) _cancelAndErrorClosure(
5655
before completing with a value. */
5756
void _cancelAndValue(StreamSubscription subscription, _Future future, value) {
5857
var cancelFuture = subscription.cancel();
59-
if (cancelFuture != null &&
60-
!identical(Zone._current._nullFuture, cancelFuture)) {
58+
if (cancelFuture != null && !identical(cancelFuture, Future._nullFuture)) {
6159
cancelFuture.whenComplete(() => future._complete(value));
6260
} else {
6361
future._complete(value);

sdk/lib/async/zone.dart

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,28 +1078,6 @@ abstract class _Zone implements Zone {
10781078
implZone, e, identical(error, e) ? stackTrace : s);
10791079
}
10801080
}
1081-
1082-
/// A reusable `null`-valued future per zone used by `dart:async`.
1083-
///
1084-
/// **DO NOT USE.**
1085-
///
1086-
/// This future is used in situations where a future is expected,
1087-
/// but no asynchronous computation actually happens,
1088-
/// like cancelling a stream from a controller with no `onCancel` callback.
1089-
/// *Some code depends on recognizing this future in order to react
1090-
/// synchronously.*
1091-
/// It does so to avoid changing event interleaving during the null safety
1092-
/// migration where, for example, the [StreamSubscription.cancel] method
1093-
/// stopped being able to return `null`.
1094-
/// The code that would be broken by such a timing change is fragile,
1095-
/// but we are not able to simply change it.
1096-
/// For better or worse, code depends on the precise timing
1097-
/// that our libraries have so far exhibited.
1098-
///
1099-
/// This future will be removed again if we can ever do so.
1100-
/// Do not use it for anything other than preserving timing
1101-
/// during the null safety migration.
1102-
_Future<Null> get _nullFuture;
11031081
}
11041082

11051083
class _CustomZone extends _Zone {
@@ -1127,9 +1105,6 @@ class _CustomZone extends _Zone {
11271105
/// The parent zone.
11281106
final _Zone parent;
11291107

1130-
/// Cached value for [_nullFuture];
1131-
_Future<Null>? _nullFutureCache;
1132-
11331108
/// The zone's scoped value declaration map.
11341109
///
11351110
/// This is always a [HashMap].
@@ -1221,18 +1196,6 @@ class _CustomZone extends _Zone {
12211196
/// parent's error-zone.
12221197
Zone get errorZone => _handleUncaughtError.zone;
12231198

1224-
_Future<Null> get _nullFuture {
1225-
_Future<Null>? result = _nullFutureCache;
1226-
if (result != null) return result;
1227-
// We only care about the zone of the null future
1228-
// because of the zone it schedules microtasks in.
1229-
var microtaskZone = _scheduleMicrotask.zone;
1230-
if (!identical(microtaskZone, this)) {
1231-
return _nullFutureCache = microtaskZone._nullFuture;
1232-
}
1233-
return _nullFutureCache = _Future<Null>.value(null);
1234-
}
1235-
12361199
void runGuarded(void f()) {
12371200
try {
12381201
run(f);
@@ -1546,8 +1509,6 @@ Zone _rootFork(Zone? self, ZoneDelegate? parent, Zone zone,
15461509
}
15471510

15481511
class _RootZone extends _Zone {
1549-
static final _nullFutureCache = _Future<Null>.zoneValue(null, _rootZone);
1550-
15511512
const _RootZone();
15521513

15531514
_ZoneFunction<RunHandler> get _run =>
@@ -1586,8 +1547,6 @@ class _RootZone extends _Zone {
15861547
// The parent zone.
15871548
_Zone? get parent => null;
15881549

1589-
_Future<Null> get _nullFuture => _nullFutureCache;
1590-
15911550
/// The zone's scoped value declaration map.
15921551
///
15931552
/// This is always a [HashMap].

sdk/lib/html/dart2js/html_dart2js.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37302,13 +37302,13 @@ class _EventStreamSubscription<T extends Event> extends StreamSubscription<T> {
3730237302
}
3730337303

3730437304
Future cancel() {
37305-
if (!_canceled) {
37306-
_unlisten();
37307-
// Clear out the target to indicate this is complete.
37308-
_target = null;
37309-
_onData = null;
37310-
}
37311-
return Future<void>.value(null);
37305+
if (_canceled) return nullFuture;
37306+
37307+
_unlisten();
37308+
// Clear out the target to indicate this is complete.
37309+
_target = null;
37310+
_onData = null;
37311+
return nullFuture;
3731237312
}
3731337313

3731437314
bool get _canceled => _target == null;

sdk/lib/internal/internal.dart

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

139-
final Expando<Future<Null>> _nullFutures = Expando<Future<Null>>();
139+
/// A reusable `null`-valued future used by `dart:async`.
140+
///
141+
/// **DO NOT USE.**
142+
///
143+
/// This future is used in situations where a future is expected,
144+
/// but no asynchronous computation actually happens,
145+
/// like cancelling a stream from a controller with no `onCancel` callback.
146+
/// *Some code depends on recognizing this future in order to react
147+
/// synchronously.*
148+
/// It does so to avoid changing event interleaving during the null safety
149+
/// migration where, for example, the [StreamSubscription.cancel] method
150+
/// stopped being able to return `null`.
151+
/// The code that would be broken by such a timing change is fragile,
152+
/// but we are not able to simply change it.
153+
/// For better or worse, code depends on the precise timing that our libraries
154+
/// have so far exhibited.
155+
///
156+
/// This future will be removed again if we can ever do so.
157+
/// Do not use it for anything other than preserving timing
158+
/// during the null safety migration.
159+
final Future<Null> nullFuture = Zone.root.run(() => Future<Null>.value(null));
140160

141161
/// A default hash function used by the platform in various places.
142162
///

tests/lib/async/null_future_zone_test.dart

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

1515
late Future nullFuture;
16+
late Future falseFuture;
1617

17-
bool nullFutureZoneUsed = false;
1818
runZoned(() {
1919
nullFuture = (new StreamController()..stream.listen(null).cancel()).done;
20+
falseFuture = it.moveNext();
2021
}, zoneSpecification: new ZoneSpecification(scheduleMicrotask:
2122
(Zone self, ZoneDelegate parent, Zone zone, void f()) {
22-
Expect.identical(zone, self);
23-
nullFutureZoneUsed = true;
24-
parent.scheduleMicrotask(zone, f);
23+
Expect.fail("Should not be called");
2524
}));
2625

2726
nullFuture.then((value) {
2827
Expect.isNull(value);
29-
Expect.isTrue(nullFutureZoneUsed);
3028
asyncEnd();
3129
});
3230

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

tests/lib_2/async/null_future_zone_test.dart

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

1717
Future nullFuture;
18+
Future falseFuture;
1819

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

2928
nullFuture.then((value) {
3029
Expect.isNull(value);
31-
Expect.isTrue(nullFutureZoneUsed);
3230
asyncEnd();
3331
});
3432

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-
4433
falseFuture.then((value) {
4534
Expect.isFalse(value);
4635
asyncEnd();

0 commit comments

Comments
 (0)