Skip to content

Commit 6f1b279

Browse files
lrhnCommit Queue
authored andcommitted
Fix issue 56806, incorrect ignore on futures.
Makes `whenComplete` create a new future with either the result of the original, or the error of a future returned by the callback. Until now it was returning the original future, causing it to be chained after it was completed. That caused issue with `ignore()`. (Which suggests that there is still something iffy about ignore and chaining.) Combines `chainCoreFuture{Sync,Async}` into one function with boolean flag. Most of the code is the same, and they had accidentally drifted apart. Fixes #56806. CoreLibraryReviewExempt: Should be simple refactoring. Tested: Regression test added Bug: http://dartbug.com/56806 Change-Id: I52751c078a4e26a10e9da0a64460e2c5c7f34ae5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/387360 Commit-Queue: Lasse Nielsen <[email protected]> Reviewed-by: Nate Bosch <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent caba043 commit 6f1b279

File tree

4 files changed

+111
-53
lines changed

4 files changed

+111
-53
lines changed

pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter81068.dart.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class B<T extends core::Object? = dynamic> extends self::A<core::String> impleme
2424

2525
[@vm.inferred-return-type.metadata=!]
2626
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4]
27-
no-such-method-forwarder method then<R extends core::Object? = dynamic>([@vm.inferred-arg-type.metadata=dart.core::_Closure] (self::B::T%) → FutureOr<self::B::then::R%>onValue, {[@vm.inferred-arg-type.metadata=dart.core::_Closure?] core::Function? onError = #C1}) → asy::Future<self::B::then::R%>
27+
no-such-method-forwarder method then<R extends core::Object? = dynamic>([@vm.inferred-arg-type.metadata=dart.core::_Closure] (self::B::T%) → FutureOr<self::B::then::R%>onValue, {[@vm.inferred-arg-type.metadata=dart.core::_Closure] core::Function? onError = #C1}) → asy::Future<self::B::then::R%>
2828
return _in::unsafeCast<asy::Future<self::B::then::R%>>([@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C2, 0, [@vm.inferred-type.metadata=dart.core::_ImmutableList] core::List::unmodifiable<core::Type>([@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::Type>] core::_GrowableList::_literal1<core::Type>(self::B::then::R%)), [@vm.inferred-type.metadata=dart.core::_ImmutableList] core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(onValue)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol, dynamic>] core::Map::unmodifiable<core::Symbol, dynamic>(<core::Symbol, dynamic>{#C3: onError}))){(core::Invocation) → dynamic});
2929

3030
[@vm.inferred-return-type.metadata=!]

sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,7 @@ _async<T>(Function() initGenerator) {
107107
// instead treat it as a completed value.
108108
if (value is Future) {
109109
if (value is _Future) {
110-
if (isRunningAsEvent) {
111-
_Future._chainCoreFutureSync(value, asyncFuture);
112-
} else {
113-
_Future._chainCoreFutureAsync(value, asyncFuture);
114-
}
110+
_Future._chainCoreFuture(value, asyncFuture, isRunningAsEvent);
115111
} else {
116112
asyncFuture._chainForeignFuture(value);
117113
}
@@ -121,7 +117,7 @@ _async<T>(Function() initGenerator) {
121117
asyncFuture._asyncComplete(JS('', '#', value));
122118
}
123119
} else {
124-
_Future._chainCoreFutureSync(onAwait(value), asyncFuture);
120+
_Future._chainCoreFuture(onAwait(value), asyncFuture, true);
125121
}
126122
} catch (e, s) {
127123
if (isRunningAsEvent) {

sdk/lib/async/future_impl.dart

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ class _Future<T> implements Future<T> {
327327
// This constructor is used by async/await.
328328
_Future() : _zone = Zone._current;
329329

330+
// Empty `_Future` in a given zone.
331+
_Future.zone(this._zone);
332+
330333
// Constructor used by [Future.value].
331334
_Future.immediate(FutureOr<T> result) : _zone = Zone._current {
332335
_asyncComplete(result);
@@ -397,11 +400,18 @@ class _Future<T> implements Future<T> {
397400
}
398401

399402
void _ignore() {
400-
_Future<Object?> source = this;
401-
while (source._isChained) {
402-
source = source._chainSource;
403+
_state |= _stateIgnoreError;
404+
if (_isChained) {
405+
// Mark chain source. If it has no listeners at all, then neither
406+
// does this future. (But this may suppress errors on other futures
407+
// with the same chain source and no listeners, which haven't been
408+
// ignored.)
409+
_Future<Object?> source = this;
410+
do {
411+
source = source._chainSource;
412+
} while (source._isChained);
413+
source._state |= _stateIgnoreError;
403414
}
404-
source._state |= _stateIgnoreError;
405415
}
406416

407417
Future<T> catchError(Function onError, {bool test(Object error)?}) {
@@ -573,7 +583,7 @@ class _Future<T> implements Future<T> {
573583
/// Completes this future with the result of [source].
574584
///
575585
/// The [source] future should not be a [_Future], use
576-
/// [_chainCoreFutureSync] for those.
586+
/// [_chainCoreFuture] for those.
577587
///
578588
/// Since [source] is an unknown [Future], it's interacted with
579589
/// through [Future.then], which is required to be asynchronous.
@@ -613,37 +623,7 @@ class _Future<T> implements Future<T> {
613623
/// propagated to the target future's listeners.
614624
/// If the source future is not completed, the target future is made
615625
/// to listen for its completion.
616-
static void _chainCoreFutureSync(_Future source, _Future target) {
617-
assert(target._mayAddListener); // Not completed, not already chained.
618-
while (source._isChained) {
619-
source = source._chainSource;
620-
}
621-
if (identical(source, target)) {
622-
target._asyncCompleteError(
623-
ArgumentError.value(
624-
source, null, "Cannot complete a future with itself"),
625-
StackTrace.current);
626-
return;
627-
}
628-
source._state |= target._state & _stateIgnoreError;
629-
if (source._isComplete) {
630-
_FutureListener? listeners = target._removeListeners();
631-
target._cloneResult(source);
632-
_propagateToListeners(target, listeners);
633-
} else {
634-
_FutureListener? listeners = target._resultOrListeners;
635-
target._setChained(source);
636-
source._prependListeners(listeners);
637-
}
638-
}
639-
640-
/// Asynchronously completes a [target] future with a [source] future.
641-
///
642-
/// If the [source] future is already completed, its result is
643-
/// asynchronously propagated to the [target] future's listeners.
644-
/// If the [source] future is not completed, the [target] future is made
645-
/// to listen for its completion.
646-
static void _chainCoreFutureAsync(_Future source, _Future target) {
626+
static void _chainCoreFuture(_Future source, _Future target, bool sync) {
647627
assert(target._mayAddListener); // Not completed, not already chained.
648628
while (source._isChained) {
649629
source = source._chainSource;
@@ -655,26 +635,32 @@ class _Future<T> implements Future<T> {
655635
StackTrace.current);
656636
return;
657637
}
638+
var ignoreError = target._state & _stateIgnoreError;
639+
source._state |= ignoreError;
658640
if (!source._isComplete) {
659641
// Chain immediately if the source is not complete.
660-
// This won't call any listeners.
642+
// This won't call any listeners, whether completing sync or async.
661643
_FutureListener? listeners = target._resultOrListeners;
662644
target._setChained(source);
663645
source._prependListeners(listeners);
664646
return;
665647
}
666648

667-
// Complete a value synchronously, if no-one is listening.
668-
// This won't call any listeners.
669-
if (!source._hasError && target._resultOrListeners == null) {
649+
if (sync ||
650+
(target._resultOrListeners == null &&
651+
(!source._hasError || ignoreError != 0))) {
652+
// Complete synchronously when allowed.
653+
// If no-one is listening this won't call any listeners synchronously.
654+
// Do delay un-ignored errors to give time to add listeners.
655+
_FutureListener? listeners = target._removeListeners();
670656
target._cloneResult(source);
657+
_propagateToListeners(target, listeners);
671658
return;
672659
}
673-
674660
// Otherwise delay the chaining to avoid any synchronous callbacks.
675661
target._setPendingComplete();
676662
target._zone.scheduleMicrotask(() {
677-
_chainCoreFutureSync(source, target);
663+
_chainCoreFuture(source, target, _allowCompleteSync);
678664
});
679665
}
680666

@@ -688,7 +674,7 @@ class _Future<T> implements Future<T> {
688674
assert(!_isComplete);
689675
if (value is Future<T>) {
690676
if (value is _Future<T>) {
691-
_chainCoreFutureSync(value, this);
677+
_chainCoreFuture(value, this, _allowCompleteSync);
692678
} else {
693679
_chainForeignFuture(value);
694680
}
@@ -707,6 +693,16 @@ class _Future<T> implements Future<T> {
707693
_propagateToListeners(this, listeners);
708694
}
709695

696+
void _completeWithResultOf(_Future<Object?> source) {
697+
assert(source._isComplete);
698+
if (source._hasError && !_zone.inSameErrorZone(source._zone)) {
699+
return;
700+
}
701+
_FutureListener? listeners = _removeListeners();
702+
_cloneResult(source);
703+
_propagateToListeners(this, listeners);
704+
}
705+
710706
void _completeError(Object error, StackTrace stackTrace) {
711707
assert(!_isComplete);
712708

@@ -786,7 +782,7 @@ class _Future<T> implements Future<T> {
786782
assert(_mayComplete);
787783
if (value is _Future<T>) {
788784
// Chain ensuring that we don't complete synchronously.
789-
_chainCoreFutureAsync(value, this);
785+
_chainCoreFuture(value, this, _requireCompleteAsync);
790786
return;
791787
}
792788
// Just listen on the foreign future. This guarantees an async delay.
@@ -892,7 +888,13 @@ class _Future<T> implements Future<T> {
892888
// before knowing if it's an error or we should use the result
893889
// of source.
894890
var originalSource = source;
895-
listenerValueOrError = completeResult.then((_) => originalSource);
891+
var joinedResult = source._newFutureWithSameType();
892+
completeResult.then<void>((_) {
893+
joinedResult._completeWithResultOf(originalSource);
894+
}, onError: (Object e, StackTrace s) {
895+
joinedResult._completeError(e, s);
896+
});
897+
listenerValueOrError = joinedResult;
896898
listenerHasError = false;
897899
}
898900
}
@@ -954,7 +956,7 @@ class _Future<T> implements Future<T> {
954956
source = chainSource;
955957
continue;
956958
} else {
957-
_chainCoreFutureSync(chainSource, result);
959+
_chainCoreFuture(chainSource, result, _allowCompleteSync);
958960
}
959961
} else {
960962
result._chainForeignFuture(chainSource);
@@ -976,6 +978,12 @@ class _Future<T> implements Future<T> {
976978
}
977979
}
978980

981+
/// New uncompleted future with same type.
982+
///
983+
/// In same zone or other [zone] if specified.
984+
_Future<T> _newFutureWithSameType([_Zone? zone]) =>
985+
_Future<T>.zone(zone ?? _zone);
986+
979987
@pragma("vm:entry-point")
980988
Future<T> timeout(Duration timeLimit, {FutureOr<T> onTimeout()?}) {
981989
if (_isComplete) return new _Future.immediate(this);
@@ -1041,3 +1049,7 @@ Function _registerErrorHandler(Function errorHandler, Zone zone) {
10411049
"Error handler must accept one Object or one Object and a StackTrace"
10421050
" as arguments, and return a value of the returned future's type");
10431051
}
1052+
1053+
// Names for positional arguments to _chainCoreFuture.
1054+
const _allowCompleteSync = true;
1055+
const _requireCompleteAsync = false;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import "dart:async";
6+
7+
import 'package:async_helper/async_helper.dart';
8+
import "package:expect/expect.dart";
9+
10+
// Regression test for http://dartbug.com/56806
11+
//
12+
// Checks that `.ignore()` gets propagated correctly when chaining futures.
13+
14+
void main() async {
15+
asyncStart();
16+
17+
await testNoThrow();
18+
19+
asyncStart();
20+
await runZoned(testDoThrow, zoneSpecification: ZoneSpecification(
21+
handleUncaughtError: (s, p, z, Object error, StackTrace stack) {
22+
asyncEnd();
23+
}));
24+
25+
asyncEnd();
26+
}
27+
28+
Future<void> testNoThrow() async {
29+
var completer = Completer<void>();
30+
final future = Future<void>.delayed(
31+
Duration.zero, () => throw StateError("Should be ignored"));
32+
var future2 = future.whenComplete(() async {
33+
await Future.delayed(Duration.zero);
34+
completer.complete();
35+
});
36+
future2.ignore(); // Has no listeners.
37+
await completer.future;
38+
}
39+
40+
Future<void> testDoThrow() async {
41+
var completer = Completer<void>();
42+
final future = Future<void>.delayed(
43+
Duration.zero, () => throw StateError("Should not be ignored"));
44+
future.ignore(); // Ignores error only on `future`.
45+
future.whenComplete(() async {
46+
await Future.delayed(Duration.zero);
47+
completer.complete();
48+
}); // Should rethrow the error, as uncaught.
49+
await completer.future;
50+
}

0 commit comments

Comments
 (0)