when setting up the log reader for a device during flutter run, discard any RPCError thrown due to the device being disconnected (#155049)

Fixes https://github.com/flutter/flutter/issues/154903

This PR contains some refactoring. To make the actual change easier to figure out, I've tried to separate parts of the change into multiple commits for easier reviewing 🙂.

**I plan on cherry-picking this change to stable.**
This commit is contained in:
Andrew Kolos 2024-09-12 08:42:15 -07:00 committed by GitHub
parent 50a190caec
commit d8f8613a5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 72 additions and 8 deletions

View File

@ -416,8 +416,19 @@ class FlutterDevice {
_loggingSubscription = null; _loggingSubscription = null;
} }
Future<void> initLogReader() async { /// Attempts to set up reading logs from the Flutter app on the device.
final vm_service.VM vm = await vmService!.service.getVM(); ///
/// This can fail if the device if no longer connected.
Future<void> tryInitLogReader() async {
final vm_service.VM? vm = await vmService!.getVmGuarded();
if (vm == null) {
globals.printError(
'Unable to initiate log reader for device'
'${device?.name}, because the Flutter VM service connection '
'is closed.',
);
return;
}
final DeviceLogReader logReader = await device!.getLogReader(app: package); final DeviceLogReader logReader = await device!.getLogReader(app: package);
logReader.appPid = vm.pid; logReader.appPid = vm.pid;
} }

View File

@ -104,7 +104,7 @@ class ColdRunner extends ResidentRunner {
if (device!.vmService == null) { if (device!.vmService == null) {
continue; continue;
} }
await device.initLogReader(); await device.tryInitLogReader();
globals.printTrace('Connected to ${device.device!.name}'); globals.printTrace('Connected to ${device.device!.name}');
} }
@ -154,7 +154,7 @@ class ColdRunner extends ResidentRunner {
} }
for (final FlutterDevice? device in flutterDevices) { for (final FlutterDevice? device in flutterDevices) {
await device!.initLogReader(); await device!.tryInitLogReader();
} }
for (final FlutterDevice? device in flutterDevices) { for (final FlutterDevice? device in flutterDevices) {
final List<FlutterView> views = await device!.vmService!.getFlutterViews(); final List<FlutterView> views = await device!.vmService!.getFlutterViews();

View File

@ -261,7 +261,7 @@ class HotRunner extends ResidentRunner {
} }
for (final FlutterDevice? device in flutterDevices) { for (final FlutterDevice? device in flutterDevices) {
await device!.initLogReader(); await device!.tryInitLogReader();
device device
.developmentShaderCompiler .developmentShaderCompiler
.configureCompiler(device.targetPlatform); .configureCompiler(device.targetPlatform);

View File

@ -491,6 +491,22 @@ class FlutterVmService {
final Uri? wsAddress; final Uri? wsAddress;
final Uri? httpAddress; final Uri? httpAddress;
/// Calls [service.getVM]. However, in the case that an [vm_service.RPCError]
/// is thrown due to the service being disconnected, the error is discarded
/// and null is returned.
Future<vm_service.VM?> getVmGuarded() async {
try {
return await service.getVM();
} on vm_service.RPCError catch (err) {
if (err.code == RPCErrorCodes.kServiceDisappeared ||
err.message.contains('Service connection disposed')) {
globals.printTrace('VmService.getVm call failed: $err');
return null;
}
rethrow;
}
}
Future<vm_service.Response?> callMethodWrapper( Future<vm_service.Response?> callMethodWrapper(
String method, { String method, {
String? isolateId, String? isolateId,

View File

@ -165,7 +165,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
} }
@override @override
Future<void> initLogReader() async { } Future<void> tryInitLogReader() async { }
} }
class FakeDevice extends Fake implements Device { class FakeDevice extends Fake implements Device {

View File

@ -228,7 +228,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
Future<void> stopEchoingDeviceLog() async { } Future<void> stopEchoingDeviceLog() async { }
@override @override
Future<void> initLogReader() async { } Future<void> tryInitLogReader() async { }
@override @override
Future<Uri> setupDevFS(String fsName, Directory rootDirectory) async { Future<Uri> setupDevFS(String fsName, Directory rootDirectory) async {

View File

@ -1934,6 +1934,43 @@ flutter:
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('FlutterDevice does not throw when unable to initiate log reader due to VM service disconnection', () async {
fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(
method: 'getVM',
error: FakeRPCError(
code: RPCErrorCodes.kServerError,
error: 'Service connection disposed',
),
),
],
);
final TestFlutterDevice flutterDevice = TestFlutterDevice(device);
flutterDevice.vmService = fakeVmServiceHost!.vmService;
await flutterDevice.tryInitLogReader();
final BufferLogger logger = globals.logger as BufferLogger;
expect(
logger.traceText,
contains(
'VmService.getVm call failed: getVM: (-32000) '
'Service connection disposed\n',
),
);
expect(
logger.errorText,
contains(
'Unable to initiate log reader for deviceFakeDevice, because '
'the Flutter VM service connection is closed.\n',
),
);
}, overrides: <Type, Generator>{
Logger: () => BufferLogger.test(),
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Uses existing DDS URI from exception field', () => testbed.run(() async { testUsingContext('Uses existing DDS URI from exception field', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice() final FakeDevice device = FakeDevice()

View File

@ -1755,7 +1755,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
Future<void> stopEchoingDeviceLog() async {} Future<void> stopEchoingDeviceLog() async {}
@override @override
Future<void> initLogReader() async {} Future<void> tryInitLogReader() async {}
@override @override
Future<Uri?> setupDevFS(String fsName, Directory rootDirectory) async { Future<Uri?> setupDevFS(String fsName, Directory rootDirectory) async {