Skip to content

Conversation

@matanlurey
Copy link
Contributor

@matanlurey matanlurey commented Jan 19, 2024

Fixes #141823

Before this change, when a test would fail, the terminal would hang (by default for 30s) until killed by the test runner.

Basically, runZonedGuarded does document (though not clearly) that a returned future should not be awaited:

The zone will always be an error-zone ([Zone.errorZone](https://api.flutter.dev/flutter/dart-async/Zone/errorZone.html)), so returning a future created inside the zone, and waiting for it outside of the zone, will risk the future not being seen to complete.

For example, you can see other places in Dart and Flutter that we circumvent that problem:

I'm open to suggestions on how to test this :)

/cc @natebosch @jakemac53 @lrhn if you have any color commentary for us.

@github-actions github-actions bot added a: text input Entering text in a text field or keyboard related problems tool Affects the "flutter" command-line tool. See also t: labels. labels Jan 19, 2024
Copy link
Contributor

@christopherfujino christopherfujino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, pending unit test results of course

@Hixie
Copy link
Contributor

Hixie commented Jan 19, 2024

What role is runZoneGuarded playing here?

@matanlurey
Copy link
Contributor Author

What role is runZoneGuarded playing here?

AFAICT, runZonedGuarded is the only way to catch 100% of asynchronous errors that occur as a side-effect.

For example:

// Will catch errors _returned_ by body, but not as a side-effect of body.
try {
  await body();
} catch (e) {
  print(e);
}

@christopherfujino
Copy link
Contributor

What role is runZoneGuarded playing here?

AFAICT, runZonedGuarded is the only way to catch 100% of asynchronous errors that occur as a side-effect.

For example:

// Will catch errors _returned_ by body, but not as a side-effect of body.
try {
  await body();
} catch (e) {
  print(e);
}

right. To put it another way, as far as I understand, runZonedGuarded() will create a new zone, run a body callback within that zone, and then catch all uncaught errors thrown from that zone.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this redundant with the one on line 169?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, these are errors caught within the executing future versus outside 😓

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't match my understanding - with the rethrow here I expect that error will also be passed to the onError callback argument, and _printBufferedErrors(context); will be called twice in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@natebosch Thanks! Is your suggestion cutting out this catch block or something else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think you can remove this catch and keep the finally to complete the completer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you sir!

@jakemac53
Copy link
Contributor

So this is super confusing and easy to get wrong, but it does actually make sense :).

The entire purpose of this API is to swallow errors, it does not allow them to escape the Zone. This includes async errors.

So, if there is an error attached to a Future which escapes the Zone (such as being returned from the body callback), that future will never complete, because it isn't allowed to complete with an error (outside the zone), and has no success value to complete with either. Instead, onError is called and that is it.

@jakemac53
Copy link
Contributor

Fwiw I thought we added a different API which does essentially what your fix is, and has a return type of R instead of R?, with a required onError which has a return type of R. But I can't find it now. @natebosch am I making this up?

@Hixie
Copy link
Contributor

Hixie commented Jan 19, 2024

@jakemac53 fwiw our general discontent around this is largely that Zones are basically entirely undocumented, so it's all guesswork when we try to use them. For example, dart-lang/sdk#26455, dart-lang/sdk#54177, dart-lang/sdk#17667, dart-lang/sdk#44669, dart-lang/sdk#26454, ...

@jakemac53
Copy link
Contributor

Sure, I have felt that pain many times before :). Even where there are docs I find them incredibly difficult to understand. I think the number of people who would self-proclaim to understand zones fully is 2, possibly just 1. I am not one of them :).

But, for this particular issue, I hope the simplified explanation I provided above helps illuminate what is actually happening here?

@Hixie
Copy link
Contributor

Hixie commented Jan 19, 2024

I'm trying to encourage you to explain zones by submitting CLs to improve the API docs rather than just commenting on PRs. :-)

@matanlurey matanlurey force-pushed the flutter-tools-do-not-hang-on-failure branch from 9392ce7 to b3eabb6 Compare January 19, 2024 19:10
@matanlurey matanlurey requested a review from Hixie January 19, 2024 19:10
@matanlurey matanlurey added the autosubmit Merge PR when tree becomes green via auto submit App label Jan 19, 2024
@matanlurey matanlurey changed the title When flutter test <x> would have a test failure, do not hang Do not hang on test failures of tests within flutter_tools Jan 19, 2024
@natebosch
Copy link
Contributor

Fwiw I thought we added a different API which does essentially what your fix is, and has a return type of R instead of R?, with a required onError which has a return type of R. But I can't find it now. @natebosch am I making this up?

I think you might be conflating another API. Future.catchError expects the error handler to return value with the same type as the Future but does not express this in the signature, and this caused some difficulty during the null safety migration. When we introduced FutureExtensions.onError we kept the same expectation but it's now checked statically.

We don't have any API that expects a value returned from a Zone error handler. There is no guarantee that this error happens at a time when it would be useful to complete a Future returned from the runZonedGuarded call - for example the following has both an early zone error and a later real value returned from the callback.

import 'dart:async';

void main() async {
  final value = await runZonedGuarded(() {
    Future.error('Some error'); // Not awaited - unhandled async error.
    return Future.delayed(const Duration(milliseconds: 500), () {
      return 'Some value';
    });
  }, (e, st) {
    print('Unhandled async error: $e\n$st');
  });
  print('Returned value: $value');
}

@natebosch
Copy link
Contributor

