Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion pkgs/coverage/lib/src/isolate_paused_listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,20 @@ class IsolatePausedListener {
_oldCollectionTasks.remove(collectionTask);
group.exit(isolateRef);
if (!_finishedListening) {
await _service.resume(isolateRef.id!);
try {
await _service.resume(isolateRef.id!);
} on SentinelException catch (_) {
} on RPCError catch (e) {
// Ignore expected VM service RPC errors when resuming an isolate
// that has already completed or exited:
// - 105: IsolateExited (isolate has exited)
// - 106: IsolateMustBePaused / CannotResume (isolate cannot be resumed)
const isolateExited = 105;
const isolateMustBePaused = 106;
if (e.code != isolateExited && e.code != isolateMustBePaused) {

@liamappelbe liamappelbe Jul 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's been a while since I've touched this code, but IIRC, I didn't want to ignore these exceptions because it usually means there's some sort of logical error in the (very complicated) isolate lifecycle management flow. Ignoring them turns them into harder to diagnose issues such as test processes just never terminating, or missing coverage data. I've used these exceptions several times to tighten up the lifecycle management.

Are you seeing this error in practice?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, we see this error in practice during rapid isolate teardowns, especially when running high-concurrency coverage suites across platforms (like the incoming Web/Chrome coverage support where isolate lifecycles turn over quickly).

Here is the exact race condition:
IsolatePausedListener listens to VM service events asynchronously (_onStart, _onPause, _onExit) and backfills existing isolates from the event stream. During fast teardowns, an isolate frequently completes its work and exits right in the window after _onIsolatePaused(isolateRef, ...) finishes awaiting, but just before _service.resume(isolateRef.id!) is called.

When _service.resume executes on an isolate that just exited milliseconds prior, the VM service throws RPCError code 105 (IsolateExited) or 106 (IsolateMustBePaused / CannotResume), or SentinelException (isolate collected). If uncaught, these benign teardown race conditions crash the listener loop or cause unhandled exceptions during test_with_coverage teardown.

To make sure we don't mask real logical errors in lifecycle management, we explicitly filter for those specific error codes:

if (e.code != isolateExited && e.code != isolateMustBePaused) {
  rethrow;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok. Can you move the try/catch directly to the failing _service.resume call then?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also, is this failure mode reproducible in a test?

rethrow;
}
}
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion pkgs/coverage/test/lcov_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,11 @@ void main() {
});
}

Future<Map<String, HitMap>> _getHitMap() async {
Future<Map<String, HitMap>>? _hitMapFuture;

Future<Map<String, HitMap>> _getHitMap() => _hitMapFuture ??= _createHitMap();

Future<Map<String, HitMap>> _createHitMap() async {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Speeds up this test by many minutes!!

expect(FileSystemEntity.isFileSync(_sampleAppPath), isTrue);

// start sample app.
Expand Down
Loading