Skip to content

Commit 4a03b76

Browse files
authored
Handle "Service connection disposed" error from VmService disconnecting while requests are outstanding (#153714)
Fixes umbrella issue flutter/flutter#153471, including its children: flutter/flutter#153472, flutter/flutter#153473, and flutter/flutter#153474. The VM service can be disposed at any time during requests (e.g. the user closes the app or stops debugging in VSCode)[^1]. dart-lang/sdk@a479f91 and dart-lang/sdk@a7d8707 updated package:vm_service to throw new `RPCError`s when the service disconnects while requests are inflight. Therefore, we need to handle these exceptions in the tool. See umbrella issue for more details. I plan on cherry-picking this change to the stable channel. [^1]: flutter/flutter#153471 (comment)
1 parent 8bf1757 commit 4a03b76

File tree

4 files changed

+48
-5
lines changed

4 files changed

+48
-5
lines changed

packages/flutter_tools/lib/src/commands/attach.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,8 @@ known, it can be explicitly provided to attach via the command-line, e.g.
414414
_logger.printStatus('Waiting for a new connection from Flutter on ${device.name}...');
415415
}
416416
} on RPCError catch (err) {
417-
if (err.code == RPCErrorCodes.kServiceDisappeared) {
417+
if (err.code == RPCErrorCodes.kServiceDisappeared ||
418+
err.message.contains('Service connection disposed')) {
418419
throwToolExit('Lost connection to device.');
419420
}
420421
rethrow;

packages/flutter_tools/lib/src/devfs.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,8 @@ class DevFS {
516516
final vm_service.Response response = await _vmService.createDevFS(fsName);
517517
_baseUri = Uri.parse(response.json!['uri'] as String);
518518
} on vm_service.RPCError catch (rpcException) {
519-
if (rpcException.code == RPCErrorCodes.kServiceDisappeared) {
519+
if (rpcException.code == RPCErrorCodes.kServiceDisappeared ||
520+
rpcException.message.contains('Service connection disposed')) {
520521
// This can happen if the device has been disconnected, so translate to
521522
// a DevFSException, which the caller will handle.
522523
throw DevFSException('Service disconnected', rpcException);

packages/flutter_tools/lib/src/vmservice.dart

+5-3
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,8 @@ class FlutterVmService {
503503
// and should begin to shutdown due to the service connection closing.
504504
// Swallow the exception here and let the shutdown logic elsewhere deal
505505
// with cleaning up.
506-
if (e.code == RPCErrorCodes.kServiceDisappeared) {
506+
if (e.code == RPCErrorCodes.kServiceDisappeared ||
507+
e.message.contains('Service connection disposed')) {
507508
return null;
508509
}
509510
rethrow;
@@ -873,8 +874,9 @@ class FlutterVmService {
873874
} on vm_service.RPCError catch (err) {
874875
// If an application is not using the framework or the VM service
875876
// disappears while handling a request, return null.
876-
if ((err.code == RPCErrorCodes.kMethodNotFound)
877-
|| (err.code == RPCErrorCodes.kServiceDisappeared)) {
877+
if ((err.code == RPCErrorCodes.kMethodNotFound) ||
878+
(err.code == RPCErrorCodes.kServiceDisappeared) ||
879+
(err.message.contains('Service connection disposed'))) {
878880
return null;
879881
}
880882
rethrow;

packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart

+39
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,45 @@ void main() {
11251125
DeviceManager: () => testDeviceManager,
11261126
});
11271127

1128+
testUsingContext('Catches "Service connection disposed" error', () async {
1129+
final FakeAndroidDevice device = FakeAndroidDevice(id: '1')
1130+
..portForwarder = const NoOpDevicePortForwarder()
1131+
..onGetLogReader = () => NoOpDeviceLogReader('test');
1132+
final FakeHotRunner hotRunner = FakeHotRunner();
1133+
final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory()
1134+
..hotRunner = hotRunner;
1135+
hotRunner.onAttach = (
1136+
Completer<DebugConnectionInfo>? connectionInfoCompleter,
1137+
Completer<void>? appStartedCompleter,
1138+
bool allowExistingDdsInstance,
1139+
bool enableDevTools,
1140+
) async {
1141+
await null;
1142+
throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kServerError, 'Service connection disposed');
1143+
};
1144+
1145+
testDeviceManager.devices = <Device>[device];
1146+
testFileSystem.file('lib/main.dart').createSync();
1147+
1148+
final AttachCommand command = AttachCommand(
1149+
hotRunnerFactory: hotRunnerFactory,
1150+
stdio: stdio,
1151+
logger: logger,
1152+
terminal: terminal,
1153+
signals: signals,
1154+
platform: platform,
1155+
processInfo: processInfo,
1156+
fileSystem: testFileSystem,
1157+
);
1158+
await expectLater(createTestCommandRunner(command).run(<String>[
1159+
'attach',
1160+
]), throwsToolExit(message: 'Lost connection to device.'));
1161+
}, overrides: <Type, Generator>{
1162+
FileSystem: () => testFileSystem,
1163+
ProcessManager: () => FakeProcessManager.any(),
1164+
DeviceManager: () => testDeviceManager,
1165+
});
1166+
11281167
testUsingContext('Does not catch generic RPC error', () async {
11291168
final FakeAndroidDevice device = FakeAndroidDevice(id: '1')
11301169
..portForwarder = const NoOpDevicePortForwarder()

0 commit comments

Comments
 (0)