Unverified Commit 50dfd137 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] prevent hot reload/restart if device has not finished devFS initialization (#73420)

parent a8247e78
...@@ -8,7 +8,6 @@ import 'package:vm_service/vm_service.dart' as vm_service; ...@@ -8,7 +8,6 @@ import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:pool/pool.dart'; import 'package:pool/pool.dart';
import 'base/async_guard.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/logger.dart'; import 'base/logger.dart';
...@@ -105,6 +104,7 @@ class HotRunner extends ResidentRunner { ...@@ -105,6 +104,7 @@ class HotRunner extends ResidentRunner {
/// reload process do not have this issue. /// reload process do not have this issue.
bool _swap = false; bool _swap = false;
/// Whether the resident runner has correctly attached to the running application.
bool _didAttach = false; bool _didAttach = false;
final Map<String, List<int>> benchmarkData = <String, List<int>>{}; final Map<String, List<int>> benchmarkData = <String, List<int>>{};
...@@ -121,7 +121,6 @@ class HotRunner extends ResidentRunner { ...@@ -121,7 +121,6 @@ class HotRunner extends ResidentRunner {
bool force = false, bool force = false,
bool pause = false, bool pause = false,
}) async { }) async {
// TODO(cbernaschina): check that isolateId is the id of the UI isolate.
final OperationResult result = await restart(pause: pause); final OperationResult result = await restart(pause: pause);
if (!result.isOk) { if (!result.isOk) {
throw vm_service.RPCError( throw vm_service.RPCError(
...@@ -469,8 +468,6 @@ class HotRunner extends ResidentRunner { ...@@ -469,8 +468,6 @@ class HotRunner extends ResidentRunner {
String reason, String reason,
}) async { }) async {
final Stopwatch restartTimer = Stopwatch()..start(); 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); final UpdateFSReport updatedDevFS = await _updateDevFS(fullRestart: true);
if (!updatedDevFS.success) { if (!updatedDevFS.success) {
for (final FlutterDevice device in flutterDevices) { for (final FlutterDevice device in flutterDevices) {
...@@ -592,6 +589,9 @@ class HotRunner extends ResidentRunner { ...@@ -592,6 +589,9 @@ class HotRunner extends ResidentRunner {
bool silent = false, bool silent = false,
bool pause = false, bool pause = false,
}) async { }) async {
if (flutterDevices.any((FlutterDevice device) => device.devFS == null)) {
return OperationResult(1, 'Device initialization has not completed.');
}
String targetPlatform; String targetPlatform;
String sdkName; String sdkName;
bool emulator; bool emulator;
...@@ -666,13 +666,7 @@ class HotRunner extends ResidentRunner { ...@@ -666,13 +666,7 @@ class HotRunner extends ResidentRunner {
if (!(await hotRunnerConfig.setupHotRestart())) { if (!(await hotRunnerConfig.setupHotRestart())) {
return OperationResult(1, 'setupHotRestart failed'); return OperationResult(1, 'setupHotRestart failed');
} }
// The current implementation of the vmservice and JSON rpc may throw result = await _restartFromSources(reason: reason,);
// 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,
));
if (!result.isOk) { if (!result.isOk) {
restartEvent = 'restart-failed'; restartEvent = 'restart-failed';
} }
......
...@@ -343,7 +343,7 @@ void main() { ...@@ -343,7 +343,7 @@ void main() {
when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.supportsHotRestart).thenReturn(true);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester); when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
final List<FlutterDevice> devices = <FlutterDevice>[ 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( final OperationResult result = await HotRunner(
devices, devices,
......
...@@ -541,6 +541,37 @@ void main() { ...@@ -541,6 +541,37 @@ void main() {
Usage: () => MockUsage(), 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 { testUsingContext('ResidentRunner can handle an reload-barred exception from hot reload', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment