Handle service disappeared RPCError when VM service connection disappears (#74424)
* Handle service disappeared RPCError when VM service connection
disappears while invoking a service extension registered by the
framework
* Add unit test, handle non-trivial cases
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index b8b35db..54d5e63 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -417,6 +417,25 @@
Uri get httpAddress => this != null ? _httpAddressExpando[this] : null;
+ Future<vm_service.Response> callMethodWrapper(
+ String method, {
+ String isolateId,
+ Map<String, dynamic> 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<void> setAssetDirectory({
@required Uri assetsDirectory,
@@ -424,7 +443,7 @@
@required String uiIsolateId,
}) async {
assert(assetsDirectory != null);
- await callMethod(kSetAssetBundlePathMethod,
+ await callMethodWrapper(kSetAssetBundlePathMethod,
isolateId: uiIsolateId,
args: <String, dynamic>{
'viewId': viewId,
@@ -439,12 +458,15 @@
Future<Map<String, Object>> getSkSLs({
@required String viewId,
}) async {
- final vm_service.Response response = await callMethod(
+ final vm_service.Response response = await callMethodWrapper(
kGetSkSLsMethod,
args: <String, String>{
'viewId': viewId,
},
);
+ if (response == null) {
+ return null;
+ }
return response.json['SkSLs'] as Map<String, Object>;
}
@@ -454,7 +476,7 @@
Future<void> flushUIThreadTasks({
@required String uiIsolateId,
}) async {
- await callMethod(
+ await callMethodWrapper(
kFlushUIThreadTasksMethod,
args: <String, String>{
'isolateId': uiIsolateId,
@@ -480,7 +502,7 @@
final Future<void> onRunnable = onIsolateEvent.firstWhere((vm_service.Event event) {
return event.kind == vm_service.EventKind.kIsolateRunnable;
});
- await callMethod(
+ await callMethodWrapper(
kRunInViewMethod,
args: <String, Object>{
'viewId': viewId,
@@ -745,9 +767,12 @@
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<Object> rawViews = response.json['views'] as List<Object>;
final List<FlutterView> views = <FlutterView>[
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 dfb2834..4485100 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 @@
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
+ testWithoutContext('Framework service extension invocations return null if service disappears ', () async {
+ final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
+ requests: <VmServiceExpectation>[
+ const FakeVmServiceRequest(method: kGetSkSLsMethod, args: <String, Object>{
+ 'viewId': '1234',
+ }, errorCode: RPCErrorCodes.kServiceDisappeared),
+ const FakeVmServiceRequest(method: kListViewsMethod, errorCode: RPCErrorCodes.kServiceDisappeared),
+ ]
+ );
+
+ final Map<String, Object> skSLs = await fakeVmServiceHost.vmService.getSkSLs(
+ viewId: '1234',
+ );
+ expect(skSLs, null);
+
+ final List<FlutterView> 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: <VmServiceExpectation>[