I'm experimenting with Dart's isolates and the VM service.
Minimal reproducible example
No external packages are required.
I have two programs:
printer
which hosts a VM service and registers a service extension. The service extension pauses or resumes the current isolate when called.controller
which callsprinter
service 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
main
isolate (runningbin/printer.dart
) shows up as "idle" no matter if it's paused or started. Why is that?There is a
vm-service
isolate. What is its purpose? Would it be true to say that Dart's VM service runs in a separate isolate when--enable-vm-service
flag is passed?Is the
vm-service
isolate the first to receive my websocket data, and then it tries to pass that data to themain
isolate, but it is paused?
In general I'm trying to build a correct mental model of what exactly is happening. Thank you in advance.