@@ -28,101 +28,93 @@ class IsolatePausedListener {
28
28
29
29
final _isolateGroups = < String , IsolateGroupState > {};
30
30
31
- int _numNonMainIsolates = 0 ;
32
- final _allNonMainIsolatesExited = Completer <void >();
33
- bool _finished = false ;
31
+ int _numIsolates = 0 ;
32
+ bool _finishedListening = false ;
34
33
35
34
IsolateRef ? _mainIsolate;
36
- final _mainIsolatePaused = Completer <bool >();
35
+ bool _hasMainIsolate = false ;
36
+
37
+ // Completes when either:
38
+ // - there is a main isolate, and it is paused or exited
39
+ // - there is no main isolate, and all isolates have exited
40
+ final _mainIsolatePausedOrAllIsolatesExited = Completer <void >();
37
41
38
42
/// Starts listening and returns a future that completes when all isolates
39
43
/// have exited.
40
44
Future <void > waitUntilAllExited () async {
41
45
await listenToIsolateLifecycleEvents (_service, _onStart, _onPause, _onExit);
42
46
43
- await _allNonMainIsolatesExited.future;
44
-
45
- // Resume the main isolate.
46
- try {
47
- if (_mainIsolate != null ) {
48
- if (await _mainIsolatePaused.future) {
49
- await _runCallbackAndResume (
50
- _mainIsolate! , ! _getGroup (_mainIsolate! ).collected);
47
+ await _mainIsolatePausedOrAllIsolatesExited.future;
48
+ _finishedListening = true ;
49
+
50
+ // Collect all remaining uncollected groups.
51
+ final collectionTasks = < Future <void >> [];
52
+ for (final group in _isolateGroups.values) {
53
+ if (! group.collected) {
54
+ group.collected = true ;
55
+ final iso = group.paused.firstOrNull ?? group.running.firstOrNull;
56
+ if (iso != null ) {
57
+ collectionTasks.add (_onIsolatePaused (iso, true ));
51
58
}
52
59
}
53
- } finally {
54
- _finished = true ;
60
+ }
61
+ await Future .wait (collectionTasks);
62
+
63
+ // Resume the main isolate.
64
+ if (_mainIsolate != null ) {
65
+ await _service.resume (_mainIsolate! .id! );
55
66
}
56
67
}
57
68
58
69
IsolateGroupState _getGroup (IsolateRef isolateRef) =>
59
70
_isolateGroups[isolateRef.isolateGroupId! ] ?? = IsolateGroupState ();
60
71
61
72
void _onStart (IsolateRef isolateRef) {
62
- if (_finished ) return ;
73
+ if (_finishedListening ) return ;
63
74
final group = _getGroup (isolateRef);
64
- group.start (isolateRef.id! );
65
- if (_mainIsolate == null && _isMainIsolate (isolateRef)) {
75
+ group.start (isolateRef);
76
+ ++ _numIsolates;
77
+ if (! _hasMainIsolate && _isMainIsolate (isolateRef)) {
66
78
_mainIsolate = isolateRef;
67
- } else {
68
- ++ _numNonMainIsolates;
79
+ _hasMainIsolate = true ;
69
80
}
70
81
}
71
82
72
83
Future <void > _onPause (IsolateRef isolateRef) async {
73
- if (_finished ) return ;
84
+ if (_finishedListening ) return ;
74
85
final group = _getGroup (isolateRef);
75
- group.pause (isolateRef.id ! );
86
+ group.pause (isolateRef);
76
87
if (isolateRef.id! == _mainIsolate? .id) {
77
- _mainIsolatePaused.complete (true );
78
-
79
- // If the main isolate is the only isolate, then _allNonMainIsolatesExited
80
- // will never be completed. So check that case here.
81
- _checkCompleted ();
88
+ _mainIsolatePausedOrAllIsolatesExited.safeComplete ();
82
89
} else {
83
- await _runCallbackAndResume (isolateRef, group.noRunningIsolates);
84
- }
85
- }
86
-
87
- Future <void > _runCallbackAndResume (
88
- IsolateRef isolateRef, bool isLastIsolateInGroup) async {
89
- if (isLastIsolateInGroup) {
90
- _getGroup (isolateRef).collected = true ;
91
- }
92
- try {
93
- await _onIsolatePaused (isolateRef, isLastIsolateInGroup);
94
- } finally {
95
- await _service.resume (isolateRef.id! );
90
+ final isLastIsolateInGroup = group.noRunningIsolates;
91
+ if (isLastIsolateInGroup) {
92
+ _getGroup (isolateRef).collected = true ;
93
+ }
94
+ try {
95
+ await _onIsolatePaused (isolateRef, isLastIsolateInGroup);
96
+ } finally {
97
+ group.exit (isolateRef);
98
+ await _service.resume (isolateRef.id! );
99
+ }
96
100
}
97
101
}
98
102
99
103
void _onExit (IsolateRef isolateRef) {
100
- if (_finished ) return ;
104
+ if (_finishedListening ) return ;
101
105
final group = _getGroup (isolateRef);
102
- group.exit (isolateRef.id! );
106
+ group.exit (isolateRef);
107
+ -- _numIsolates;
103
108
if (group.noLiveIsolates && ! group.collected) {
104
109
_log ('ERROR: An isolate exited without pausing, causing '
105
110
'coverage data to be lost for group ${isolateRef .isolateGroupId !}.' );
106
111
}
107
112
if (isolateRef.id! == _mainIsolate? .id) {
108
- if (! _mainIsolatePaused.isCompleted) {
109
- // Main isolate exited without pausing.
110
- _mainIsolatePaused.complete (false );
111
- }
112
- } else {
113
- -- _numNonMainIsolates;
114
- _checkCompleted ();
115
- }
116
- }
117
-
118
- bool get _mainRunning =>
119
- _mainIsolate != null && ! _mainIsolatePaused.isCompleted;
120
-
121
- void _checkCompleted () {
122
- if (_numNonMainIsolates == 0 &&
123
- ! _mainRunning &&
124
- ! _allNonMainIsolatesExited.isCompleted) {
125
- _allNonMainIsolatesExited.complete ();
113
+ // Main isolate exited without pausing.
114
+ _mainIsolate = null ;
115
+ _mainIsolatePausedOrAllIsolatesExited.safeComplete ();
116
+ } else if (! _hasMainIsolate && _numIsolates == 0 ) {
117
+ _mainIsolatePausedOrAllIsolatesExited.safeComplete ();
126
118
}
127
119
}
128
120
@@ -139,6 +131,12 @@ class IsolatePausedListener {
139
131
}
140
132
}
141
133
134
+ extension on Completer {
135
+ void safeComplete () {
136
+ if (! isCompleted) complete ();
137
+ }
138
+ }
139
+
142
140
/// Listens to isolate start and pause events, and backfills events for isolates
143
141
/// that existed before listening started.
144
142
///
@@ -220,30 +218,30 @@ Future<void> listenToIsolateLifecycleEvents(
220
218
class IsolateGroupState {
221
219
// IDs of the isolates running in this group.
222
220
@visibleForTesting
223
- final running = < String > {};
221
+ final running = < IsolateRef > {};
224
222
225
223
// IDs of the isolates paused just before exiting in this group.
226
224
@visibleForTesting
227
- final paused = < String > {};
225
+ final paused = < IsolateRef > {};
228
226
229
227
bool collected = false ;
230
228
231
229
bool get noRunningIsolates => running.isEmpty;
232
230
bool get noLiveIsolates => running.isEmpty && paused.isEmpty;
233
231
234
- void start (String id ) {
235
- paused.remove (id );
236
- running.add (id );
232
+ void start (IsolateRef iso ) {
233
+ paused.remove (iso );
234
+ running.add (iso );
237
235
}
238
236
239
- void pause (String id ) {
240
- running.remove (id );
241
- paused.add (id );
237
+ void pause (IsolateRef iso ) {
238
+ running.remove (iso );
239
+ paused.add (iso );
242
240
}
243
241
244
- void exit (String id ) {
245
- running.remove (id );
246
- paused.remove (id );
242
+ void exit (IsolateRef iso ) {
243
+ running.remove (iso );
244
+ paused.remove (iso );
247
245
}
248
246
249
247
@override
0 commit comments