@lrhn - WDYT about adding an API in package:async for this? The Result class might be a good place to stick it - that class is already dealing with treating errors as values. Maybe something like

  /// Runs [callback] in a zone and returns the result or the first
  /// unhandled error that occurs in the zone.
  ///
  /// If an unhandled asynchronous error occurs before the Future
  /// returned from [callback] has completed, the callback result
  /// will be ignored (if it ever completes), and the returned Future
  /// will complete with the same error.
  ///
  /// If there are multiple unhandled asynchronous errors, or if the
  /// Future returned by [callback] has an error after an unhandled
  /// asynchronous error, all but the first occurring error is ignored.
  ///
  /// This API may be useful to avoid Futures that never complete
  /// due to an error that cannot escape it's error zone.
  Future<T> Result.runZonedSmuggleErrors(Future<T> Function() callback);

Maybe we can also have a callback to handle those subsequent errors instead of strictly ignoring them.

@jakemac53
Copy link
Contributor

Yeah I think that is really the API most people are expecting here.

@auto-submit auto-submit bot merged commit 0b22694 into flutter:master Jan 22, 2024
@matanlurey matanlurey deleted the flutter-tools-do-not-hang-on-failure branch January 22, 2024 19:52
@XilaiZhang XilaiZhang added cp: beta cherry pick this pull request to beta release candidate branch and removed cp: beta cherry pick this pull request to beta release candidate branch labels Jan 22, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 23, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 23, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 23, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 23, 2024
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Jan 23, 2024
flutter/flutter@3ee8ff2...5b673c2

2024-01-23 [email protected] Roll Flutter Engine from fd0335a910b8 to b229878c57f5 (1 revision) (flutter/flutter#142051)
2024-01-23 [email protected] Remove unused clipBehavior from OverflowBar (flutter/flutter#141976)
2024-01-23 [email protected] Roll Packages from e4cbf23 to 841fe90 (7 revisions) (flutter/flutter#142047)
2024-01-23 [email protected] Roll Flutter Engine from d2855da628da to fd0335a910b8 (1 revision) (flutter/flutter#142046)
2024-01-23 [email protected] Add Share button to the SelectableRegion toolbar on Android (flutter/flutter#141447)
2024-01-23 [email protected] Relax the warning of unavailable tokens in `gen_defaults` when a default value is provided (flutter/flutter#140872)
2024-01-23 [email protected] Roll Flutter Engine from 37f68f6fc7fc to d2855da628da (1 revision) (flutter/flutter#142033)
2024-01-23 [email protected] Roll Flutter Engine from 9e582c9032e5 to 37f68f6fc7fc (1 revision) (flutter/flutter#142027)
2024-01-23 [email protected] Roll Flutter Engine from df6b15d35703 to 9e582c9032e5 (1 revision) (flutter/flutter#142026)
2024-01-23 [email protected] Roll Flutter Engine from d3dbd4225e08 to df6b15d35703 (6 revisions) (flutter/flutter#142023)
2024-01-23 [email protected] Add a comment about how to test flutter_goldens (flutter/flutter#141902)
2024-01-23 [email protected] Enable contextMenuBuilder in the absence of selectionControls (flutter/flutter#141810)
2024-01-23 [email protected] Roll Flutter Engine from b069d7f8f1fd to d3dbd4225e08 (3 revisions) (flutter/flutter#142005)
2024-01-23 98614782+auto-submit[bot]@users.noreply.github.com Reverts "hello_world app: migrate to Gradle Kotlin DSL" (flutter/flutter#142018)
2024-01-22 [email protected] Floating cursor docs (flutter/flutter#133002)
2024-01-22 [email protected] refactor: Rename filterPluginsByPlatform, cleanup Platform Strings (flutter/flutter#141780)
2024-01-22 [email protected] hello_world app: migrate to Gradle Kotlin DSL (flutter/flutter#141541)
2024-01-22 [email protected] Roll Flutter Engine from b2762f410840 to b069d7f8f1fd (4 revisions) (flutter/flutter#141993)
2024-01-22 [email protected] Remove duplicate code as suggested by natebosch. (flutter/flutter#141988)
2024-01-22 [email protected] Revert "Remove hack from PageView." (flutter/flutter#141977)
2024-01-22 [email protected] Roll Flutter Engine from d653559ae183 to b2762f410840 (3 revisions) (flutter/flutter#141978)
2024-01-22 [email protected] Do not hang on test failures of tests within `flutter_tools` (flutter/flutter#141821)
2024-01-22 [email protected] Remove unneeded expectation in test (flutter/flutter#141822)
2024-01-22 [email protected] Roll Flutter Engine from 1efe8ba6bc04 to d653559ae183 (3 revisions) (flutter/flutter#141972)
2024-01-22 [email protected] Add documentation which explains that `debugPrint` also logs in release mode (flutter/flutter#141595)
2024-01-22 [email protected] Fix `RangeSlider` throws a null-check error after `clearSemantics` is called (flutter/flutter#141965)
2024-01-22 [email protected] Roll Flutter Engine from c49989292fc1 to 1efe8ba6bc04 (1 revision) (flutter/flutter#141969)
2024-01-22 [email protected] [web] - Fix broken `TextField` in semantics mode when it's a sibling of `Navigator` (flutter/flutter#138446)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: text input Entering text in a text field or keyboard related problems autosubmit Merge PR when tree becomes green via auto submit App tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[flutter_tools] When running tool tests locally, test failures sometimes cause the runner to hang

6 participants