[flutter_tools] prevent hot reload/restart if device has not finished devFS initialization (#73420)
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index fbb6cff..443a7c2 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -8,7 +8,6 @@ import 'package:meta/meta.dart'; import 'package:pool/pool.dart'; -import 'base/async_guard.dart'; import 'base/context.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; @@ -105,6 +104,7 @@ /// reload process do not have this issue. bool _swap = false; + /// Whether the resident runner has correctly attached to the running application. bool _didAttach = false; final Map<String, List<int>> benchmarkData = <String, List<int>>{}; @@ -121,7 +121,6 @@ bool force = false, bool pause = false, }) async { - // TODO(cbernaschina): check that isolateId is the id of the UI isolate. final OperationResult result = await restart(pause: pause); if (!result.isOk) { throw vm_service.RPCError( @@ -469,8 +468,6 @@ String reason, }) async { final Stopwatch restartTimer = Stopwatch()..start(); - // TODO(aam): Add generator reset logic once we switch to using incremental - // compiler for full application recompilation on restart. final UpdateFSReport updatedDevFS = await _updateDevFS(fullRestart: true); if (!updatedDevFS.success) { for (final FlutterDevice device in flutterDevices) { @@ -592,6 +589,9 @@ bool silent = false, bool pause = false, }) async { + if (flutterDevices.any((FlutterDevice device) => device.devFS == null)) { + return OperationResult(1, 'Device initialization has not completed.'); + } String targetPlatform; String sdkName; bool emulator; @@ -666,13 +666,7 @@ if (!(await hotRunnerConfig.setupHotRestart())) { return OperationResult(1, 'setupHotRestart failed'); } - // The current implementation of the vmservice and JSON rpc may throw - // unhandled exceptions into the zone that cannot be caught with a regular - // try catch. The usage is [asyncGuard] is required to normalize the error - // handling, at least until we can refactor the underlying code. - result = await asyncGuard(() => _restartFromSources( - reason: reason, - )); + result = await _restartFromSources(reason: reason,); if (!result.isOk) { restartEvent = 'restart-failed'; }
diff --git a/packages/flutter_tools/test/general.shard/hot_test.dart b/packages/flutter_tools/test/general.shard/hot_test.dart index a18097f..080e9f2 100644 --- a/packages/flutter_tools/test/general.shard/hot_test.dart +++ b/packages/flutter_tools/test/general.shard/hot_test.dart
@@ -343,7 +343,7 @@ when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester); final List<FlutterDevice> devices = <FlutterDevice>[ - FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug), + FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)..devFS = MockDevFs(), ]; final OperationResult result = await HotRunner( devices,
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index e0606a8..bc1ac35 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -541,6 +541,37 @@ Usage: () => MockUsage(), })); + testUsingContext('ResidentRunner fails its operation if the device initialization is not complete', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + listViews, + listViews, + setAssetBundlePath, + ]); + when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { + return 'Example'; + }); + when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async { + return TargetPlatform.android_arm; + }); + when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async { + return false; + }); + final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync(); + final Completer<void> onAppStart = Completer<void>.sync(); + unawaited(residentRunner.attach( + appStartedCompleter: onAppStart, + connectionInfoCompleter: onConnectionInfo, + )); + await onAppStart.future; + when(mockFlutterDevice.devFS).thenReturn(null); + + final OperationResult result = await residentRunner.restart(fullRestart: false); + expect(result.fatal, false); + expect(result.code, 1); + expect(result.message, contains('Device initialization has not completed.')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); + })); + testUsingContext('ResidentRunner can handle an reload-barred exception from hot reload', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ listViews,