cdd8311b14
Tested: Test added for fix. Change-Id: Ifbefb01ef0caf9de80e44c44e5de4cc51bb129cc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/489080 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Lasse Nielsen <lrn@google.com>
127 lines
3.6 KiB
Dart
127 lines
3.6 KiB
Dart
// Copyright (c) 2026, 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.
|
|
|
|
// Tests that the microtask queue is not broken if a microtask throws.
|
|
|
|
import 'dart:isolate';
|
|
import "dart:async";
|
|
|
|
import 'package:expect/async_helper.dart';
|
|
import 'package:expect/expect.dart';
|
|
|
|
void main() async {
|
|
asyncStart();
|
|
// Runs code in a new isolate with `errorsAreFatal` set to `fatal`.
|
|
// The isolate code:
|
|
// - schedules two microtasks, each sending an event when they run,
|
|
// and then they throw.
|
|
// - if `timer` is true, also scheduled a timer which reports
|
|
// running, but doesn't throw. (To check see that a microtask
|
|
// doesn't get postponed to after the timer.)
|
|
// - runs the code that schedules the microtasks either
|
|
// synchronously in the isolate entry point (if `start` is `"sync"`),
|
|
// as a microtask (if it's `"microtask"`) or as a zero-duration
|
|
// timer (if it's `"timer"`).
|
|
|
|
for (var fatal in [true, false]) {
|
|
for (var timer in [false, true]) {
|
|
for (var start in ["sync", "microtask", "timer"]) {
|
|
// ID to keep cases apart.
|
|
var id = 'ID-${fatal ? 'F' : ''}-${timer ? 'T' : ''}-$start';
|
|
// Expectation.
|
|
var expect = [
|
|
// Always runs once microtask.
|
|
"M:$id#1", "E:$id#1",
|
|
if (!fatal) // If not fatal ...
|
|
...[
|
|
// Also runs second microtask,
|
|
"M:$id#2", "E:$id#2",
|
|
// and timer if requested, in that order.
|
|
if (timer) "T:$id",
|
|
],
|
|
"done",
|
|
];
|
|
Expect.listEquals(
|
|
expect,
|
|
await test(id, fatal: fatal, timer: timer, start: start),
|
|
"(fatal: $fatal, timer: $timer, start: $start)",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
asyncEnd();
|
|
}
|
|
|
|
/// Spawns isolate with given [fatal] running test with the remaining parameters.
|
|
///
|
|
/// Collects sent messages and uncaught errors, plus a final `"done"` when
|
|
/// the isolate closes, and returns the list.
|
|
Future<List<String>> test(
|
|
String id, {
|
|
required bool fatal,
|
|
required bool timer,
|
|
required String start,
|
|
}) async {
|
|
var log = <String>[];
|
|
var done = Completer<void>();
|
|
var port = RawReceivePort();
|
|
port.handler = (m) {
|
|
switch (m) {
|
|
case null:
|
|
log.add("done");
|
|
done.complete();
|
|
port.close();
|
|
case [var e, _]:
|
|
log.add("E:$e");
|
|
case var o:
|
|
log.add("$o");
|
|
}
|
|
};
|
|
await Isolate.spawn(
|
|
run,
|
|
(id, fatal, timer, start, port.sendPort),
|
|
errorsAreFatal: fatal,
|
|
onError: port.sendPort,
|
|
onExit: port.sendPort,
|
|
);
|
|
await done.future;
|
|
return log;
|
|
}
|
|
|
|
/// Remote isolate entry point.
|
|
///
|
|
/// Unpacks parameters and runs [runTasks] either synchronously
|
|
/// or as a timer event.
|
|
void run((String id, bool fatal, bool timer, String start, SendPort) message) {
|
|
var (id, fatal, timer, start, output) = message;
|
|
switch (start) {
|
|
case "sync":
|
|
runTasks(id, timer, output);
|
|
case "microtask":
|
|
Zone.current.scheduleMicrotask(() {
|
|
runTasks(id, timer, output);
|
|
});
|
|
case "timer":
|
|
Zone.current.createTimer(Duration.zero, () {
|
|
runTasks(id, timer, output);
|
|
});
|
|
}
|
|
}
|
|
|
|
void runTasks(String id, bool timer, SendPort output) {
|
|
Zone.current.scheduleMicrotask(() {
|
|
output.send("M:$id#1");
|
|
throw "$id#1";
|
|
});
|
|
Zone.current.scheduleMicrotask(() {
|
|
output.send("M:$id#2");
|
|
throw "$id#2";
|
|
});
|
|
if (timer) {
|
|
Zone.current.createTimer(Duration.zero, () {
|
|
output.send("T:$id");
|
|
});
|
|
}
|
|
}
|