Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions packages/flutter_tools/lib/src/run_hot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ class HotRunner extends ResidentRunner {
);
operations.add(
reloadIsolate.then((vm_service.Isolate? isolate) async {
if ((isolate != null) && isPauseEvent(isolate.pauseEvent!.kind!)) {
if (isolate != null) {
// 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
Expand All @@ -655,7 +655,7 @@ class HotRunner extends ResidentRunner {
// 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
// These settings do 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>>[
Expand All @@ -667,12 +667,22 @@ class HotRunner extends ResidentRunner {
device.vmService!.service.removeBreakpoint(isolate.id!, breakpoint.id!),
];
await Future.wait(breakpointAndExceptionRemoval);
await device.vmService!.service.resume(view.uiIsolate!.id!);
if (isPauseEvent(isolate.pauseEvent!.kind!)) {
await device.vmService!.service.resume(view.uiIsolate!.id!);
}
}
}),
);
}

// Wait for the UI isolates to have their breakpoints removed and exception pause mode
// cleared while also ensuring the isolate's are no longer paused. If we don't clear
// the exception pause mode before we start killing child isolates, it's possible that
// any UI isolate waiting on a result from a child isolate could throw an unhandled
// exception and re-pause the isolate, causing hot restart to hang.
await Future.wait(operations);
operations.clear();

// The engine handles killing and recreating isolates that it has spawned
// ("uiIsolates"). The isolates that were spawned from these uiIsolates
// will not be restarted, and so they must be manually killed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,14 @@ void main() {
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{})!.toJson(),
Expand Down Expand Up @@ -785,6 +793,14 @@ void main() {
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{})!.toJson(),
Expand Down Expand Up @@ -852,18 +868,28 @@ void main() {
jsonResponse: fakePausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{})!.toJson(),
),
const FakeVmServiceRequest(
method: 'setIsolatePauseMode',
args: <String, String>{'isolateId': '1', 'exceptionPauseMode': 'None'},
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().toJson(),
),
const FakeVmServiceRequest(
FakeVmServiceRequest(
method: 'removeBreakpoint',
args: <String, String>{'isolateId': '1', 'breakpointId': 'test-breakpoint'},
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'breakpointId': 'test-breakpoint',
},
),
FakeVmServiceRequest(
method: 'resume',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{})!.toJson(),
),
const FakeVmServiceRequest(method: 'resume', args: <String, String>{'isolateId': '1'}),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
Expand Down Expand Up @@ -913,6 +939,14 @@ void main() {
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{})!.toJson(),
Expand Down Expand Up @@ -940,6 +974,14 @@ void main() {
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{})!.toJson(),
Expand Down Expand Up @@ -967,6 +1009,14 @@ void main() {
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{})!.toJson(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:file/file.dart';
import 'package:vm_service/vm_service.dart';
import 'package:vm_service/vm_service_io.dart';

import '../src/common.dart';
import 'test_data/hot_restart_with_paused_child_isolate_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';

void main() {
late Directory tempDir;
final HotRestartWithPausedChildIsolateProject project = HotRestartWithPausedChildIsolateProject();
late FlutterRunTestDriver flutter;

setUp(() async {
tempDir = createResolvedTempDirectorySync('hot_restart_test.');
await project.setUpIn(tempDir);
flutter = FlutterRunTestDriver(tempDir);
});

tearDown(() async {
await flutter.stop();
tryToDelete(tempDir);
});

// Possible regression test for https://github.com/flutter/flutter/issues/161466
testWithoutContext("Hot restart doesn't hang when an unhandled exception is "
'thrown in the UI isolate', () async {
await flutter.run(withDebugger: true, startPaused: true, pauseOnExceptions: true);
final VmService vmService = await vmServiceConnectUri(flutter.vmServiceWsUri.toString());
final Isolate root = await flutter.getFlutterIsolate();

// The UI isolate has already started paused. Setup a listener for the
// child isolate that will spawn when the isolate resumes. Resume the
// spawned child which will pause on start, and then wait for it to execute
// the `debugger()` call.
final Completer<void> childIsolatePausedCompleter = Completer<void>();
vmService.onDebugEvent.listen((Event event) async {
if (event.kind == EventKind.kPauseStart) {
await vmService.resume(event.isolate!.id!);
} else if (event.kind == EventKind.kPauseBreakpoint) {
if (!childIsolatePausedCompleter.isCompleted) {
await vmService.streamCancel(EventStreams.kDebug);
childIsolatePausedCompleter.complete();
}
}
});
await vmService.streamListen(EventStreams.kDebug);

await vmService.resume(root.id!);
await childIsolatePausedCompleter.future;

// This call will fail to return if the UI isolate pauses on an unhandled
// exception due to the isolate spawned by `Isolate.run` not completing.
await flutter.hotRestart();
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'project.dart';

// Reproduction case from
// https://github.com/flutter/flutter/issues/161466#issuecomment-2743309718.
class HotRestartWithPausedChildIsolateProject extends Project {
@override
final String pubspec = '''
name: test
environment:
sdk: ^3.7.0-0

dependencies:
flutter:
sdk: flutter
''';

@override
final String main = r'''
import 'dart:async';
import 'dart:developer';
import 'dart:isolate';

import 'package:flutter/material.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized().platformDispatcher.onError = (Object error, StackTrace? stack) {
print('HERE');
return true;
};
runApp(
const Center(
child: Text(
'Hello, world!',
key: Key('title'),
textDirection: TextDirection.ltr,
),
),
);

Isolate.run(() {
print('COMPUTING');
debugger();
});
}
''';
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ abstract final class FlutterTestDriver {
final Completer<void> isolateStarted = Completer<void>();
_vmService!.onIsolateEvent.listen((Event event) {
if (event.kind == EventKind.kIsolateStart) {
isolateStarted.complete();
if (!isolateStarted.isCompleted) {
isolateStarted.complete();
}
} else if (event.kind == EventKind.kIsolateExit && event.isolate?.id == _flutterIsolateId) {
// Hot restarts cause all the isolates to exit, so we need to refresh
// our idea of what the Flutter isolate ID is.
Expand Down
Loading