@@ -177,7 +177,9 @@ base mixin DartToolingDaemonSupport
177177 return _callOnVmService (
178178 callback: (vmService) async {
179179 final appListener = await _AppListener .forVmService (vmService, this );
180- if (! appListener.registeredServices.contains (_flutterDriverService)) {
180+ if (! appListener.registeredServices.containsKey (
181+ _flutterDriverService,
182+ )) {
181183 return _flutterDriverNotRegistered;
182184 }
183185 final vm = await vmService.getVM ();
@@ -358,36 +360,17 @@ base mixin DartToolingDaemonSupport
358360 Future <CallToolResult > hotReload (CallToolRequest request) async {
359361 return _callOnVmService (
360362 callback: (vmService) async {
363+ final appListener = await _AppListener .forVmService (vmService, this );
361364 if (request.arguments? ['clearRuntimeErrors' ] == true ) {
362- ( await _AppListener . forVmService (vmService, this )) .errorLog.clear ();
365+ appListener .errorLog.clear ();
363366 }
364367
365368 final vm = await vmService.getVM ();
366369 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- });
381370
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' );
391374
392375 /// If we haven't seen a specific one, we just call the default one.
393376 if (hotReloadMethodName == null ) {
@@ -406,9 +389,12 @@ base mixin DartToolingDaemonSupport
406389 report = ReloadReport (success: false );
407390 }
408391 }
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+ );
412398 }
413399 final success = report.success == true ;
414400 return CallToolResult (
@@ -1066,7 +1052,12 @@ class _AppListener {
10661052 /// A broadcast stream of all errors that come in after you start listening.
10671053 Stream <String > get errorsStream => _errorsController.stream;
10681054
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 ?>>> {};
10701061
10711062 /// Controller for the [errorsStream] .
10721063 final StreamController <String > _errorsController;
@@ -1105,9 +1096,36 @@ class _AppListener {
11051096 final errorLog = ErrorLog ();
11061097 errorsController.stream.listen (errorLog.add);
11071098 final subscriptions = < StreamSubscription <void >> [];
1108- final registeredServices = < String > {};
1099+ final registeredServices = < String , String ? > {};
1100+ final pendingServiceRequests = < String , List <Completer <String ?>>> {};
11091101
11101102 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+ ]);
11111129 subscriptions.add (
11121130 vmService.onExtensionEventWithHistory.listen ((Event e) {
11131131 if (e.extensionKind == 'Flutter.Error' ) {
@@ -1135,23 +1153,6 @@ class _AppListener {
11351153 }),
11361154 );
11371155
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-
11551156 await [
11561157 vmService.streamListen (EventStreams .kExtension),
11571158 vmService.streamListen (EventStreams .kIsolate),
@@ -1161,7 +1162,9 @@ class _AppListener {
11611162
11621163 final vm = await vmService.getVM ();
11631164 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+ }
11651168 } catch (e) {
11661169 logger.log (LoggingLevel .error, 'Error subscribing to app errors: $e ' );
11671170 }
@@ -1175,18 +1178,46 @@ class _AppListener {
11751178 }();
11761179 }
11771180
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+
11781206 Future <void > shutdown () async {
11791207 errorLog.clear ();
11801208 registeredServices.clear ();
11811209 await _errorsController.close ();
11821210 await Future .wait (_subscriptions.map ((s) => s.cancel ()));
11831211 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;
11881218 } 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.
11901221 }
11911222 }
11921223}
0 commit comments