Make realAsyncZone run microtasks and timers in the correct zone. (#162731)
Current implementation runs timers and microtask callbacks in the root zone. That assumes that the top-level `scheduleMicrotask` or `Timer` constructors have been used, which have so far wrapped the callback with `runCallbackGuarded` before calling the zone implementation. That means that doing `zone.scheduleMicrotask` directly would not ensure that the microtask was run in the correct zone. If a `run` handler throws, it wouldn't be caught. This change makes the `realAsyncZone` do whatever the root zone would do if its `ZoneDelegate` got called with the intended zone and arguments. That should be consistent with the current behavior, and be compatible with incoming bug-fixes to the platform `Zone` behavior. Prepares Flutter for landing https://dart-review.googlesource.com/c/sdk/+/406961 which is currently blocked (so this indirectly fixes https://github.com/dart-lang/sdk/issues/59913). There are no new tests, the goal is that all existing tests keep running, and that they keep doing so when the Dart CL lands. Currently that CL only breaks one test, the `dev/automated_tests/test_smoke_test/fail_test_on_exception_after_test.dart` test which threw the error-after-test in the root zone instead of the test zone. This change fixes that.
This commit is contained in:
committed by
GitHub
parent
8297e44993
commit
9e829cbd92
@@ -1361,7 +1361,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
||||
final Zone realAsyncZone = Zone.current.fork(
|
||||
specification: ZoneSpecification(
|
||||
scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone zone, void Function() f) {
|
||||
Zone.root.scheduleMicrotask(f);
|
||||
_rootDelegate.scheduleMicrotask(zone, f);
|
||||
},
|
||||
createTimer: (
|
||||
Zone self,
|
||||
@@ -1370,7 +1370,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
||||
Duration duration,
|
||||
void Function() f,
|
||||
) {
|
||||
return Zone.root.createTimer(duration, f);
|
||||
return _rootDelegate.createTimer(zone, duration, f);
|
||||
},
|
||||
createPeriodicTimer: (
|
||||
Zone self,
|
||||
@@ -1379,7 +1379,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
||||
Duration period,
|
||||
void Function(Timer timer) f,
|
||||
) {
|
||||
return Zone.root.createPeriodicTimer(period, f);
|
||||
return _rootDelegate.createPeriodicTimer(zone, period, f);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -1442,6 +1442,26 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
||||
_currentFakeAsync!.flushMicrotasks();
|
||||
}
|
||||
|
||||
/// The [ZoneDelegate] for [Zone.root].
|
||||
///
|
||||
/// Used to schedule (real) microtasks and timers in the root zone,
|
||||
/// to be run in the correct zone.
|
||||
static final ZoneDelegate _rootDelegate = _captureRootZoneDelegate();
|
||||
|
||||
/// Hack to extract the [ZoneDelegate] for [Zone.root].
|
||||
static ZoneDelegate _captureRootZoneDelegate() {
|
||||
final Zone captureZone = Zone.root.fork(
|
||||
specification: ZoneSpecification(
|
||||
run: <R>(Zone self, ZoneDelegate parent, Zone zone, R Function() f) {
|
||||
return parent as R;
|
||||
},
|
||||
),
|
||||
);
|
||||
// The `_captureRootZoneDelegate` argument just happens to be a constant
|
||||
// function with the necessary type. It's not called recursively.
|
||||
return captureZone.run<ZoneDelegate>(_captureRootZoneDelegate);
|
||||
}
|
||||
|
||||
@override
|
||||
void scheduleAttachRootWidget(Widget rootWidget) {
|
||||
// We override the default version of this so that the application-startup widget tree
|
||||
|
||||
@@ -406,6 +406,82 @@ void main() {
|
||||
expect(result, isNull);
|
||||
expect(tester.takeException(), isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('runs in original zone', (WidgetTester tester) async {
|
||||
final Zone testZone = Zone.current;
|
||||
Zone? runAsyncZone;
|
||||
Zone? timerZone;
|
||||
Zone? periodicTimerZone;
|
||||
Zone? microtaskZone;
|
||||
|
||||
Zone? innerZone;
|
||||
Zone? innerTimerZone;
|
||||
Zone? innerPeriodicTimerZone;
|
||||
Zone? innerMicrotaskZone;
|
||||
|
||||
await tester.binding.runAsync<void>(() async {
|
||||
final Zone currentZone = Zone.current;
|
||||
runAsyncZone = currentZone;
|
||||
|
||||
// Complete a future when all callbacks have completed.
|
||||
int pendingCallbacks = 6;
|
||||
final Completer<void> callbacksDone = Completer<void>();
|
||||
void onCallback() {
|
||||
if (--pendingCallbacks == 0) {
|
||||
testZone.run(() {
|
||||
callbacksDone.complete(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// On the runAsync zone itself.
|
||||
currentZone.createTimer(Duration.zero, () {
|
||||
timerZone = Zone.current;
|
||||
onCallback();
|
||||
});
|
||||
currentZone.createPeriodicTimer(Duration.zero, (Timer timer) {
|
||||
timer.cancel();
|
||||
periodicTimerZone = Zone.current;
|
||||
onCallback();
|
||||
});
|
||||
currentZone.scheduleMicrotask(() {
|
||||
microtaskZone = Zone.current;
|
||||
onCallback();
|
||||
});
|
||||
|
||||
// On a nested user-created zone.
|
||||
final Zone inner = runZoned(() => Zone.current);
|
||||
innerZone = inner;
|
||||
inner.createTimer(Duration.zero, () {
|
||||
innerTimerZone = Zone.current;
|
||||
onCallback();
|
||||
});
|
||||
inner.createPeriodicTimer(Duration.zero, (Timer timer) {
|
||||
timer.cancel();
|
||||
innerPeriodicTimerZone = Zone.current;
|
||||
onCallback();
|
||||
});
|
||||
inner.scheduleMicrotask(() {
|
||||
innerMicrotaskZone = Zone.current;
|
||||
onCallback();
|
||||
});
|
||||
|
||||
await callbacksDone.future;
|
||||
});
|
||||
expect(runAsyncZone, isNotNull);
|
||||
expect(timerZone, same(runAsyncZone));
|
||||
expect(periodicTimerZone, same(runAsyncZone));
|
||||
expect(microtaskZone, same(runAsyncZone));
|
||||
|
||||
expect(innerZone, isNotNull);
|
||||
expect(innerTimerZone, same(innerZone));
|
||||
expect(innerPeriodicTimerZone, same(innerZone));
|
||||
expect(innerMicrotaskZone, same(innerZone));
|
||||
|
||||
expect(runAsyncZone, isNot(same(testZone)));
|
||||
expect(runAsyncZone, isNot(same(innerZone)));
|
||||
expect(innerZone, isNot(same(testZone)));
|
||||
});
|
||||
});
|
||||
|
||||
group('showKeyboard', () {
|
||||
|
||||
Reference in New Issue
Block a user