[flutter_tools] eagerly set asset directory path, cache flutter views, simplify error handling (#68978)
Performs some small cleanup on the hot reload code path.
- Combines nested try/catch into single try catch, update on clause now that package:vm_service is used and Map does not need to be caught.
- Cache FlutterViews for the lifetime of the hot reload method handler
- Set asset directory path once during startup and remove conditional set during hot reload
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index f135727..21bf77c 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -110,7 +110,6 @@
final Map<String, List<int>> benchmarkData = <String, List<int>>{};
DateTime firstBuildTime;
- bool _shouldResetAssetDirectory = true;
void _addBenchmarkData(String name, int value) {
benchmarkData[name] ??= <int>[];
@@ -205,7 +204,7 @@
),
);
}
- } on Exception catch (error) {
+ } on DevFSException catch (error) {
globals.printError('Error initializing DevFS: $error');
return 3;
}
@@ -226,6 +225,14 @@
device.generator.accept();
}
final List<FlutterView> views = await device.vmService.getFlutterViews();
+ final Uri deviceAssetsDirectoryUri = device.devFS.baseUri.resolveUri(globals.fs.path.toUri(getAssetBuildDirectory()));
+ await Future.wait<void>(views.map<Future<void>>(
+ (FlutterView view) => device.vmService.setAssetDirectory(
+ assetsDirectory: deviceAssetsDirectoryUri,
+ uiIsolateId: view.uiIsolate.id,
+ viewId: view.id,
+ )
+ ));
for (final FlutterView view in views) {
globals.printTrace('Connected to $view.');
}
@@ -729,17 +736,36 @@
);
},
);
- } on vm_service.RPCError {
- HotEvent('exception',
- targetPlatform: targetPlatform,
- sdkName: sdkName,
- emulator: emulator,
- fullRestart: false,
- nullSafety: usageNullSafety,
- reason: reason,
- fastReassemble: null,
- ).send();
- return OperationResult(1, 'hot reload failed to complete', fatal: true);
+ } on vm_service.RPCError catch (error) {
+ String errorMessage = 'hot reload failed to complete';
+ int errorCode = 1;
+ if (error.code == kIsolateReloadBarred) {
+ errorCode = error.code;
+ errorMessage = 'Unable to hot reload application due to an unrecoverable error in '
+ 'the source code. Please address the error and then use "R" to '
+ 'restart the app.\n'
+ '${error.message} (error code: ${error.code})';
+ HotEvent('reload-barred',
+ targetPlatform: targetPlatform,
+ sdkName: sdkName,
+ emulator: emulator,
+ fullRestart: false,
+ reason: reason,
+ nullSafety: usageNullSafety,
+ fastReassemble: null,
+ ).send();
+ } else {
+ HotEvent('exception',
+ targetPlatform: targetPlatform,
+ sdkName: sdkName,
+ emulator: emulator,
+ fullRestart: false,
+ nullSafety: usageNullSafety,
+ reason: reason,
+ fastReassemble: null,
+ ).send();
+ }
+ return OperationResult(errorCode, errorMessage, fatal: true);
} finally {
status.cancel();
}
@@ -764,20 +790,6 @@
];
}
- Future<void> _resetAssetDirectory(FlutterDevice device) async {
- final Uri deviceAssetsDirectoryUri = device.devFS.baseUri.resolveUri(
- globals.fs.path.toUri(getAssetBuildDirectory()));
- assert(deviceAssetsDirectoryUri != null);
- final List<FlutterView> views = await device.vmService.getFlutterViews();
- await Future.wait<void>(views.map<Future<void>>(
- (FlutterView view) => device.vmService.setAssetDirectory(
- assetsDirectory: deviceAssetsDirectoryUri,
- uiIsolateId: view.uiIsolate.id,
- viewId: view.id,
- )
- ));
- }
-
Future<OperationResult> _reloadSources({
String targetPlatform,
String sdkName,
@@ -786,8 +798,10 @@
String reason,
void Function(String message) onSlow,
}) async {
+ final Map<FlutterDevice, List<FlutterView>> viewCache = <FlutterDevice, List<FlutterView>>{};
for (final FlutterDevice device in flutterDevices) {
final List<FlutterView> views = await device.vmService.getFlutterViews();
+ viewCache[device] = views;
for (final FlutterView view in views) {
if (view.uiIsolate == null) {
return OperationResult(2, 'Application isolate not found', fatal: true);
@@ -806,74 +820,36 @@
}
String reloadMessage;
final Stopwatch vmReloadTimer = Stopwatch()..start();
- Map<String, dynamic> firstReloadDetails;
- try {
- const String entryPath = 'main.dart.incremental.dill';
- final List<Future<DeviceReloadReport>> allReportsFutures = <Future<DeviceReloadReport>>[];
- for (final FlutterDevice device in flutterDevices) {
- if (_shouldResetAssetDirectory) {
- // Asset directory has to be set only once when the engine switches from
- // running from bundle to uploaded files.
- await _resetAssetDirectory(device);
- _shouldResetAssetDirectory = false;
- }
- final List<Future<vm_service.ReloadReport>> reportFutures = await _reloadDeviceSources(
- device,
- entryPath, pause: pause,
- );
- allReportsFutures.add(Future.wait(reportFutures).then(
- (List<vm_service.ReloadReport> reports) async {
- // TODO(aam): Investigate why we are validating only first reload report,
- // which seems to be current behavior
- final vm_service.ReloadReport firstReport = reports.first;
- // Don't print errors because they will be printed further down when
- // `validateReloadReport` is called again.
- await device.updateReloadStatus(
- validateReloadReport(firstReport, printErrors: false),
- );
- return DeviceReloadReport(device, reports);
- },
- ));
- }
- final List<DeviceReloadReport> reports = await Future.wait(allReportsFutures);
- for (final DeviceReloadReport report in reports) {
- final vm_service.ReloadReport reloadReport = report.reports[0];
- if (!validateReloadReport(reloadReport)) {
- // Reload failed.
- HotEvent('reload-reject',
- targetPlatform: targetPlatform,
- sdkName: sdkName,
- emulator: emulator,
- fullRestart: false,
- reason: reason,
- nullSafety: usageNullSafety,
- fastReassemble: null,
- ).send();
- // Reset devFS lastCompileTime to ensure the file will still be marked
- // as dirty on subsequent reloads.
- _resetDevFSCompileTime();
- final ReloadReportContents contents = ReloadReportContents.fromReloadReport(reloadReport);
- return OperationResult(1, 'Reload rejected: ${contents.notices.join("\n")}');
- }
- // Collect stats only from the first device. If/when run -d all is
- // refactored, we'll probably need to send one hot reload/restart event
- // per device to analytics.
- firstReloadDetails ??= castStringKeyedMap(reloadReport.json['details']);
- final int loadedLibraryCount = reloadReport.json['details']['loadedLibraryCount'] as int;
- final int finalLibraryCount = reloadReport.json['details']['finalLibraryCount'] as int;
- globals.printTrace('reloaded $loadedLibraryCount of $finalLibraryCount libraries');
- reloadMessage = 'Reloaded $loadedLibraryCount of $finalLibraryCount libraries';
- }
- } on Map<String, dynamic> catch (error, stackTrace) {
- globals.printTrace('Hot reload failed: $error\n$stackTrace');
- final int errorCode = error['code'] as int;
- String errorMessage = error['message'] as String;
- if (errorCode == kIsolateReloadBarred) {
- errorMessage = 'Unable to hot reload application due to an unrecoverable error in '
- 'the source code. Please address the error and then use "R" to '
- 'restart the app.\n'
- '$errorMessage (error code: $errorCode)';
- HotEvent('reload-barred',
+ Map<String, Object> firstReloadDetails;
+ const String entryPath = 'main.dart.incremental.dill';
+ final List<Future<DeviceReloadReport>> allReportsFutures = <Future<DeviceReloadReport>>[];
+
+ for (final FlutterDevice device in flutterDevices) {
+ final List<Future<vm_service.ReloadReport>> reportFutures = await _reloadDeviceSources(
+ device,
+ entryPath,
+ pause: pause,
+ );
+ allReportsFutures.add(Future.wait(reportFutures).then(
+ (List<vm_service.ReloadReport> reports) async {
+ // TODO(aam): Investigate why we are validating only first reload report,
+ // which seems to be current behavior
+ final vm_service.ReloadReport firstReport = reports.first;
+ // Don't print errors because they will be printed further down when
+ // `validateReloadReport` is called again.
+ await device.updateReloadStatus(
+ validateReloadReport(firstReport, printErrors: false),
+ );
+ return DeviceReloadReport(device, reports);
+ },
+ ));
+ }
+ final List<DeviceReloadReport> reports = await Future.wait(allReportsFutures);
+ for (final DeviceReloadReport report in reports) {
+ final vm_service.ReloadReport reloadReport = report.reports[0];
+ if (!validateReloadReport(reloadReport)) {
+ // Reload failed.
+ HotEvent('reload-reject',
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
@@ -882,27 +858,35 @@
nullSafety: usageNullSafety,
fastReassemble: null,
).send();
- return OperationResult(errorCode, errorMessage);
+ // Reset devFS lastCompileTime to ensure the file will still be marked
+ // as dirty on subsequent reloads.
+ _resetDevFSCompileTime();
+ final ReloadReportContents contents = ReloadReportContents.fromReloadReport(reloadReport);
+ return OperationResult(1, 'Reload rejected: ${contents.notices.join("\n")}');
}
- return OperationResult(errorCode, '$errorMessage (error code: $errorCode)');
- } on Exception catch (error, stackTrace) {
- globals.printTrace('Hot reload failed: $error\n$stackTrace');
- return OperationResult(1, '$error');
+ // Collect stats only from the first device. If/when run -d all is
+ // refactored, we'll probably need to send one hot reload/restart event
+ // per device to analytics.
+ firstReloadDetails ??= castStringKeyedMap(reloadReport.json['details']);
+ final int loadedLibraryCount = reloadReport.json['details']['loadedLibraryCount'] as int;
+ final int finalLibraryCount = reloadReport.json['details']['finalLibraryCount'] as int;
+ globals.printTrace('reloaded $loadedLibraryCount of $finalLibraryCount libraries');
+ reloadMessage = 'Reloaded $loadedLibraryCount of $finalLibraryCount libraries';
}
+
// Record time it took for the VM to reload the sources.
_addBenchmarkData('hotReloadVMReloadMilliseconds', vmReloadTimer.elapsed.inMilliseconds);
final Stopwatch reassembleTimer = Stopwatch()..start();
await _evictDirtyAssets();
- // Check if any isolates are paused and reassemble those
- // that aren't.
+ // Check if any isolates are paused and reassemble those that aren't.
final Map<FlutterView, vm_service.VmService> reassembleViews = <FlutterView, vm_service.VmService>{};
final List<Future<void>> reassembleFutures = <Future<void>>[];
String serviceEventKind;
int pausedIsolatesFound = 0;
bool failedReassemble = false;
for (final FlutterDevice device in flutterDevices) {
- final List<FlutterView> views = await device.vmService.getFlutterViews();
+ final List<FlutterView> views = viewCache[device];
for (final FlutterView view in views) {
// Check if the isolate is paused, and if so, don't reassemble. Ignore the
// PostPauseEvent event - the client requesting the pause will resume the app.
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 e51dd65..d6ef19f 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -112,6 +112,15 @@
},
);
+const FakeVmServiceRequest setAssetBundlePath = FakeVmServiceRequest(
+ method: '_flutter.setAssetBundlePath',
+ args: <String, Object>{
+ 'viewId': 'a',
+ 'assetDirectory': 'build/flutter_assets',
+ 'isolateId': '1',
+ }
+);
+
void main() {
final Uri testUri = Uri.parse('foo://bar');
Testbed testbed;
@@ -168,7 +177,7 @@
return UpdateFSReport(
success: true,
syncedBytes: 0,
- invalidatedSourcesCount: 0,
+ invalidatedSourcesCount: 1,
);
});
when(mockFlutterDevice.devFS).thenReturn(mockDevFS);
@@ -194,6 +203,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> onAppStart = Completer<void>.sync();
@@ -219,6 +229,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
final MockResidentCompiler residentCompiler = MockResidentCompiler();
residentRunner = HotRunner(
@@ -345,6 +356,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
final MockResidentCompiler residentCompiler = MockResidentCompiler();
residentRunner = HotRunner(
@@ -387,6 +399,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
@@ -465,6 +478,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
listViews,
]);
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
@@ -514,10 +528,66 @@
Usage: () => MockUsage(),
}));
+ testUsingContext('ResidentRunner can handle an reload-barred exception from hot reload', () => testbed.run(() async {
+ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+ listViews,
+ listViews,
+ setAssetBundlePath,
+ listViews,
+ ]);
+ 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.updateDevFS(
+ mainUri: anyNamed('mainUri'),
+ target: anyNamed('target'),
+ bundle: anyNamed('bundle'),
+ firstBuildTime: anyNamed('firstBuildTime'),
+ bundleFirstUpload: anyNamed('bundleFirstUpload'),
+ bundleDirty: anyNamed('bundleDirty'),
+ fullRestart: anyNamed('fullRestart'),
+ projectRootPath: anyNamed('projectRootPath'),
+ pathToReload: anyNamed('pathToReload'),
+ invalidatedFiles: anyNamed('invalidatedFiles'),
+ dillOutputPath: anyNamed('dillOutputPath'),
+ packageConfig: anyNamed('packageConfig'),
+ )).thenThrow(vm_service.RPCError('something bad happened', kIsolateReloadBarred, ''));
+
+ final OperationResult result = await residentRunner.restart(fullRestart: false);
+ expect(result.fatal, true);
+ expect(result.code, kIsolateReloadBarred);
+ expect(result.message, contains('Unable to hot reload application due to an unrecoverable error'));
+ verify(globals.flutterUsage.sendEvent('hot', 'reload-barred', parameters: <String, String>{
+ cdKey(CustomDimensions.hotEventTargetPlatform):
+ getNameForTargetPlatform(TargetPlatform.android_arm),
+ cdKey(CustomDimensions.hotEventSdkName): 'Example',
+ cdKey(CustomDimensions.hotEventEmulator): 'false',
+ cdKey(CustomDimensions.hotEventFullRestart): 'false',
+ cdKey(CustomDimensions.nullSafety): 'false',
+ })).called(1);
+ expect(fakeVmServiceHost.hasRemainingExpectations, false);
+ }, overrides: <Type, Generator>{
+ Usage: () => MockUsage(),
+ }));
+
testUsingContext('ResidentRunner reports hot reload event with null safety analytics', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
listViews,
]);
residentRunner = HotRunner(
@@ -582,16 +652,8 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
listViews,
- listViews,
- const FakeVmServiceRequest(
- method: '_flutter.setAssetBundlePath',
- args: <String, Object>{
- 'viewId': 'a',
- 'assetDirectory': 'build/flutter_assets',
- 'isolateId': '1',
- }
- ),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{
@@ -615,7 +677,6 @@
},
},
),
- listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
@@ -660,7 +721,7 @@
dillOutputPath: anyNamed('dillOutputPath'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async {
- return UpdateFSReport(success: true);
+ return UpdateFSReport(success: true, invalidatedSourcesCount: 1);
});
final OperationResult result = await residentRunner.restart(fullRestart: false);
@@ -675,16 +736,8 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
listViews,
- listViews,
- const FakeVmServiceRequest(
- method: '_flutter.setAssetBundlePath',
- args: <String, Object>{
- 'viewId': 'a',
- 'assetDirectory': 'build/flutter_assets',
- 'isolateId': '1',
- }
- ),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{
@@ -756,7 +809,7 @@
dillOutputPath: anyNamed('dillOutputPath'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async {
- return UpdateFSReport(success: true);
+ return UpdateFSReport(success: true, invalidatedSourcesCount: 1);
});
final OperationResult result = await residentRunner.restart(fullRestart: false);
@@ -772,15 +825,7 @@
listViews,
listViews,
listViews,
- listViews,
- const FakeVmServiceRequest(
- method: '_flutter.setAssetBundlePath',
- args: <String, Object>{
- 'viewId': 'a',
- 'assetDirectory': 'build/flutter_assets',
- 'isolateId': '1',
- }
- ),
+ setAssetBundlePath,
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{
@@ -804,7 +849,6 @@
},
},
),
- listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
@@ -856,19 +900,11 @@
),
listViews,
listViews,
- listViews,
- const FakeVmServiceRequest(
- method: '_flutter.setAssetBundlePath',
- args: <String, Object>{
- 'viewId': 'a',
- 'assetDirectory': 'build/flutter_assets',
- 'isolateId': '1',
- }
- ),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: fakeVM.toJson(),
),
+ setAssetBundlePath,
const FakeVmServiceRequest(
method: 'reloadSources',
args: <String, Object>{
@@ -884,7 +920,6 @@
},
},
),
- listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
@@ -938,7 +973,11 @@
invalidatedFiles: anyNamed('invalidatedFiles'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async {
- return UpdateFSReport(success: true, fastReassembleClassName: 'FOO');
+ return UpdateFSReport(
+ success: true,
+ fastReassembleClassName: 'FOO',
+ invalidatedSourcesCount: 1,
+ );
});
final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
@@ -969,6 +1008,7 @@
listViews,
listViews,
listViews,
+ setAssetBundlePath,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
@@ -1038,6 +1078,7 @@
listViews,
listViews,
listViews,
+ setAssetBundlePath,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
@@ -1113,6 +1154,7 @@
listViews,
listViews,
listViews,
+ setAssetBundlePath,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
@@ -1243,6 +1285,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
return 'Example';
@@ -2112,6 +2155,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
setWsAddress(testUri, fakeVmServiceHost.vmService);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
@@ -2137,6 +2181,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
setWsAddress(testUri, fakeVmServiceHost.vmService);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
@@ -2163,6 +2208,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
setWsAddress(testUri, fakeVmServiceHost.vmService);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
@@ -2197,6 +2243,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
setWsAddress(testUri, fakeVmServiceHost.vmService);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
@@ -2231,6 +2278,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
setWsAddress(testUri, fakeVmServiceHost.vmService);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
@@ -2258,6 +2306,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
setWsAddress(testUri, fakeVmServiceHost.vmService);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
@@ -2322,6 +2371,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
final MockDevicePortForwarder mockPortForwarder = MockDevicePortForwarder();
when(mockDevice.portForwarder).thenReturn(mockPortForwarder);
@@ -2353,6 +2403,7 @@
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
+ setAssetBundlePath,
]);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = HotRunner(