| // 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(); |
| } |