@@ -24,10 +24,62 @@ base mixin DartToolingDaemonSupport on ToolsSupport {
24
24
/// ready to be invoked.
25
25
bool _getDebugSessionsReady = false ;
26
26
27
+ /// A Map of [VmService] objects by their associated VM Service URI
28
+ /// (represented as a String).
29
+ ///
30
+ /// [VmService] objects are automatically removed from the Map when the
31
+ /// [VmService] shuts down.
32
+ @visibleForTesting
33
+ final activeVmServices = < String , VmService > {};
34
+
35
+ /// Whether to await the disposal of all [VmService] objects in
36
+ /// [activeVmServices] upon server shutdown or loss of DTD connection.
37
+ ///
38
+ /// Defaults to false but can be flipped to true for testing purposes.
39
+ @visibleForTesting
40
+ static bool debugAwaitVmServiceDisposal = false ;
41
+
27
42
/// Called when the DTD connection is lost, resets all associated state.
28
- void _resetDtd () {
43
+ Future < void > _resetDtd () async {
29
44
_dtd = null ;
30
45
_getDebugSessionsReady = false ;
46
+
47
+ final future = Future .wait (
48
+ activeVmServices.values.map ((vmService) => vmService.dispose ()),
49
+ );
50
+ debugAwaitVmServiceDisposal ? await future : unawaited (future);
51
+
52
+ activeVmServices.clear ();
53
+ }
54
+
55
+ @visibleForTesting
56
+ Future <void > updateActiveVmServices () async {
57
+ final dtd = _dtd;
58
+ if (dtd == null ) return ;
59
+
60
+ // TODO: in the future, get the active VM service URIs from DTD directly
61
+ // instead of from the `Editor.getDebugSessions` service method.
62
+ if (! _getDebugSessionsReady) {
63
+ // Give it a chance to get ready.
64
+ await Future <void >.delayed (const Duration (seconds: 1 ));
65
+ if (! _getDebugSessionsReady) return ;
66
+ }
67
+
68
+ final response = await dtd.getDebugSessions ();
69
+ final debugSessions = response.debugSessions;
70
+ for (final debugSession in debugSessions) {
71
+ if (activeVmServices.containsKey (debugSession.vmServiceUri)) {
72
+ continue ;
73
+ }
74
+ final vmService = await vmServiceConnectUri (debugSession.vmServiceUri);
75
+ activeVmServices[debugSession.vmServiceUri] = vmService;
76
+ unawaited (
77
+ vmService.onDone.then ((_) {
78
+ activeVmServices.remove (debugSession.vmServiceUri);
79
+ vmService.dispose ();
80
+ }),
81
+ );
82
+ }
31
83
}
32
84
33
85
@override
@@ -46,6 +98,12 @@ base mixin DartToolingDaemonSupport on ToolsSupport {
46
98
return super .initialize (request);
47
99
}
48
100
101
+ @override
102
+ Future <void > shutdown () async {
103
+ await _resetDtd ();
104
+ await super .shutdown ();
105
+ }
106
+
49
107
/// Connects to the Dart Tooling Daemon.
50
108
FutureOr <CallToolResult > _connect (CallToolRequest request) async {
51
109
if (_dtd != null ) {
@@ -65,7 +123,7 @@ base mixin DartToolingDaemonSupport on ToolsSupport {
65
123
_dtd = await DartToolingDaemon .connect (
66
124
Uri .parse (request.arguments! ['uri' ] as String ),
67
125
);
68
- unawaited (_dtd! .done.then ((_) => _resetDtd ()));
126
+ unawaited (_dtd! .done.then ((_) async => await _resetDtd ()));
69
127
70
128
_listenForServices ();
71
129
return CallToolResult (
@@ -151,62 +209,66 @@ base mixin DartToolingDaemonSupport on ToolsSupport {
151
209
Future <CallToolResult > hotReload (CallToolRequest request) async {
152
210
return _callOnVmService (
153
211
callback: (vmService) async {
154
- final vm = await vmService.getVM ();
155
-
156
- final hotReloadMethodNameCompleter = Completer <String ?>();
157
- vmService.onEvent (EventStreams .kService).listen ((Event e) {
158
- if (e.kind == EventKind .kServiceRegistered) {
159
- final serviceName = e.service! ;
160
- if (serviceName == 'reloadSources' ) {
161
- // This may look something like 's0.reloadSources'.
162
- hotReloadMethodNameCompleter.complete (e.method);
163
- }
164
- }
165
- });
166
- await vmService.streamListen (EventStreams .kService);
167
- final hotReloadMethodName = await hotReloadMethodNameCompleter.future
168
- .timeout (
169
- const Duration (milliseconds: 1000 ),
170
- onTimeout: () async {
171
- return null ;
172
- },
212
+ StreamSubscription <Event >? serviceStreamSubscription;
213
+ try {
214
+ final hotReloadMethodNameCompleter = Completer <String ?>();
215
+ serviceStreamSubscription = vmService
216
+ .onEvent (EventStreams .kService)
217
+ .listen ((Event e) {
218
+ if (e.kind == EventKind .kServiceRegistered) {
219
+ final serviceName = e.service! ;
220
+ if (serviceName == 'reloadSources' ) {
221
+ // This may look something like 's0.reloadSources'.
222
+ hotReloadMethodNameCompleter.complete (e.method);
223
+ }
224
+ }
225
+ });
226
+
227
+ await vmService.streamListen (EventStreams .kService);
228
+
229
+ final hotReloadMethodName = await hotReloadMethodNameCompleter.future
230
+ .timeout (
231
+ const Duration (milliseconds: 1000 ),
232
+ onTimeout: () async {
233
+ return null ;
234
+ },
235
+ );
236
+ if (hotReloadMethodName == null ) {
237
+ return CallToolResult (
238
+ isError: true ,
239
+ content: [
240
+ TextContent (
241
+ text:
242
+ 'The hot reload service has not been registered yet. '
243
+ 'Please wait a few seconds and try again.' ,
244
+ ),
245
+ ],
173
246
);
174
- await vmService.streamCancel (EventStreams .kService);
175
-
176
- if (hotReloadMethodName == null ) {
177
- return CallToolResult (
178
- isError: true ,
179
- content: [
180
- TextContent (
181
- text:
182
- 'The hot reload service has not been registered yet, '
183
- 'please wait a few seconds and try again.' ,
184
- ),
185
- ],
186
- );
187
- }
247
+ }
188
248
189
- final result = await vmService.callMethod (
190
- hotReloadMethodName,
191
- isolateId: vm.isolates! .first.id,
192
- );
193
- final resultType = result.json? ['type' ];
194
- if (resultType == 'Success' ||
195
- (resultType == 'ReloadReport' && result.json? ['success' ] == true )) {
196
- return CallToolResult (
197
- content: [TextContent (text: 'Hot reload succeeded.' )],
198
- );
199
- } else {
200
- return CallToolResult (
201
- isError: true ,
202
- content: [
203
- TextContent (
204
- text:
205
- 'Hot reload failed:\n '
206
- '${result .json }' ,
207
- ),
208
- ],
249
+ final vm = await vmService.getVM ();
250
+ final result = await vmService.callMethod (
251
+ hotReloadMethodName,
252
+ isolateId: vm.isolates! .first.id,
209
253
);
254
+ final resultType = result.json? ['type' ];
255
+ if (resultType == 'Success' ||
256
+ (resultType == 'ReloadReport' &&
257
+ result.json? ['success' ] == true )) {
258
+ return CallToolResult (
259
+ content: [TextContent (text: 'Hot reload succeeded.' )],
260
+ );
261
+ } else {
262
+ return CallToolResult (
263
+ isError: true ,
264
+ content: [
265
+ TextContent (text: 'Hot reload failed:\n ${result .json }' ),
266
+ ],
267
+ );
268
+ }
269
+ } finally {
270
+ await serviceStreamSubscription? .cancel ();
271
+ await vmService.streamCancel (EventStreams .kService);
210
272
}
211
273
},
212
274
);
@@ -355,19 +417,12 @@ base mixin DartToolingDaemonSupport on ToolsSupport {
355
417
if (! _getDebugSessionsReady) return _dtdNotReady;
356
418
}
357
419
358
- final response = await dtd.getDebugSessions ();
359
- final debugSessions = response.debugSessions;
360
- if (debugSessions.isEmpty) return _noActiveDebugSession;
420
+ await updateActiveVmServices ();
421
+ if (activeVmServices.isEmpty) return _noActiveDebugSession;
361
422
362
- // TODO: Consider holding on to this connection.
363
- final vmService = await vmServiceConnectUri (
364
- debugSessions.first.vmServiceUri,
365
- );
366
- try {
367
- return await callback (vmService);
368
- } finally {
369
- unawaited (vmService.dispose ());
370
- }
423
+ // TODO: support selecting a VM Service if more than one are available.
424
+ final vmService = activeVmServices.values.first;
425
+ return await callback (vmService);
371
426
}
372
427
373
428
@visibleForTesting
0 commit comments