diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index 1fac03a7fd..a503c82748 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart @@ -414,7 +414,8 @@ known, it can be explicitly provided to attach via the command-line, e.g. _logger.printStatus('Waiting for a new connection from Flutter on ${device.name}...'); } } on RPCError catch (err) { - if (err.code == RPCErrorCodes.kServiceDisappeared) { + if (err.code == RPCErrorCodes.kServiceDisappeared || + err.message.contains('Service connection disposed')) { throwToolExit('Lost connection to device.'); } rethrow; diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index a38e9824d1..c42a19b0fc 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -516,7 +516,8 @@ class DevFS { final vm_service.Response response = await _vmService.createDevFS(fsName); _baseUri = Uri.parse(response.json!['uri'] as String); } on vm_service.RPCError catch (rpcException) { - if (rpcException.code == RPCErrorCodes.kServiceDisappeared) { + if (rpcException.code == RPCErrorCodes.kServiceDisappeared || + rpcException.message.contains('Service connection disposed')) { // This can happen if the device has been disconnected, so translate to // a DevFSException, which the caller will handle. throw DevFSException('Service disconnected', rpcException); diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 64be04caba..f9814589f5 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -503,7 +503,8 @@ class FlutterVmService { // 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) { + if (e.code == RPCErrorCodes.kServiceDisappeared || + e.message.contains('Service connection disposed')) { return null; } rethrow; @@ -873,8 +874,9 @@ class FlutterVmService { } on vm_service.RPCError catch (err) { // If an application is not using the framework or the VM service // disappears while handling a request, return null. - if ((err.code == RPCErrorCodes.kMethodNotFound) - || (err.code == RPCErrorCodes.kServiceDisappeared)) { + if ((err.code == RPCErrorCodes.kMethodNotFound) || + (err.code == RPCErrorCodes.kServiceDisappeared) || + (err.message.contains('Service connection disposed'))) { return null; } rethrow; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 33e2414922..7c3ec69fda 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -1125,6 +1125,45 @@ void main() { DeviceManager: () => testDeviceManager, }); + testUsingContext('Catches "Service connection disposed" error', () async { + final FakeAndroidDevice device = FakeAndroidDevice(id: '1') + ..portForwarder = const NoOpDevicePortForwarder() + ..onGetLogReader = () => NoOpDeviceLogReader('test'); + final FakeHotRunner hotRunner = FakeHotRunner(); + final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory() + ..hotRunner = hotRunner; + hotRunner.onAttach = ( + Completer? connectionInfoCompleter, + Completer? appStartedCompleter, + bool allowExistingDdsInstance, + bool enableDevTools, + ) async { + await null; + throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kServerError, 'Service connection disposed'); + }; + + testDeviceManager.devices = [device]; + testFileSystem.file('lib/main.dart').createSync(); + + final AttachCommand command = AttachCommand( + hotRunnerFactory: hotRunnerFactory, + stdio: stdio, + logger: logger, + terminal: terminal, + signals: signals, + platform: platform, + processInfo: processInfo, + fileSystem: testFileSystem, + ); + await expectLater(createTestCommandRunner(command).run([ + 'attach', + ]), throwsToolExit(message: 'Lost connection to device.')); + }, overrides: { + FileSystem: () => testFileSystem, + ProcessManager: () => FakeProcessManager.any(), + DeviceManager: () => testDeviceManager, + }); + testUsingContext('Does not catch generic RPC error', () async { final FakeAndroidDevice device = FakeAndroidDevice(id: '1') ..portForwarder = const NoOpDevicePortForwarder()