Unverified Commit c6dce231 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] remove breakpoints from paused isolate on hot restart (#62069)

The embedder requires that the isolate is unpaused, because the runInView method requires interaction with dart engine APIs that are not thread-safe. These APIs must be run on the same thread that would be blocked by the pause. Simply unpausing is not sufficient, because this does not prevent the isolate from immediately hitting a breakpoint, for example if the breakpoint was placed in a loop or in a frequently called method. Instead, all breakpoints are first disabled and then the isolate resumed.
parent 6c6c7ba8
...@@ -500,7 +500,19 @@ class HotRunner extends ResidentRunner { ...@@ -500,7 +500,19 @@ class HotRunner extends ResidentRunner {
.getIsolateOrNull(view.uiIsolate.id); .getIsolateOrNull(view.uiIsolate.id);
operations.add(reloadIsolate.then((vm_service.Isolate isolate) async { operations.add(reloadIsolate.then((vm_service.Isolate isolate) async {
if ((isolate != null) && isPauseEvent(isolate.pauseEvent.kind)) { if ((isolate != null) && isPauseEvent(isolate.pauseEvent.kind)) {
// Resume the isolate so that it can be killed by the embedder. // The embedder requires that the isolate is unpaused, because the
// runInView method requires interaction with dart engine APIs that
// are not thread-safe, and thus must be run on the same thread that
// would be blocked by the pause. Simply unpausing is not sufficient,
// because this does not prevent the isolate from immediately hitting
// a breakpoint, for example if the breakpoint was placed in a loop
// or in a frequently called method. Instead, all breakpoints are first
// disabled and then the isolate resumed.
final List<Future<void>> breakpointRemoval = <Future<void>>[
for (final vm_service.Breakpoint breakpoint in isolate.breakpoints)
device.vmService.removeBreakpoint(isolate.id, breakpoint.id)
];
await Future.wait(breakpointRemoval);
await device.vmService.resume(view.uiIsolate.id); await device.vmService.resume(view.uiIsolate.id);
} }
})); }));
......
...@@ -65,7 +65,17 @@ final vm_service.Isolate fakePausedIsolate = vm_service.Isolate( ...@@ -65,7 +65,17 @@ final vm_service.Isolate fakePausedIsolate = vm_service.Isolate(
kind: vm_service.EventKind.kPauseException, kind: vm_service.EventKind.kPauseException,
timestamp: 0 timestamp: 0
), ),
breakpoints: <vm_service.Breakpoint>[], breakpoints: <vm_service.Breakpoint>[
vm_service.Breakpoint(
breakpointNumber: 123,
id: 'test-breakpoint',
location: vm_service.SourceLocation(
tokenPos: 0,
script: vm_service.ScriptRef(id: 'test-script', uri: 'lib/foo.dart'),
),
resolved: true,
),
],
exceptionPauseMode: null, exceptionPauseMode: null,
libraries: <vm_service.LibraryRef>[], libraries: <vm_service.LibraryRef>[],
livePorts: 0, livePorts: 0,
...@@ -968,6 +978,81 @@ void main() { ...@@ -968,6 +978,81 @@ void main() {
Usage: () => MockUsage(), Usage: () => MockUsage(),
})); }));
testUsingContext('ResidentRunner can remove breakpoints from paused isolate during hot restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakePausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
const FakeVmServiceRequest(
method: 'removeBreakpoint',
args: <String, String>{
'isolateId': '1',
'breakpointId': 'test-breakpoint',
}
),
const FakeVmServiceRequest(
method: 'resume',
args: <String, String>{
'isolateId': '1',
}
),
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,
));
final OperationResult result = await residentRunner.restart(fullRestart: true);
expect(result.isOk, true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async { testUsingContext('ResidentRunner Can handle an RPC exception from hot restart', () => 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