Dart Isolates

By Christian Grobmeier

Dart is very young but also a very promising language. You can write it similar to JavaScript, but it can also look like Java. In JavaScript there is no real multithreading. Just the Chrome Browser currently does some magic and spawns multiple threads for some tasks. But a developer cannot rely “maybe” and “probably”.

Dart fixes the situation. With Dart you can have some kind of multithreading. What Java has with Threads, Dart has with so called “Isolates”. Isolates are very cool. They have been highly inspired by Erlang and it seems there is a collaboration between Erlang people and the Dart devs.

To create Isolates you need to know about the following classes:

Isolates are fully independent from each other. It means, they do not share any variables – no static and no members or anything else. You can only communicate between Isolates through so called ports. This has a good benefit – you might be able to restart parts of your program if something goes wrong. But you need to take care on the two flavors of Isolates explained later.

An Isolate lives as long as its ports are open. If you close the port, the Isolate might go away. You cannot check if it actually goes away from within Dart at the moment, you’ll need to trust Dart VM.

Let’s look at it more in detail now. Our plan is to let one class “Starter” start another Isolate named “Worker”.

Creating the worker class – extending Isolate

The Worker class is actually pretty simple. It looks like that:

class Worker extends Isolate {
	Worker() : super.heavy();

	main() {
		this.port.receive(
        	void _(var message, SendPort replyTo) {
        		print ("Worker receives: ${message}");
        		replyTo.send("Pong");
				this.port.close();
	        }
	    );
	}
}

As you can see, the Worker class extends Isolate. In the next line we call the super constructor. In fact it is a named constructor called “heavy”. I will care later on the difference between “light” and “heavy” Isolates.

Once an Isolate starts its service as an own thread (it is spawned – look at the Starter class below how this is done) the inherited field “port” get populated with an ReceivePort. It means, the Isolate can get messages from another Isolate through this port.
Therefore it might be rather important to add a function to “receive()”. Otherwise you will just run the main() method and thats it. In worst case you even leave the this.port open and that newly created Isolate will never go away.

Therefore it might be a good idea to put just necessary initialization code into the main() method and then implement a method which is called on receive of a message. In addition this method needs to take care on closing it’s port.

This is actually what I have done in my Worker class. You can see the main() method and that I call receive() with an anonymous function. From now on this function will be called when a message arrives.

Looking at my anonymous function you see I get the “message” param, which might be anything: it is dynamic. Second parameter is a SendPort. Actually this gives you the chance to respond to the Caller. I actually do this, sending a “Pong” message back.

So this was pretty straightforward. Now let’s look on the other class, which should spawn the worker.

Creating the Starter class

Here is the code:

class Starter {
	ReceivePort _receivePort;

	Starter.start() : 
		_receivePort = new ReceivePort() {
			this._receivePort.receive(
				void _(var message, SendPort replyTo) {
	        		print ("Receiving from Worker: ${message}");
	        		_receivePort.close();
		        }
			);

			Worker worker = new Worker();
			worker.spawn().then((SendPort port) {
				port.send('Ping', _receivePort.toSendPort());
		});		
	}
}

OK, looks a bit more complicated at first glance, but it isn’t. I created a Standard class called “Starter” with a field _receivePort. I want to use it to get some responses from the Isolates I create from here.

Then I define my named Constructor “start()” and initialize the ReceivePort in line 5. Similar to the Worker class, I put a function into my ReceivePort which should be called once I get a response to this port. In fact, the “Pong” message from the Worker will arrive here.

In line 13 I create a new Object of the Worker. No magic, nothing happens. The actual Worker becomes a true Isolate when spawn() is called in line 14. After the spawn has created the new Isolate, “then()” will be called with a SendPort. Surprise – the SendPort is the port which has been populated by Dart in Worker.port.

Now you can use this SendPort to send a message. I do this in line 15. I send the message and need to give a SendPort to my Worker. ReceiverPort fortunately has the method toSendPort() which creates the backchannel.

