Skip to content

Commit b91aedb

Browse files
Fix StateError during hot reload when no Dart isolates found (#130537)
Fixes flutter/flutter#116262
1 parent f5a5581 commit b91aedb

2 files changed

Lines changed: 76 additions & 7 deletions

File tree

packages/flutter_tools/lib/src/run_hot.dart

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class HotRunner extends ResidentRunner {
9191
this.multidexEnabled = false,
9292
super.devtoolsHandler,
9393
StopwatchFactory stopwatchFactory = const StopwatchFactory(),
94-
ReloadSourcesHelper reloadSourcesHelper = _defaultReloadSourcesHelper,
94+
ReloadSourcesHelper reloadSourcesHelper = defaultReloadSourcesHelper,
9595
ReassembleHelper reassembleHelper = _defaultReassembleHelper,
9696
}) : _stopwatchFactory = stopwatchFactory,
9797
_reloadSourcesHelper = reloadSourcesHelper,
@@ -1174,7 +1174,8 @@ typedef ReloadSourcesHelper = Future<OperationResult> Function(
11741174
String? reason,
11751175
);
11761176

1177-
Future<OperationResult> _defaultReloadSourcesHelper(
1177+
@visibleForTesting
1178+
Future<OperationResult> defaultReloadSourcesHelper(
11781179
HotRunner hotRunner,
11791180
List<FlutterDevice?> flutterDevices,
11801181
bool? pause,
@@ -1186,18 +1187,21 @@ Future<OperationResult> _defaultReloadSourcesHelper(
11861187
) async {
11871188
final Stopwatch vmReloadTimer = Stopwatch()..start();
11881189
const String entryPath = 'main.dart.incremental.dill';
1189-
final List<Future<DeviceReloadReport>> allReportsFutures = <Future<DeviceReloadReport>>[];
1190+
final List<Future<DeviceReloadReport?>> allReportsFutures = <Future<DeviceReloadReport?>>[];
11901191

11911192
for (final FlutterDevice? device in flutterDevices) {
11921193
final List<Future<vm_service.ReloadReport>> reportFutures = await _reloadDeviceSources(
11931194
device!,
11941195
entryPath,
11951196
pause: pause,
11961197
);
1197-
allReportsFutures.add(Future.wait(reportFutures).then(
1198+
allReportsFutures.add(Future.wait(reportFutures).then<DeviceReloadReport?>(
11981199
(List<vm_service.ReloadReport> reports) async {
11991200
// TODO(aam): Investigate why we are validating only first reload report,
12001201
// which seems to be current behavior
1202+
if (reports.isEmpty) {
1203+
return null;
1204+
}
12011205
final vm_service.ReloadReport firstReport = reports.first;
12021206
// Don't print errors because they will be printed further down when
12031207
// `validateReloadReport` is called again.
@@ -1208,9 +1212,9 @@ Future<OperationResult> _defaultReloadSourcesHelper(
12081212
},
12091213
));
12101214
}
1211-
final List<DeviceReloadReport> reports = await Future.wait(allReportsFutures);
1212-
final vm_service.ReloadReport reloadReport = reports.first.reports[0];
1213-
if (!HotRunner.validateReloadReport(reloadReport)) {
1215+
final Iterable<DeviceReloadReport> reports = (await Future.wait(allReportsFutures)).whereType<DeviceReloadReport>();
1216+
final vm_service.ReloadReport? reloadReport = reports.isEmpty ? null : reports.first.reports[0];
1217+
if (reloadReport == null || !HotRunner.validateReloadReport(reloadReport)) {
12141218
// Reload failed.
12151219
HotEvent('reload-reject',
12161220
targetPlatform: targetPlatform!,
@@ -1223,6 +1227,9 @@ Future<OperationResult> _defaultReloadSourcesHelper(
12231227
// Reset devFS lastCompileTime to ensure the file will still be marked
12241228
// as dirty on subsequent reloads.
12251229
_resetDevFSCompileTime(flutterDevices);
1230+
if (reloadReport == null) {
1231+
return OperationResult(1, 'No Dart isolates found');
1232+
}
12261233
final ReloadReportContents contents = ReloadReportContents.fromReloadReport(reloadReport);
12271234
return OperationResult(1, 'Reload rejected: ${contents.notices.join("\n")}');
12281235
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_tools/src/devfs.dart';
6+
import 'package:flutter_tools/src/resident_runner.dart';
7+
import 'package:flutter_tools/src/run_hot.dart';
8+
import 'package:flutter_tools/src/vmservice.dart';
9+
import 'package:test/fake.dart';
10+
import 'package:vm_service/vm_service.dart' as vm_service;
11+
12+
import '../src/context.dart';
13+
14+
void main() {
15+
testUsingContext('defaultReloadSourcesHelper() handles empty DeviceReloadReports)', () {
16+
defaultReloadSourcesHelper(
17+
_FakeHotRunner(),
18+
<FlutterDevice?>[_FakeFlutterDevice()],
19+
false,
20+
const <String, dynamic>{},
21+
'android',
22+
'flutter-sdk',
23+
false,
24+
'test-reason',
25+
);
26+
});
27+
}
28+
29+
class _FakeHotRunner extends Fake implements HotRunner {}
30+
31+
class _FakeDevFS extends Fake implements DevFS {
32+
@override
33+
final Uri? baseUri = Uri();
34+
35+
@override
36+
void resetLastCompiled() {}
37+
}
38+
39+
class _FakeFlutterDevice extends Fake implements FlutterDevice {
40+
@override
41+
final DevFS? devFS = _FakeDevFS();
42+
43+
@override
44+
final FlutterVmService? vmService = _FakeFlutterVmService();
45+
}
46+
47+
class _FakeFlutterVmService extends Fake implements FlutterVmService {
48+
@override
49+
final vm_service.VmService service = _FakeVmService();
50+
}
51+
52+
class _FakeVmService extends Fake implements vm_service.VmService {
53+
@override
54+
Future<_FakeVm> getVM() async => _FakeVm();
55+
}
56+
57+
class _FakeVm extends Fake implements vm_service.VM {
58+
final List<vm_service.IsolateRef> _isolates = <vm_service.IsolateRef>[];
59+
60+
@override
61+
List<vm_service.IsolateRef>? get isolates => _isolates;
62+
}

0 commit comments

Comments
 (0)