@@ -177,7 +177,9 @@ base mixin DartToolingDaemonSupport
177
177
return _callOnVmService (
178
178
callback: (vmService) async {
179
179
final appListener = await _AppListener .forVmService (vmService, this );
180
- if (! appListener.registeredServices.contains (_flutterDriverService)) {
180
+ if (! appListener.registeredServices.containsKey (
181
+ _flutterDriverService,
182
+ )) {
181
183
return _flutterDriverNotRegistered;
182
184
}
183
185
final vm = await vmService.getVM ();
@@ -358,36 +360,17 @@ base mixin DartToolingDaemonSupport
358
360
Future <CallToolResult > hotReload (CallToolRequest request) async {
359
361
return _callOnVmService (
360
362
callback: (vmService) async {
363
+ final appListener = await _AppListener .forVmService (vmService, this );
361
364
if (request.arguments? ['clearRuntimeErrors' ] == true ) {
362
- ( await _AppListener . forVmService (vmService, this )) .errorLog.clear ();
365
+ appListener .errorLog.clear ();
363
366
}
364
367
365
368
final vm = await vmService.getVM ();
366
369
ReloadReport ? report;
367
- StreamSubscription <Event >? serviceStreamSubscription;
368
- try {
369
- final hotReloadMethodNameCompleter = Completer <String ?>();
370
- serviceStreamSubscription = vmService
371
- .onEvent (EventStreams .kService)
372
- .listen ((Event e) {
373
- if (e.kind == EventKind .kServiceRegistered) {
374
- final serviceName = e.service! ;
375
- if (serviceName == 'reloadSources' ) {
376
- // This may look something like 's0.reloadSources'.
377
- hotReloadMethodNameCompleter.complete (e.method);
378
- }
379
- }
380
- });
381
370
382
- await vmService.streamListen (EventStreams .kService);
383
-
384
- final hotReloadMethodName = await hotReloadMethodNameCompleter.future
385
- .timeout (
386
- const Duration (milliseconds: 1000 ),
387
- onTimeout: () async {
388
- return null ;
389
- },
390
- );
371
+ try {
372
+ final hotReloadMethodName = await appListener
373
+ .waitForServiceRegistration ('reloadSources' );
391
374
392
375
/// If we haven't seen a specific one, we just call the default one.
393
376
if (hotReloadMethodName == null ) {
@@ -406,9 +389,12 @@ base mixin DartToolingDaemonSupport
406
389
report = ReloadReport (success: false );
407
390
}
408
391
}
409
- } finally {
410
- await serviceStreamSubscription? .cancel ();
411
- await vmService.streamCancel (EventStreams .kService);
392
+ } catch (e) {
393
+ // Handle potential errors during the process
394
+ return CallToolResult (
395
+ isError: true ,
396
+ content: [TextContent (text: 'Hot reload failed: $e ' )],
397
+ );
412
398
}
413
399
final success = report.success == true ;
414
400
return CallToolResult (
@@ -1066,7 +1052,12 @@ class _AppListener {
1066
1052
/// A broadcast stream of all errors that come in after you start listening.
1067
1053
Stream <String > get errorsStream => _errorsController.stream;
1068
1054
1069
- final Set <String > registeredServices;
1055
+ /// A map of service names to the names of their methods.
1056
+ final Map <String , String ?> registeredServices;
1057
+
1058
+ /// A map of service names to completers that should be fired when the service
1059
+ /// is registered.
1060
+ final _pendingServiceRequests = < String , List <Completer <String ?>>> {};
1070
1061
1071
1062
/// Controller for the [errorsStream] .
1072
1063
final StreamController <String > _errorsController;
@@ -1105,9 +1096,36 @@ class _AppListener {
1105
1096
final errorLog = ErrorLog ();
1106
1097
errorsController.stream.listen (errorLog.add);
1107
1098
final subscriptions = < StreamSubscription <void >> [];
1108
- final registeredServices = < String > {};
1099
+ final registeredServices = < String , String ? > {};
1100
+ final pendingServiceRequests = < String , List <Completer <String ?>>> {};
1109
1101
1110
1102
try {
1103
+ subscriptions.addAll ([
1104
+ vmService.onServiceEvent.listen ((Event e) {
1105
+ switch (e.kind) {
1106
+ case EventKind .kServiceRegistered:
1107
+ final serviceName = e.service! ;
1108
+ registeredServices[serviceName] = e.method;
1109
+ // If there are any pending requests for this service, complete
1110
+ // them.
1111
+ if (pendingServiceRequests.containsKey (serviceName)) {
1112
+ for (final completer
1113
+ in pendingServiceRequests[serviceName]! ) {
1114
+ completer.complete (e.method);
1115
+ }
1116
+ pendingServiceRequests.remove (serviceName);
1117
+ }
1118
+ case EventKind .kServiceUnregistered:
1119
+ registeredServices.remove (e.service! );
1120
+ }
1121
+ }),
1122
+ vmService.onIsolateEvent.listen ((e) {
1123
+ switch (e.kind) {
1124
+ case EventKind .kServiceExtensionAdded:
1125
+ registeredServices[e.extensionRPC! ] = null ;
1126
+ }
1127
+ }),
1128
+ ]);
1111
1129
subscriptions.add (
1112
1130
vmService.onExtensionEventWithHistory.listen ((Event e) {
1113
1131
if (e.extensionKind == 'Flutter.Error' ) {
@@ -1135,23 +1153,6 @@ class _AppListener {
1135
1153
}),
1136
1154
);
1137
1155
1138
- subscriptions.addAll ([
1139
- vmService.onServiceEvent.listen ((Event e) {
1140
- switch (e.kind) {
1141
- case EventKind .kServiceRegistered:
1142
- registeredServices.add (e.service! );
1143
- case EventKind .kServiceUnregistered:
1144
- registeredServices.remove (e.service! );
1145
- }
1146
- }),
1147
- vmService.onIsolateEvent.listen ((e) {
1148
- switch (e.kind) {
1149
- case EventKind .kServiceExtensionAdded:
1150
- registeredServices.add (e.extensionRPC! );
1151
- }
1152
- }),
1153
- ]);
1154
-
1155
1156
await [
1156
1157
vmService.streamListen (EventStreams .kExtension),
1157
1158
vmService.streamListen (EventStreams .kIsolate),
@@ -1161,7 +1162,9 @@ class _AppListener {
1161
1162
1162
1163
final vm = await vmService.getVM ();
1163
1164
final isolate = await vmService.getIsolate (vm.isolates! .first.id! );
1164
- registeredServices.addAll (isolate.extensionRPCs ?? []);
1165
+ for (final extension in isolate.extensionRPCs ?? < String > []) {
1166
+ registeredServices[extension ] = null ;
1167
+ }
1165
1168
} catch (e) {
1166
1169
logger.log (LoggingLevel .error, 'Error subscribing to app errors: $e ' );
1167
1170
}
@@ -1175,18 +1178,46 @@ class _AppListener {
1175
1178
}();
1176
1179
}
1177
1180
1181
+ /// Returns a future that completes with the registered method name for the
1182
+ /// given [serviceName] .
1183
+ Future <String ?> waitForServiceRegistration (
1184
+ String serviceName, {
1185
+ Duration timeout = const Duration (seconds: 1 ),
1186
+ }) async {
1187
+ if (registeredServices.containsKey (serviceName)) {
1188
+ return registeredServices[serviceName];
1189
+ }
1190
+ final completer = Completer <String ?>();
1191
+ _pendingServiceRequests.putIfAbsent (serviceName, () => []).add (completer);
1192
+
1193
+ return completer.future.timeout (
1194
+ timeout,
1195
+ onTimeout: () {
1196
+ // Important: Clean up the completer from the list on timeout.
1197
+ _pendingServiceRequests[serviceName]? .remove (completer);
1198
+ if (_pendingServiceRequests[serviceName]? .isEmpty ?? false ) {
1199
+ _pendingServiceRequests.remove (serviceName);
1200
+ }
1201
+ return null ; // Return null on timeout
1202
+ },
1203
+ );
1204
+ }
1205
+
1178
1206
Future <void > shutdown () async {
1179
1207
errorLog.clear ();
1180
1208
registeredServices.clear ();
1181
1209
await _errorsController.close ();
1182
1210
await Future .wait (_subscriptions.map ((s) => s.cancel ()));
1183
1211
try {
1184
- await _vmService.streamCancel (EventStreams .kExtension);
1185
- await _vmService.streamCancel (EventStreams .kIsolate);
1186
- await _vmService.streamCancel (EventStreams .kStderr);
1187
- await _vmService.streamCancel (EventStreams .kService);
1212
+ await [
1213
+ _vmService.streamCancel (EventStreams .kExtension),
1214
+ _vmService.streamCancel (EventStreams .kIsolate),
1215
+ _vmService.streamCancel (EventStreams .kStderr),
1216
+ _vmService.streamCancel (EventStreams .kService),
1217
+ ].wait;
1188
1218
} on RPCError catch (_) {
1189
- // The vm service might already be disposed in which causes these to fail.
1219
+ // The vm service might already be disposed which could cause these to
1220
+ // fail.
1190
1221
}
1191
1222
}
1192
1223
}
0 commit comments