[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,