blob: 30711b966ecd643b16a5adc546686d1b1e34b3eb [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. 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:convert";
import "dart:io";
import "package:vm_service/vm_service.dart" as vmService;
import "package:vm_service/vm_service_io.dart" as vmService;
export "package:vm_service/vm_service.dart";
export "package:vm_service/vm_service_io.dart";
class VMServiceHelper {
late vmService.VmService _serviceClient;
vmService.VmService get serviceClient => _serviceClient;
VMServiceHelper();
Future connect(Uri observatoryUri) async {
String path = observatoryUri.path;
if (!path.endsWith("/")) path += "/";
String wsUriString = 'ws://${observatoryUri.authority}${path}ws';
_serviceClient = await vmService.vmServiceConnectUri(wsUriString,
log: const StdOutLog());
}
Future disconnect() async {
await _serviceClient.dispose();
}
Future<void> resumeAllIsolates() async {
vmService.VM vm = await serviceClient.getVM();
for (vmService.IsolateRef isolate in vm.isolates!) {
try {
await serviceClient.resume(isolate.id!);
} catch (e) {
// It might exit at some point so we can't expect to get a good result.
}
}
}
Future<void> waitUntilSomeIsolatePausedAtExit() async {
while (true) {
vmService.VM vm = await serviceClient.getVM();
if (vm.isolates!.isNotEmpty) {
for (vmService.IsolateRef isolate in vm.isolates!) {
String isolateId = isolate.id!;
if (await isPausedAtExit(isolateId) == true) {
return;
}
}
}
await Future.delayed(const Duration(milliseconds: 10));
}
}
Future<bool> waitUntilPaused(String isolateId) async {
int nulls = 0;
int tries = 0;
while (true) {
tries++;
bool? result = await isPaused(isolateId);
if (result == null) {
nulls++;
if (nulls > 5) {
// We've now asked for the isolate 5 times and in all cases gotten
// `Sentinel`. Most likely things aren't working for whatever reason.
return false;
}
} else if (result) {
return true;
} else {
if (tries > 50) {
// Waited 5+ seconds --- check if some isolate is paused at start
// and resume if it is. This is for instance the case with macros.
await _resumeAllPausedAtStartIsolates();
tries = 0;
}
await Future.delayed(const Duration(milliseconds: 100));
}
}
}
Future<void> _resumeAllPausedAtStartIsolates() async {
vmService.VM vm = await serviceClient.getVM();
for (vmService.IsolateRef isolateRef in vm.isolates!) {
try {
String? id = isolateRef.id;
if (id == null) continue;
vmService.Isolate isolate = await _serviceClient.getIsolate(id);
if (isolate.pauseEvent?.kind == "PauseStart") {
print("Found isolate paused at start - resuming it.");
await serviceClient.resume(id);
}
} catch (e) {
// It might exit at some point so we can't expect to get a good result.
}
}
}
Future<bool?> isPaused(String isolateId) async {
vmService.Isolate isolate = await _serviceClient.getIsolate(isolateId);
String? kind = isolate.pauseEvent!.kind;
if (kind != "Resume" && kind != "None") return true;
return false;
}
Future<bool> isPausedAtStart(String isolateId) async {
dynamic tmp = await _serviceClient.getIsolate(isolateId);
if (tmp is vmService.Isolate) {
vmService.Isolate isolate = tmp;
return isolate.pauseEvent!.kind == "PauseStart";
}
return false;
}
Future<bool> isPausedAtExit(String isolateId) async {
vmService.Isolate isolate = await _serviceClient.getIsolate(isolateId);
return isolate.pauseEvent!.kind == "PauseExit";
}
Future<vmService.AllocationProfile> forceGC(String isolateId) async {
await waitUntilIsolateIsRunnable(isolateId);
int expectGcAfter = new DateTime.now().millisecondsSinceEpoch;
while (true) {
vmService.AllocationProfile allocationProfile;
try {
allocationProfile =
await _serviceClient.getAllocationProfile(isolateId, gc: true);
} catch (e) {
print(e.runtimeType);
rethrow;
}
if (allocationProfile.dateLastServiceGC != null &&
allocationProfile.dateLastServiceGC! >= expectGcAfter) {
return allocationProfile;
}
}
}
Future<bool?> isIsolateRunnable(String isolateId) async {
dynamic tmp = await _serviceClient.getIsolate(isolateId);
if (tmp is vmService.Isolate) {
vmService.Isolate isolate = tmp;
return isolate.runnable;
}
return null;
}
Future<void> waitUntilIsolateIsRunnable(String isolateId) async {
int nulls = 0;
while (true) {
bool? result = await isIsolateRunnable(isolateId);
if (result == null) {
nulls++;
if (nulls > 5) {
// We've now asked for the isolate 5 times and in all cases gotten
// `Sentinel`. Most likely things aren't working for whatever reason.
return;
}
} else if (result) {
return;
} else {
await Future.delayed(const Duration(milliseconds: 100));
}
}
}
Future<String> getIsolateId() async {
vmService.VM vm = await _serviceClient.getVM();
if (vm.isolates!.length != 1) {
throw "Expected 1 isolate, got ${vm.isolates!.length}";
}
vmService.IsolateRef isolateRef = vm.isolates!.single;
return isolateRef.id!;
}
}
class StdOutLog implements vmService.Log {
const StdOutLog();
@override
void severe(String message) {
print("> SEVERE: $message");
}
@override
void warning(String message) {
print("> WARNING: $message");
}
}
abstract class LaunchingVMServiceHelper extends VMServiceHelper {
late Process _process;
Process get process => _process;
bool _started = false;
Future<void> start(
List<String> scriptAndArgs, {
void Function(String line)? stdoutReceiver,
void Function(String line)? stderrReceiver,
bool pauseIsolateOnStart = true,
}) async {
if (_started) throw "Already started";
_started = true;
_process = await Process.start(Platform.resolvedExecutable, [
if (pauseIsolateOnStart) "--pause_isolates_on_start",
"--enable-vm-service=0",
...scriptAndArgs
]);
_process.stdout
.transform(utf8.decoder)
.transform(new LineSplitter())
.listen((line) {
const kDartVMServiceListening = 'The Dart VM service is listening on ';
if (line.startsWith(kDartVMServiceListening)) {
Uri observatoryUri =
Uri.parse(line.substring(kDartVMServiceListening.length));
_setupAndRun(observatoryUri).catchError((e, st) {
// Manually kill the process or it will leak,
// see http://dartbug.com/42918
killProcess();
// This seems to rethrow.
throw e;
});
}
if (stdoutReceiver != null) {
stdoutReceiver(line);
} else {
stdout.writeln("> $line");
}
});
_process.stderr
.transform(utf8.decoder)
.transform(new LineSplitter())
.listen((line) {
if (stderrReceiver != null) {
stderrReceiver(line);
} else {
stderr.writeln("> $line");
}
});
// ignore: unawaited_futures
_process.exitCode.then((value) {
processExited(value);
});
}
Future<void> startWithoutRunning(Uri observatoryUri) async {
if (_started) throw "Already started";
_started = true;
await _setupAndRun(observatoryUri);
}
void processExited(int exitCode) {}
void killProcess() {
_process.kill();
}
Future _setupAndRun(Uri observatoryUri) async {
await connect(observatoryUri);
await run();
}
Future<void> run();
}