I'm experimenting with Dart's isolates and the VM service.
Minimal reproducible example
No external packages are required.
I have two programs:
printerwhich hosts a VM service and registers a service extension. The service extension pauses or resumes the current isolate when called.controllerwhich callsprinterservice extension
printer.dart
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:isolate';
Capability? capability;
void main() {
  var count = 0;
  () async {
    while (true) {
      print('Count: $count');
      count++;
      await Future.delayed(Duration(seconds: 1));
    }
  }();
  const String extensionName = 'ext.printer.getCount';
  developer.registerExtension('ext.printer.control', _getControlHandler);
  final String isolateId = developer.Service.getIsolateId(Isolate.current)!;
  print('Registered service extension $extensionName in $isolateId');
}
Future<developer.ServiceExtensionResponse> _getControlHandler(
  String method,
  Map<String, String> parameters,
) async {
  print('Received command: ${parameters['command']}');
  if (parameters['command'] == 'pause') {
    print('Pausing...');
    capability = Isolate.current.pause();
    print('Paused! Capability: $capability'); // This is printed
  } else if (parameters['command'] == 'resume') {
    print('Resuming...');
    Isolate.current.resume(capability!);
    capability = null;
  }
  return developer.ServiceExtensionResponse.result(jsonEncode({}));
}
To run printer:
$ dart run --enable-vm-service --serve-observatory bin/printer.dart
controller.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io' as io;
Future<void> main(List<String> args) async {
  final String webSocketUrl = args[0];
  final String isolateId = args[1];
  final io.WebSocket socket = await io.WebSocket.connect(webSocketUrl);
  socket.listen(
    (dynamic data) {
      var encoder = JsonEncoder.withIndent("  ");
      final response = encoder.convert(jsonDecode(data));
      print('Got response from ext.printer.control:\n$response');
    },
  );
  io.stdin
    ..lineMode = false
    ..echoMode = false;
  StreamSubscription? subscription;
  void onData(List<int> codes) {
    print('received: $codes');
    if (codes.first == 27) {
      // Exit on ESC key
      print('Canceling subscription and exiting...');
      subscription?.cancel();
      return;
    }
    final char = String.fromCharCode(codes.first).toUpperCase();
    if (char == 'P') {
      print('Will pause...');
      socket.add(jsonEncode({
        'jsonrpc': '2.0',
        'id': 21,
        'method': 'ext.printer.control',
        'params': {'isolateId': isolateId, 'command': 'pause'},
      }));
    } else if (char == 'R') {
      print('Will resume...');
      socket.add(jsonEncode({
        'jsonrpc': '2.0',
        'id': 37,
        'method': 'ext.printer.control',
        'params': {'isolateId': isolateId, 'command': 'resume'},
      }));
    }
  }
  subscription = io.stdin.listen(onData);
}
To run controller, you need two strings that are output when printer is run: the Dart VM service address and the main isolate ID:
# let's assume that:
#  * printer's VM service address is "http://127.0.0.1:8181/_Tm_1iNeskE=/" (http -> ws)
#  * printer's main isolate ID is "isolates/8509839127081443"
# then you call controller like this:
$ dart run bin/controller.dart ws://127.0.0.1:8181/_Tm_1iNeskE=/ isolates/8509839127081443
Main question
I start both programs and then in controller, I press "P" on keyboard to call a service extension inside printer that stops its main isolate:
printer output:
Received command: pause
Pausing...
Paused! Capability: Capability
I would expect that when I press "R" again, the printer's main isolate will be resumed – however, that does not happen. It stays paused and I was not able to bring it back to the resumed state. How can I do that?
I thought that my mistake was that I had only one isolate, and the service extension's code is executed inside that isolate - but it is paused, so no code can execute. Is this correct?
More questions
When I open the Dart Observatory for the printer program, I see there are 3 isolates:
The Dart Observatory can be opened by opening in the browser the first link output by the printer program:
$ dart run --enable-vm-service --serve-observatory bin/printer.dart The Dart VM service is listening on http://127.0.0.1:8181/_Tm_1iNeskE=/
The following questions arise:
The
mainisolate (runningbin/printer.dart) shows up as "idle" no matter if it's paused or started. Why is that?There is a
vm-serviceisolate. What is its purpose? Would it be true to say that Dart's VM service runs in a separate isolate when--enable-vm-serviceflag is passed?Is the
vm-serviceisolate the first to receive my websocket data, and then it tries to pass that data to themainisolate, but it is paused?
In general I'm trying to build a correct mental model of what exactly is happening. Thank you in advance.
