mirror of
https://github.com/flutter/flutter
synced 2024-10-04 15:29:52 +00:00
Disable pause-on-exceptions for (outgoing) isolates during hot restart (#93411)
This commit is contained in:
parent
e4b78ffc43
commit
99e85b1c5f
|
@ -603,14 +603,19 @@ class HotRunner extends ResidentRunner {
|
|||
// are not thread-safe, and thus must be run on the same thread that
|
||||
// would be blocked by the pause. Simply un-pausing 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>>[
|
||||
// a breakpoint (for example if the breakpoint was placed in a loop
|
||||
// or in a frequently called method) or an exception. Instead, all
|
||||
// breakpoints are first disabled and exception pause mode set to
|
||||
// None, and then the isolate resumed.
|
||||
// These settings to not need restoring as Hot Restart results in
|
||||
// new isolates, which will be configured by the editor as they are
|
||||
// started.
|
||||
final List<Future<void>> breakpointAndExceptionRemoval = <Future<void>>[
|
||||
device.vmService.service.setExceptionPauseMode(isolate.id, 'None'),
|
||||
for (final vm_service.Breakpoint breakpoint in isolate.breakpoints)
|
||||
device.vmService.service.removeBreakpoint(isolate.id, breakpoint.id)
|
||||
];
|
||||
await Future.wait(breakpointRemoval);
|
||||
await Future.wait(breakpointAndExceptionRemoval);
|
||||
await device.vmService.service.resume(view.uiIsolate.id);
|
||||
}
|
||||
}));
|
||||
|
|
|
@ -906,7 +906,7 @@ void main() {
|
|||
Usage: () => TestUsage(),
|
||||
}));
|
||||
|
||||
testUsingContext('ResidentRunner can remove breakpoints from paused isolate during hot restart', () => testbed.run(() async {
|
||||
testUsingContext('ResidentRunner can remove breakpoints and exception-pause-mode from paused isolate during hot restart', () => testbed.run(() async {
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
||||
listViews,
|
||||
listViews,
|
||||
|
@ -922,6 +922,13 @@ void main() {
|
|||
method: 'getVM',
|
||||
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
|
||||
),
|
||||
const FakeVmServiceRequest(
|
||||
method: 'setExceptionPauseMode',
|
||||
args: <String, String>{
|
||||
'isolateId': '1',
|
||||
'mode': 'None',
|
||||
}
|
||||
),
|
||||
const FakeVmServiceRequest(
|
||||
method: 'removeBreakpoint',
|
||||
args: <String, String>{
|
||||
|
|
|
@ -187,6 +187,39 @@ void main() {
|
|||
|
||||
await dap.client.terminate();
|
||||
});
|
||||
|
||||
testWithoutContext('can hot restart when exceptions occur on outgoing isolates', () async {
|
||||
final BasicProjectThatThrows _project = BasicProjectThatThrows();
|
||||
await _project.setUpIn(tempDir);
|
||||
|
||||
// Launch the app and wait for it to stop at an exception.
|
||||
int originalThreadId, newThreadId;
|
||||
await Future.wait(<Future<Object>>[
|
||||
// Capture the thread ID of the stopped thread.
|
||||
dap.client.stoppedEvents.first.then((StoppedEventBody event) => originalThreadId = event.threadId),
|
||||
dap.client.start(
|
||||
exceptionPauseMode: 'All', // Ensure we stop on all exceptions
|
||||
launch: () => dap.client.launch(
|
||||
cwd: _project.dir.path,
|
||||
toolArgs: <String>['-d', 'flutter-tester'],
|
||||
),
|
||||
),
|
||||
], eagerError: true);
|
||||
|
||||
// Hot restart, ensuring it completes and capturing the ID of the new thread
|
||||
// to pause.
|
||||
await Future.wait(<Future<Object>>[
|
||||
// Capture the thread ID of the newly stopped thread.
|
||||
dap.client.stoppedEvents.first.then((StoppedEventBody event) => newThreadId = event.threadId),
|
||||
dap.client.hotRestart(),
|
||||
], eagerError: true);
|
||||
|
||||
// We should not have stopped on the original thread, but the new thread
|
||||
// from after the restart.
|
||||
expect(newThreadId, isNot(equals(originalThreadId)));
|
||||
|
||||
await dap.client.terminate();
|
||||
});
|
||||
}
|
||||
|
||||
/// Extracts the output from a set of [OutputEventBody], removing any
|
||||
|
|
|
@ -56,6 +56,10 @@ class DapTestClient {
|
|||
Stream<OutputEventBody> get outputEvents => events('output')
|
||||
.map((Event e) => OutputEventBody.fromJson(e.body! as Map<String, Object?>));
|
||||
|
||||
/// Returns a stream of [StoppedEventBody] events.
|
||||
Stream<StoppedEventBody> get stoppedEvents => events('stopped')
|
||||
.map((Event e) => StoppedEventBody.fromJson(e.body! as Map<String, Object?>));
|
||||
|
||||
/// Returns a stream of the string output from [OutputEventBody] events.
|
||||
Stream<String> get output => outputEvents.map((OutputEventBody output) => output.output);
|
||||
|
||||
|
@ -172,10 +176,11 @@ class DapTestClient {
|
|||
Future<void> start({
|
||||
String? program,
|
||||
String? cwd,
|
||||
String exceptionPauseMode = 'None',
|
||||
Future<Object?> Function()? launch,
|
||||
}) {
|
||||
return Future.wait(<Future<Object?>>[
|
||||
initialize(),
|
||||
initialize(exceptionPauseMode: exceptionPauseMode),
|
||||
launch?.call() ?? this.launch(program: program, cwd: cwd),
|
||||
], eagerError: true);
|
||||
}
|
||||
|
@ -201,7 +206,7 @@ class DapTestClient {
|
|||
} else {
|
||||
completer.completeError(message);
|
||||
}
|
||||
} else if (message is Event) {
|
||||
} else if (message is Event && !_eventController.isClosed) {
|
||||
_eventController.add(message);
|
||||
|
||||
// When we see a terminated event, close the event stream so if any
|
||||
|
|
|
@ -53,6 +53,68 @@ class BasicProject extends Project {
|
|||
int get topLevelFunctionBreakpointLine => lineContaining(main, '// TOP LEVEL BREAKPOINT');
|
||||
}
|
||||
|
||||
/// A project that throws multiple exceptions during Widget builds.
|
||||
///
|
||||
/// A repro for the issue at https://github.com/Dart-Code/Dart-Code/issues/3448
|
||||
/// where Hot Restart could become stuck on exceptions and never complete.
|
||||
class BasicProjectThatThrows extends Project {
|
||||
|
||||
@override
|
||||
final String pubspec = '''
|
||||
name: test
|
||||
environment:
|
||||
sdk: ">=2.12.0-0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
''';
|
||||
|
||||
@override
|
||||
final String main = r'''
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void a() {
|
||||
throw Exception('a');
|
||||
}
|
||||
|
||||
void b() {
|
||||
try {
|
||||
a();
|
||||
} catch (e) {
|
||||
throw Exception('b');
|
||||
}
|
||||
}
|
||||
|
||||
void c() {
|
||||
try {
|
||||
b();
|
||||
} catch (e) {
|
||||
throw Exception('c');
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(App());
|
||||
}
|
||||
|
||||
class App extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
c();
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Study Flutter',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.green,
|
||||
),
|
||||
home: Container(),
|
||||
);
|
||||
}
|
||||
}
|
||||
''';
|
||||
}
|
||||
|
||||
class BasicProjectWithTimelineTraces extends Project {
|
||||
@override
|
||||
final String pubspec = '''
|
||||
|
|
Loading…
Reference in a new issue