[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.
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 c0dc958..ce491f2 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -65,7 +65,17 @@
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 @@
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,