From 91ce3473091a9654dc860609e240f042e88c7950 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Mon, 16 Jul 2018 16:19:15 +0000 Subject: [PATCH] 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 Commit-Queue: Leaf Petersen --- CHANGELOG.md | 7 ++++ sdk/lib/async/future.dart | 6 ++-- sdk/lib/async/future_impl.dart | 18 ++++++---- tests/lib_2/async/null_future_zone_test.dart | 36 ++++++++++++++++++++ 4 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 tests/lib_2/async/null_future_zone_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 50c344a24d5..841198fef84 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 3af2658be03..494f4712852 100644 --- a/sdk/lib/async/future.dart +++ b/sdk/lib/async/future.dart @@ -148,10 +148,12 @@ abstract class FutureOr { */ abstract class Future { /// A `Future` completed with `null`. - static final _Future _nullFuture = new _Future.value(null); + static final _Future _nullFuture = + new _Future.zoneValue(null, Zone.root); /// A `Future` completed with `false`. - static final _Future _falseFuture = new _Future.value(false); + static final _Future _falseFuture = + new _Future.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 19ec045c3db..b85e6ab8122 100644 --- a/sdk/lib/async/future_impl.dart +++ b/sdk/lib/async/future_impl.dart @@ -186,7 +186,7 @@ class _Future implements Future { * 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 @@ class _Future implements Future { var _resultOrListeners; // This constructor is used by async/await. - _Future(); + _Future() : _zone = Zone.current; - _Future.immediate(FutureOr result) { + _Future.immediate(FutureOr 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 00000000000..802c399dd07 --- /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(); + }); + }(); +}