| // 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"); |
| }); |
| } |
| } |