Then you are done – oh wait, lets start it.

main() {
	new Starter.start();
}

Now you you’ll see the outcome. The Program should exit after the messages have been sent around. If it doesn’t you have probably open ports around.

Difference between light and heavy Isolates

If you create an Isolate, you have the choice between “light” and “heavy”. The first one is the default. So, what is the difference? Actually both start an Isolate with fresh static state and both can only communicate via ports. And, best of all, both flavors are asynchronous.

The difference is, light Isolates live in the same thread as the creating Isolate. One could say, this is basically like in JavaScript. It means only one execution can happen at one time. If there is an message, then it will be enqueued until the currently running Isolate has finished and the next one can put it from the Queue.

Heavy Isolates on the other hand create a real new Thread. It is an actual process and now things can be done in parallel. After all even Heavy Isolates get messages in their queue, but they work on the next message only after they have finished the current loop. This is appealing, because you don’t need to think about threading concurrency issues.
On the other hand, if you need some performance, you probably need some more Isolates. But spawning a new process does always have some kind of performance cost. It might make sense for some applications on the server side to create an Isolate pool when the VM starts.

And hey, you should know about some more stuff, before using it…

Sidenotes

Dart is very young and you can expect changes in the area of Isolates. At the moment difference between light and heavy is true for DartC (and only if Webworkers are available).

At the VM side every Isolate will be created as a new native thread. In other terms, there is no difference between light and heavy threads.

As mentioned, there is already some discussion on the dev side about Isolates. It might happen that light and heavy will disappear completely and something else will replace it. Even the idea of using a threadpool for light Isolates exists (very appealing, honestly).

Postscriptum: Thanks very much to the friendly Google Devs who helped me understand Isolates better!


Follow me on Twitter :-)

  • http://instantiatorgratification.blogspot.com Lewis

    This has been very helpful – thank you! Planning my first foray into Dart quite soon.

  • Pingback: Dart is on Chromium – and Dart meets Scala « Grobmeier on Dart, Java, Struts, PHP and more

  • Pingback: 10 reasons why Dart is cooler than JavaScript « Grobmeier on Dart, Java, Struts, PHP and more

  • Ali

    Hi,

    Thanks, this was very helpful. There is something I do not quite understand though. The receive commands are non-blocking (meaning that it is not like Erlang that a receive command blocks right there until it receives some conforming message), am I right? So, it confuses me as sometimes it works and sometimes it does now work. For example, I built an isolates that awaits two messages and prints out whenever it receives one, and later on I built two isolates that each of them send one message that isolate, but nothing get prints out, and I am wondering what is happening.

  • http://www.grobmeier.de Christian Grobmeier

    Ali, nonblocking vs blocking: it depends. When you generate JS out of it, it is blocking (like in JavaScript). On VM side it should not be blocking if you use heavy isolates. On your error, not sure about it: have you kept the port to the isolate open? If you have some code I might have some time next week to look at it. Probably you can you find some inspiration here: https://github.com/grobmeier/hydart (which is the example above).

    Please note, there are currently heavy changes underway in the Isolates are. What you see in this post will not work anymore in pretty near future. For example, heavy/light Isolates seem to go away, and the implementatio of Isolates do not depend on class extension anymore.

  • Ali

    Hi Christian,

    Thank you for the reply. Well, based on the example you have discussed here, in the “Starter.start()”, the receive command comes before spawning Worker, right? So, if receive is blocking, then it is never going to proceed unless some other process sends a message to it. If I write this code in Erlang, I would first spawn and then do the receiving. Beside this, something that I want to do and I do not know how to do it, is when I want to spawn two new processes and make sure that the first process is actually created first (i.e. Wait until first process is up and running, and then I spawn the second one). Right now, if I just go ahead and spawn two process, there is no guarantee that the one that comes first in the code is actually the one that gets up and running. Sorry, I wen too long:), Thanks for your time.

  • Pingback: Darts new Isolate API #dartlang « Grobmeier on Dart, Java, Struts, PHP and more