Handle "Service connection disposed" error from VmService disconnecting while requests are outstanding (#153714)
Fixes umbrella issue https://github.com/flutter/flutter/issues/153471, including its children: https://github.com/flutter/flutter/issues/153472, https://github.com/flutter/flutter/issues/153473, and https://github.com/flutter/flutter/issues/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].a479f91e80
anda7d8707a59
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]: https://github.com/flutter/flutter/issues/153471#issuecomment-2296294221
This commit is contained in:
parent
8bf1757b9e
commit
4a03b76c68
@ -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}...');
|
_logger.printStatus('Waiting for a new connection from Flutter on ${device.name}...');
|
||||||
}
|
}
|
||||||
} on RPCError catch (err) {
|
} 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.');
|
throwToolExit('Lost connection to device.');
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
|
@ -516,7 +516,8 @@ class DevFS {
|
|||||||
final vm_service.Response response = await _vmService.createDevFS(fsName);
|
final vm_service.Response response = await _vmService.createDevFS(fsName);
|
||||||
_baseUri = Uri.parse(response.json!['uri'] as String);
|
_baseUri = Uri.parse(response.json!['uri'] as String);
|
||||||
} on vm_service.RPCError catch (rpcException) {
|
} 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
|
// This can happen if the device has been disconnected, so translate to
|
||||||
// a DevFSException, which the caller will handle.
|
// a DevFSException, which the caller will handle.
|
||||||
throw DevFSException('Service disconnected', rpcException);
|
throw DevFSException('Service disconnected', rpcException);
|
||||||
|
@ -503,7 +503,8 @@ class FlutterVmService {
|
|||||||
// and should begin to shutdown due to the service connection closing.
|
// and should begin to shutdown due to the service connection closing.
|
||||||
// Swallow the exception here and let the shutdown logic elsewhere deal
|
// Swallow the exception here and let the shutdown logic elsewhere deal
|
||||||
// with cleaning up.
|
// with cleaning up.
|
||||||
if (e.code == RPCErrorCodes.kServiceDisappeared) {
|
if (e.code == RPCErrorCodes.kServiceDisappeared ||
|
||||||
|
e.message.contains('Service connection disposed')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
@ -873,8 +874,9 @@ class FlutterVmService {
|
|||||||
} on vm_service.RPCError catch (err) {
|
} on vm_service.RPCError catch (err) {
|
||||||
// If an application is not using the framework or the VM service
|
// If an application is not using the framework or the VM service
|
||||||
// disappears while handling a request, return null.
|
// disappears while handling a request, return null.
|
||||||
if ((err.code == RPCErrorCodes.kMethodNotFound)
|
if ((err.code == RPCErrorCodes.kMethodNotFound) ||
|
||||||
|| (err.code == RPCErrorCodes.kServiceDisappeared)) {
|
(err.code == RPCErrorCodes.kServiceDisappeared) ||
|
||||||
|
(err.message.contains('Service connection disposed'))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
|
@ -1125,6 +1125,45 @@ void main() {
|
|||||||
DeviceManager: () => testDeviceManager,
|
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<DebugConnectionInfo>? connectionInfoCompleter,
|
||||||
|
Completer<void>? appStartedCompleter,
|
||||||
|
bool allowExistingDdsInstance,
|
||||||
|
bool enableDevTools,
|
||||||
|
) async {
|
||||||
|
await null;
|
||||||
|
throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kServerError, 'Service connection disposed');
|
||||||
|
};
|
||||||
|
|
||||||
|
testDeviceManager.devices = <Device>[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(<String>[
|
||||||
|
'attach',
|
||||||
|
]), throwsToolExit(message: 'Lost connection to device.'));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => testFileSystem,
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
DeviceManager: () => testDeviceManager,
|
||||||
|
});
|
||||||
|
|
||||||
testUsingContext('Does not catch generic RPC error', () async {
|
testUsingContext('Does not catch generic RPC error', () async {
|
||||||
final FakeAndroidDevice device = FakeAndroidDevice(id: '1')
|
final FakeAndroidDevice device = FakeAndroidDevice(id: '1')
|
||||||
..portForwarder = const NoOpDevicePortForwarder()
|
..portForwarder = const NoOpDevicePortForwarder()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user