diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index b8b35dbdc3..54d5e63720 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -417,6 +417,25 @@ extension FlutterVmService on vm_service.VmService { Uri get httpAddress => this != null ? _httpAddressExpando[this] : null; + Future callMethodWrapper( + String method, { + String isolateId, + Map args + }) async { + try { + return await callMethod(method, isolateId: isolateId, args: args); + } on vm_service.RPCError catch (e) { + // If the service disappears mid-request the tool is unable to recover + // and should begin to shutdown due to the service connection closing. + // Swallow the exception here and let the shutdown logic elsewhere deal + // with cleaning up. + if (e.code == RPCErrorCodes.kServiceDisappeared) { + return null; + } + rethrow; + } + } + /// Set the asset directory for the an attached Flutter view. Future setAssetDirectory({ @required Uri assetsDirectory, @@ -424,7 +443,7 @@ extension FlutterVmService on vm_service.VmService { @required String uiIsolateId, }) async { assert(assetsDirectory != null); - await callMethod(kSetAssetBundlePathMethod, + await callMethodWrapper(kSetAssetBundlePathMethod, isolateId: uiIsolateId, args: { 'viewId': viewId, @@ -439,12 +458,15 @@ extension FlutterVmService on vm_service.VmService { Future> getSkSLs({ @required String viewId, }) async { - final vm_service.Response response = await callMethod( + final vm_service.Response response = await callMethodWrapper( kGetSkSLsMethod, args: { 'viewId': viewId, }, ); + if (response == null) { + return null; + } return response.json['SkSLs'] as Map; } @@ -454,7 +476,7 @@ extension FlutterVmService on vm_service.VmService { Future flushUIThreadTasks({ @required String uiIsolateId, }) async { - await callMethod( + await callMethodWrapper( kFlushUIThreadTasksMethod, args: { 'isolateId': uiIsolateId, @@ -480,7 +502,7 @@ extension FlutterVmService on vm_service.VmService { final Future onRunnable = onIsolateEvent.firstWhere((vm_service.Event event) { return event.kind == vm_service.EventKind.kIsolateRunnable; }); - await callMethod( + await callMethodWrapper( kRunInViewMethod, args: { 'viewId': viewId, @@ -745,9 +767,12 @@ extension FlutterVmService on vm_service.VmService { Duration delay = const Duration(milliseconds: 50), }) async { while (true) { - final vm_service.Response response = await callMethod( + final vm_service.Response response = await callMethodWrapper( kListViewsMethod, ); + if (response == null) { + return null; + } final List rawViews = response.json['views'] as List; final List views = [ for (final Object rawView in rawViews) diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart index dfb283490c..448510025e 100644 --- a/packages/flutter_tools/test/general.shard/vmservice_test.dart +++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart @@ -312,6 +312,27 @@ void main() { expect(fakeVmServiceHost.hasRemainingExpectations, false); }); + testWithoutContext('Framework service extension invocations return null if service disappears ', () async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( + requests: [ + const FakeVmServiceRequest(method: kGetSkSLsMethod, args: { + 'viewId': '1234', + }, errorCode: RPCErrorCodes.kServiceDisappeared), + const FakeVmServiceRequest(method: kListViewsMethod, errorCode: RPCErrorCodes.kServiceDisappeared), + ] + ); + + final Map skSLs = await fakeVmServiceHost.vmService.getSkSLs( + viewId: '1234', + ); + expect(skSLs, null); + + final List views = await fakeVmServiceHost.vmService.getFlutterViews(); + expect(views, null); + + expect(fakeVmServiceHost.hasRemainingExpectations, false); + }); + testWithoutContext('getFlutterViews polls until a view is returned', () async { final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [