mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Service extensions are unable to handle requests when the isolate they were registered on is paused. The DevTools launcher logic was waiting for some service extension invocations to complete before advertising the already active DevTools instance, but when --start-paused was provided these requests would never complete, preventing users from using DevTools to resume the paused isolate. Fixes https://github.com/flutter/flutter/issues/126691
240 lines
6.6 KiB
Dart
240 lines
6.6 KiB
Dart
// 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 'base/file_system.dart';
|
|
import 'base/logger.dart';
|
|
import 'build_info.dart';
|
|
import 'globals.dart' as globals;
|
|
import 'resident_runner.dart';
|
|
import 'tracing.dart';
|
|
import 'vmservice.dart';
|
|
|
|
const String kFlutterTestOutputsDirEnvName = 'FLUTTER_TEST_OUTPUTS_DIR';
|
|
class ColdRunner extends ResidentRunner {
|
|
ColdRunner(
|
|
super.flutterDevices, {
|
|
required super.target,
|
|
required super.debuggingOptions,
|
|
this.traceStartup = false,
|
|
this.awaitFirstFrameWhenTracing = true,
|
|
this.applicationBinary,
|
|
this.multidexEnabled = false,
|
|
bool super.ipv6 = false,
|
|
super.stayResident,
|
|
super.machine,
|
|
super.devtoolsHandler,
|
|
}) : super(
|
|
hotMode: false,
|
|
);
|
|
|
|
final bool traceStartup;
|
|
final bool awaitFirstFrameWhenTracing;
|
|
final File? applicationBinary;
|
|
final bool multidexEnabled;
|
|
bool _didAttach = false;
|
|
|
|
@override
|
|
bool get canHotReload => false;
|
|
|
|
@override
|
|
Logger get logger => globals.logger;
|
|
|
|
@override
|
|
FileSystem get fileSystem => globals.fs;
|
|
|
|
@override
|
|
Future<int> run({
|
|
Completer<DebugConnectionInfo>? connectionInfoCompleter,
|
|
Completer<void>? appStartedCompleter,
|
|
bool enableDevTools = false,
|
|
String? route,
|
|
}) async {
|
|
try {
|
|
for (final FlutterDevice? device in flutterDevices) {
|
|
final int result = await device!.runCold(
|
|
coldRunner: this,
|
|
route: route,
|
|
);
|
|
if (result != 0) {
|
|
appFailedToStart();
|
|
return result;
|
|
}
|
|
}
|
|
} on Exception catch (err, stack) {
|
|
globals.printError('$err\n$stack');
|
|
appFailedToStart();
|
|
return 1;
|
|
}
|
|
|
|
// Connect to the VM Service.
|
|
if (debuggingEnabled) {
|
|
try {
|
|
await connectToServiceProtocol(allowExistingDdsInstance: false);
|
|
} on Exception catch (exception) {
|
|
globals.printError(exception.toString());
|
|
appFailedToStart();
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
if (debuggingEnabled) {
|
|
if (enableDevTools) {
|
|
// The method below is guaranteed never to return a failing future.
|
|
unawaited(residentDevtoolsHandler!.serveAndAnnounceDevTools(
|
|
devToolsServerAddress: debuggingOptions.devToolsServerAddress,
|
|
flutterDevices: flutterDevices,
|
|
isStartPaused: debuggingOptions.startPaused,
|
|
));
|
|
}
|
|
if (debuggingOptions.serveObservatory) {
|
|
await enableObservatory();
|
|
}
|
|
}
|
|
|
|
if (flutterDevices.first.vmServiceUris != null) {
|
|
// For now, only support one debugger connection.
|
|
connectionInfoCompleter?.complete(DebugConnectionInfo(
|
|
httpUri: flutterDevices.first.vmService!.httpAddress,
|
|
wsUri: flutterDevices.first.vmService!.wsAddress,
|
|
));
|
|
}
|
|
|
|
globals.printTrace('Application running.');
|
|
|
|
for (final FlutterDevice? device in flutterDevices) {
|
|
if (device!.vmService == null) {
|
|
continue;
|
|
}
|
|
await device.initLogReader();
|
|
globals.printTrace('Connected to ${device.device!.name}');
|
|
}
|
|
|
|
if (traceStartup) {
|
|
// Only trace startup for the first device.
|
|
final FlutterDevice device = flutterDevices.first;
|
|
if (device.vmService != null) {
|
|
globals.printStatus('Tracing startup on ${device.device!.name}.');
|
|
final String outputPath = globals.platform.environment[kFlutterTestOutputsDirEnvName] ?? getBuildDirectory();
|
|
await downloadStartupTrace(
|
|
device.vmService!,
|
|
awaitFirstFrame: awaitFirstFrameWhenTracing,
|
|
logger: globals.logger,
|
|
output: globals.fs.directory(outputPath),
|
|
);
|
|
}
|
|
appFinished();
|
|
}
|
|
|
|
appStartedCompleter?.complete();
|
|
|
|
writeVmServiceFile();
|
|
|
|
if (stayResident && !traceStartup) {
|
|
return waitForAppToFinish();
|
|
}
|
|
await cleanupAtFinish();
|
|
return 0;
|
|
}
|
|
|
|
@override
|
|
Future<int> attach({
|
|
Completer<DebugConnectionInfo>? connectionInfoCompleter,
|
|
Completer<void>? appStartedCompleter,
|
|
bool allowExistingDdsInstance = false,
|
|
bool enableDevTools = false,
|
|
bool needsFullRestart = true,
|
|
}) async {
|
|
_didAttach = true;
|
|
try {
|
|
await connectToServiceProtocol(
|
|
getSkSLMethod: writeSkSL,
|
|
allowExistingDdsInstance: allowExistingDdsInstance,
|
|
);
|
|
} on Exception catch (error) {
|
|
globals.printError('Error connecting to the service protocol: $error');
|
|
return 2;
|
|
}
|
|
|
|
for (final FlutterDevice? device in flutterDevices) {
|
|
await device!.initLogReader();
|
|
}
|
|
for (final FlutterDevice? device in flutterDevices) {
|
|
final List<FlutterView> views = await device!.vmService!.getFlutterViews();
|
|
for (final FlutterView view in views) {
|
|
globals.printTrace('Connected to $view.');
|
|
}
|
|
}
|
|
|
|
if (debuggingEnabled) {
|
|
if (enableDevTools) {
|
|
// The method below is guaranteed never to return a failing future.
|
|
unawaited(residentDevtoolsHandler!.serveAndAnnounceDevTools(
|
|
devToolsServerAddress: debuggingOptions.devToolsServerAddress,
|
|
flutterDevices: flutterDevices,
|
|
isStartPaused: debuggingOptions.startPaused,
|
|
));
|
|
}
|
|
if (debuggingOptions.serveObservatory) {
|
|
await enableObservatory();
|
|
}
|
|
}
|
|
|
|
appStartedCompleter?.complete();
|
|
if (stayResident) {
|
|
return waitForAppToFinish();
|
|
}
|
|
await cleanupAtFinish();
|
|
return 0;
|
|
}
|
|
|
|
@override
|
|
Future<void> cleanupAfterSignal() async {
|
|
await stopEchoingDeviceLog();
|
|
if (_didAttach) {
|
|
appFinished();
|
|
}
|
|
await exitApp();
|
|
}
|
|
|
|
@override
|
|
Future<void> cleanupAtFinish() async {
|
|
for (final FlutterDevice? flutterDevice in flutterDevices) {
|
|
await flutterDevice!.device!.dispose();
|
|
}
|
|
|
|
await residentDevtoolsHandler!.shutdown();
|
|
await stopEchoingDeviceLog();
|
|
}
|
|
|
|
@override
|
|
void printHelp({ required bool details }) {
|
|
globals.printStatus('Flutter run key commands.');
|
|
if (details) {
|
|
printHelpDetails();
|
|
commandHelp.hWithDetails.print();
|
|
} else {
|
|
commandHelp.hWithoutDetails.print();
|
|
}
|
|
if (_didAttach) {
|
|
commandHelp.d.print();
|
|
}
|
|
commandHelp.c.print();
|
|
commandHelp.q.print();
|
|
printDebuggerList();
|
|
}
|
|
|
|
@override
|
|
Future<void> preExit() async {
|
|
for (final FlutterDevice? device in flutterDevices) {
|
|
// If we're running in release mode, stop the app using the device logic.
|
|
if (device!.vmService == null) {
|
|
await device.device!.stopApp(device.package, userIdentifier: device.userIdentifier);
|
|
}
|
|
}
|
|
await super.preExit();
|
|
}
|
|
}
|