What is the best way to track the state of an Isolate in Dart?
Asked Answered
Y

2

5

I'm trying to track whether the isolate is currently running or not (and in the future whether it has errored out) using isolate.addOnExitListener(...). However, the following snippet of code is not working how I would expect:

items.forEach((name, item) async {
      Isolate isolate = await Isolate.spawnUri(...);
      item.status = "running";
      ReceivePort receivePort = new ReceivePort();
      isolate.addOnExitListener(receivePort.sendPort);
      receivePort.listen((message){
        if (message == null) {
          print("Item exited: ${item.name}");
          item.status = "stopped";
        }
      });
});

The "items" map contains 3 values, each with a distinct name: item1, item2, item3

When I run this code, the only output I get is: "Item exited: item3"

I expected the following output (not necessarily in order since isolates are asynchronous): "Item exited: item1" "Item exited: item2" "Item exited: item3"

Here is the code being run in the isolates:

import 'dart:io';
main(List args) {
  print('Hello world: standard out!');
  stderr.writeln('Hello world: standard error!');
}

It seems like the closure is being lost. Am I doing something wrong here? Is there a better way to track the state of an isolate?

Thanks in advance!

Yovonnda answered 25/3, 2015 at 4:19 Comment(1)
You should consider starting the isolate in a paused state, so you have the time to set up the onExitListener.Pocketbook
A
4

If you want to make absolutely sure that you can install onExit and onError listeners in an isolate before any of the isolate's code executes, then you can spawn the isolate paused. See documentation about spawnUri.

Here is an example:

var isolate = await Isolate.spawnUri(myUri, args, message, paused: true);
var receivePort = new ReceivePort();
isolate.addOnExitListener(receivePort.sendPort);

receivePort.listen((message){
  if (message == null) { // A null message means the isolate exited
    print("Item exited: ${item.name}");
    item.status = "stopped";
  }
});

isolate.resume(isolate.pauseCapability);

Once you have registered the appropriate listeners, you can start the newly created isolate with resume.

This is very similar to the suggestion of an initial handshake, but in this case it is builtin to the library.

Ambitendency answered 26/3, 2015 at 0:6 Comment(1)
I don't know how I missed that in the documentation! This worked perfectly.Yovonnda
A
3

I had the same behavior when the isolate didn't do anything notable (just one print statement). It seems it exited before the onExitListener was registered.

DartDoc of onExitListener says

  • If the isolate is already dead, no message will be sent.

The isolate code

import 'dart:async' show Future, Stream;

void main(List<String> args) {
  new Future.delayed(new Duration(milliseconds: 500), 
      () =>print('isolate ${args}'));
}

With the additional delay I got the desired on exit notification. The delay needs to be quite high :-(.

You can do some initial handshake to ensure the isolate doesn't exit before everything is set up properly

import 'dart:isolate';
import 'dart:async' show Future, Stream, Completer;
import 'dart:io' as io;

class Item {
  String name;
  String status;
  Item(this.name);
}

void main() {
  final items = {'a': new Item('a'), 'b': new Item('b'), 'c': new Item('c')};
  items.forEach((name, item) async {
    ReceivePort receivePort = new ReceivePort();
    SendPort sendPort = receivePort.sendPort;
    Isolate isolate = await Isolate.spawnUri(
        Uri.parse('isolate.dart'), [sendPort, name], null);
    receivePort.listen((message) {
      if (message is SendPort) {
        message.send('connected');
      } else if (message == null) {
        print("Item exited: ${item.name}");
        item.status = "stopped";
      } else {
        print("Message: ${message}");
      }
    });
    isolate.addOnExitListener(receivePort.sendPort);
    item.status = "running";
  });
}
import 'dart:isolate';

void main(List<String> args) {
  SendPort sendPort = (args[0] as SendPort);
  var receivePort = new ReceivePort();
  sendPort.send(receivePort.sendPort);
  // keeps the isolate alive at least until the first messgae arrives
  receivePort.first.then((e) => print('isolate received: $e'));
}
Amphigory answered 25/3, 2015 at 6:22 Comment(1)
It's true that dead isolates can't respond. I'd probably use the Isolate.ping method to get a response after sending the addOnExitListener event (which is what the package:isolate IsolateRunner does). If you don't get a ping response either before some unreasonable timeout (like a second), then assume that the isolate is dead. In general, send-ports are like raw IP packets - fire and forget - so for any kind of "reliable" communication you need top put a protocol like TCP on top, and at some points you just have to assume that the isolate is dead since it didn't respond in time.Palmitin

© 2022 - 2024 — McMap. All rights reserved.