[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.
This commit is contained in:
parent
6c6c7ba8ec
commit
c6dce2318b
@ -500,7 +500,19 @@ class HotRunner extends ResidentRunner {
|
||||
.getIsolateOrNull(view.uiIsolate.id);
|
||||
operations.add(reloadIsolate.then((vm_service.Isolate isolate) async {
|
||||
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);
|
||||
}
|
||||
}));
|
||||
|
@ -65,7 +65,17 @@ final vm_service.Isolate fakePausedIsolate = vm_service.Isolate(
|
||||
kind: vm_service.EventKind.kPauseException,
|
||||
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,
|
||||
libraries: <vm_service.LibraryRef>[],
|
||||
livePorts: 0,
|
||||
@ -968,6 +978,81 @@ void main() {
|
||||
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 {
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
||||
listViews,
|
||||
|
Loading…
x
Reference in New Issue
Block a user