Reapply "Create _nullFuture and _falseFuture in the root zone."
Originally landed by https://dart-review.googlesource.com/c/sdk/+/49509
Reverted because an internal test is fragile and changes behavior when the bug is fixed.
Change-Id: I8516082e5741547c46aa521a91826846dc101303
Reviewed-on: https://dart-review.googlesource.com/63743
Reviewed-by: Leaf Petersen <leafp@google.com>
Commit-Queue: Leaf Petersen <leafp@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50c344a..841198f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,6 +34,13 @@
* Re-enable `Iterable.whereType`. The method was disabled because code
was still being compiled in Dart 1 mode, and the function was
error-prone when used in that code.
+* `dart:async`
+ * Changed an internal lazily-allocated reusable "null future" to always belong
+ to the root zone. This avoids race conditions where the first access to the
+ future determined which zone it would belong to. The zone is only used
+ for *scheduling* the callback of listeners, the listeners themselves will
+ run in the correct zone in any case.
+ Issue [#32556](http://dartbug.com/32556).
## 2.0.0-dev.67.0
diff --git a/sdk/lib/async/future.dart b/sdk/lib/async/future.dart
index 3af2658..494f471 100644
--- a/sdk/lib/async/future.dart
+++ b/sdk/lib/async/future.dart
@@ -148,10 +148,12 @@
*/
abstract class Future<T> {
/// A `Future<Null>` completed with `null`.
- static final _Future<Null> _nullFuture = new _Future<Null>.value(null);
+ static final _Future<Null> _nullFuture =
+ new _Future<Null>.zoneValue(null, Zone.root);
/// A `Future<bool>` completed with `false`.
- static final _Future<bool> _falseFuture = new _Future<bool>.value(false);
+ static final _Future<bool> _falseFuture =
+ new _Future<bool>.zoneValue(false, Zone.root);
/**
* Creates a future containing the result of calling [computation]
diff --git a/sdk/lib/async/future_impl.dart b/sdk/lib/async/future_impl.dart
index 19ec045..b85e6ab 100644
--- a/sdk/lib/async/future_impl.dart
+++ b/sdk/lib/async/future_impl.dart
@@ -186,7 +186,7 @@
* Until the future is completed, the field may hold the zone that
* listener callbacks used to create this future should be run in.
*/
- final Zone _zone = Zone.current;
+ final Zone _zone;
/**
* Either the result, a list of listeners or another future.
@@ -206,20 +206,24 @@
var _resultOrListeners;
// This constructor is used by async/await.
- _Future();
+ _Future() : _zone = Zone.current;
- _Future.immediate(FutureOr<T> result) {
+ _Future.immediate(FutureOr<T> result) : _zone = Zone.current {
_asyncComplete(result);
}
- _Future.immediateError(var error, [StackTrace stackTrace]) {
+ /** Creates a future with the value and the specified zone. */
+ _Future.zoneValue(T value, this._zone) {
+ _setValue(value);
+ }
+
+ _Future.immediateError(var error, [StackTrace stackTrace])
+ : _zone = Zone.current {
_asyncCompleteError(error, stackTrace);
}
/** Creates a future that is already completed with the value. */
- _Future.value(T value) {
- _setValue(value);
- }
+ _Future.value(T value) : this.zoneValue(value, Zone.current);
bool get _mayComplete => _state == _stateIncomplete;
bool get _isPendingComplete => _state == _statePendingComplete;
diff --git a/tests/lib_2/async/null_future_zone_test.dart b/tests/lib_2/async/null_future_zone_test.dart
new file mode 100644
index 0000000..802c399
--- /dev/null
+++ b/tests/lib_2/async/null_future_zone_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2013, 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 "package:expect/expect.dart";
+import "package:async_helper/async_helper.dart";
+import 'dart:async';
+
+main() {
+ asyncStart(2);
+ () async {
+ var it = new StreamIterator(new Stream.fromIterable([]));
+ Expect.isFalse(await it.moveNext());
+
+ Future nullFuture;
+ Future falseFuture;
+
+ runZoned(() {
+ nullFuture = (new StreamController()..stream.listen(null).cancel()).done;
+ falseFuture = it.moveNext();
+ }, zoneSpecification: new ZoneSpecification(scheduleMicrotask:
+ (Zone self, ZoneDelegate parent, Zone zone, void f()) {
+ Expect.fail("Should not be called");
+ }));
+
+ nullFuture.then((value) {
+ Expect.isNull(value);
+ asyncEnd();
+ });
+
+ falseFuture.then((value) {
+ Expect.isFalse(value);
+ asyncEnd();
+ });
+ }();
+}