Search code examples
dartdart-isolates

Correctly killing newly spawned isolates


I am aware of the fact that when both microtask and event queues of an isolate are empty, the isolate is killed. However, I'm not able to find a reference on the documentation of how a worker isolate can be killed under certain circumstances.


Context

Let's make this example:

Future<void> main() {
  final receivePort = ReceivePort();
  final worker = await Isolate.spawn<SendPort>((_) {}, receivePort.sendPort);

  await runMyProgram(receivePort, worker);
}

Here the main isolate is creating a new one (worker) and then the program starts doing stuff.


Question

How do I manually kill the newly spawned isolate when it's not needed anymore? I wasn't able to explicitly find this information on the documentation so I am kind of guessing. Do I have to do this?

 receivePort.close();
 worker.kill();

Or is it enough to just close the port, like this?

  receivePort.close();

Note

I thought about this. If the worker isolate has both queues (microtask and event) empty and I close the receive port, it should be killed automatically. If this is the case, calling receivePort.close() should be enough!


Solution

  • You can kill an isolate from the outside, using the Isolate.kill method on an Isolate object representing that isolate. (That's why you should be careful about giving away such isolate objects, and why you can create an isolate object without the "kill" capability, that you can more safely pass around.)

    You can immediately kill an isolate from the inside using the static Isolate.exit. Or using Isolate.current.kill. It's like Process.exit, but only for a single isolate.

    Or you can make sure you have closed every open receive port in the isolate, and stopped doing anything. That's the usual approach, but it can fail if you run code provided by others in your isolate. They might open receive ports or start periodic timers which run forever, and that you know nothing about. (You can try to contain that code in a Zone where you control timers, but that won't stop them from creating receive ports, and they can always access Zone.root directly to leave the zone you put them in.) Or someone might have Isolate.pauseed your isolate, so the worker code won't run.


    If I wanted to be absolutely certain that an isolate is killed, I'd start out by communicating with my own code running in that isolate (the port receiving worker instructions) and tell it to shut down nicely, as a part of the protocol I am already using to communicate. The worker code can choose to use Isolate.exit when it's done, or just close all its own resources and hope it's enough. I'd probably tend to use Isolate.exit, but only after waiting for existing worker tasks getting done. Such a worker task might be hanging (waiting for a future which will never complete). Or it might be live-locking everything by being stuck in a while (true){..can't stop, won't stop!..}. In that case, the waiting should have a timeout.

    Because of that, I'd also listen for the isolate to shut down, using Isolate.addOnExitHandler, and start a timer for some reasonable duration, and if I haven't received an "on exit" notification before the timer runs out, or some feedback on the worker shutdown request telling me that things are fine, I'd escalate to isolate.kill(priority: Isolate.immediate); which can kill even a while (true) ... loop.