Unverified Commit 6d360562 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] alternate the name of the dill file used for hot restart (#65435)

* [flutter_tools] alternate the name of the dill file used for hot restart

* switch alternative name to .swap
parent b4551e31
......@@ -702,7 +702,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
fullRestart: fullRestart,
dillOutputPath: dillOutputPath,
projectRootPath: projectRootPath,
pathToReload: getReloadPath(fullRestart: fullRestart),
pathToReload: getReloadPath(fullRestart: fullRestart, swap: false),
invalidatedFiles: invalidationResult.uris,
packageConfig: invalidationResult.packageConfig,
trackWidgetCreation: debuggingOptions.buildInfo.trackWidgetCreation,
......
......@@ -822,7 +822,15 @@ abstract class ResidentRunner {
}
String get dillOutputPath => _dillOutputPath ?? globals.fs.path.join(artifactDirectory.path, 'app.dill');
String getReloadPath({ bool fullRestart }) => mainPath + (fullRestart ? '' : '.incremental') + '.dill';
String getReloadPath({
bool fullRestart = false,
@required bool swap,
}) {
if (!fullRestart) {
return '$mainPath.incremental.dill';
}
return '$mainPath${swap ? '.swap' : ''}.dill';
}
bool get debuggingEnabled => debuggingOptions.debuggingEnabled;
bool get isRunningDebug => debuggingOptions.buildInfo.isDebug;
......
......@@ -90,6 +90,20 @@ class HotRunner extends ResidentRunner {
final bool benchmarkMode;
final File applicationBinary;
final bool hostIsIde;
/// When performing a hot restart, the tool needs to upload a new main.dart.dill to
/// each attached device's devfs. Replacing the existing file is not safe and does
/// not work at all on the windows embedder, because the old dill file will still be
/// memory-mapped by the embedder. To work around this issue, the tool will alternate
/// names for the uploaded dill, sometimes inserting `.swap`. Since the active dill will
/// never be replaced, there is no risk of writing the file while the embedder is attempting
/// to read from it. This also avoids filling up the devfs, if a incrementing counter was
/// used instead.
///
/// This is only used for hot restart, incremental dills uploaded as part of the hot
/// reload process do not have this issue.
bool _swap = false;
bool _didAttach = false;
final Map<String, List<int>> benchmarkData = <String, List<int>>{};
......@@ -396,7 +410,7 @@ class HotRunner extends ResidentRunner {
bundleDirty: !isFirstUpload && rebuildBundle,
fullRestart: fullRestart,
projectRootPath: projectRootPath,
pathToReload: getReloadPath(fullRestart: fullRestart),
pathToReload: getReloadPath(fullRestart: fullRestart, swap: _swap),
invalidatedFiles: invalidationResult.uris,
packageConfig: invalidationResult.packageConfig,
dillOutputPath: dillOutputPath,
......@@ -544,7 +558,7 @@ class HotRunner extends ResidentRunner {
}
await Future.wait(operations);
await _launchFromDevFS(mainPath + '.dill');
await _launchFromDevFS('$mainPath${_swap ? '.swap' : ''}.dill');
restartTimer.stop();
globals.printTrace('Hot restart performed in ${getElapsedAsMilliseconds(restartTimer.elapsed)}.');
_addBenchmarkData('hotRestartMillisecondsToFrame',
......@@ -570,6 +584,9 @@ class HotRunner extends ResidentRunner {
}
await Future.wait(isolateNotifications);
}
// Toggle the main dill name after successfully uploading.
_swap =! _swap;
return OperationResult.ok;
}
......@@ -809,7 +826,7 @@ class HotRunner extends ResidentRunner {
Map<String, dynamic> firstReloadDetails;
try {
final String entryPath = globals.fs.path.relative(
getReloadPath(fullRestart: false),
getReloadPath(fullRestart: false, swap: _swap),
from: projectRootPath,
);
final List<Future<DeviceReloadReport>> allReportsFutures = <Future<DeviceReloadReport>>[];
......
......@@ -1102,6 +1102,137 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner will alternative the name of the dill file uploaded for a hot restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
},
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'lib/main.dart.dill',
'assetDirectory': 'build/flutter_assets',
},
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
),
),
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
},
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'lib/main.dart.swap.dill',
'assetDirectory': 'build/flutter_assets',
},
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
),
),
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
},
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'lib/main.dart.dill',
'assetDirectory': 'build/flutter_assets',
},
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
),
)
]);
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;
});
when(mockDevice.supportsHotRestart).thenReturn(true);
final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> onAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: onAppStart,
connectionInfoCompleter: onConnectionInfo,
));
await residentRunner.restart(fullRestart: true);
await residentRunner.restart(fullRestart: true);
await residentRunner.restart(fullRestart: true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
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