Monday, November 4, 2013

New Fangled Dart Isolates


I thought keeping Dart for Hipsters would get easier once the core of the Dart language has stabilized. That may be the case, but it seems like there are new breaking changes to important core libraries just as I get one another core library properly updated in the book. Ah well, I can't complain too much. First off, I love life on the bleeding edge with all the changes (and really, they are doing a remarkable job keeping things not too dangerous). Secondly, I wrote the book when the language was in pre-pre-pre-alpha, what did I expected?

The latest problem is the isolates section at the end of the chapter. Not two months ago, I got this section working and now it breaks again. I was just a small section of a collection of cool features that I wanted to talk about, so I could just toss it from the book. Still, let's see what the effort involved would be to keep it.

I was rather pleased to end my last experiment with a test for the book code that looked like:
    test('can send back replies', (){
      SendPort sender = spawnFunction(Isolate.findDoom);
      expect(sender.call(2013), completion("Thurs"));
    });
That is nice and compact but belies a bunch of code running in the isolate to calculate the Doomsday for a given year (well, OK, not too much code). I spawn an isolate with spawnFunction(), call into the isolate with a value of 2013, and expect to find that the Doomsday falls on a Thursday this year.

Except, instead of a nice passing test, I get:
RROR: [isolates] can send back replies
  Test failed: Caught No top-level method 'spawnFunction' declared.
  
  NoSuchMethodError : method not found: 'spawnFunction'
  Receiver: top-level
  Arguments: [...]
That is fair enough. The dart:isolate library underwent some major refactoring. It looks like I need to make some changes inside the isolate code as well, but I start by replacing the no-longer-existing spawnFunction() with Isolate.spawn() from the announcement:
    test('can send back replies', (){
      // SendPort sender = spawnFunction(Isolate.findDoom);
      var res = new ReceivePort();
      Future<Isolate> remote = Isolate.spawn(DoomsDay.findDoom, res.sendPort);
      expect(sender.call(2013), completion("Thurs"));
    });
I do not expect that will work right away, but the failure confuses the heck out of me:
ERROR: [isolates] can send back replies
  Test failed: Caught IsolateSpawnException: 'spawnFunction is not supported from a dom-enabled isolate. Please use spawnUri instead.'
So it seems that, even though I am using the new Isolate.spawn() interface, I am still going through the old spawnFunction() that used to work just fine for me. Only now I am not allowed to use it.

This test, like the bulk of the tests in Dart for Hipsters, is run in a browser context. It would seem that the only way to run a browser isolate is now Isolate.spawnUri(), which is less desirable for the book (and real world usage). I would guess that Isolate.spawnUri() only works in a browser whereas Isolate.spawn() seemingly only works on the server. So my relatively quick introduction now only covers half the use cases of Isolates, which seems less useful for readers.

Based on the discussion that followed the original announcement, it seems likely that further changes are coming, so perhaps it is best to temporarily remove the section with an eye toward reinstatement at a later time. But since I'm here already, I wonder how easy it is to use that spawnUri()....

Following along with the example, I believe that spawnUri() still needs a ReceivePort response object. That becomes the third argument to Isolate.spawnUri(), with the first being the code file itself and the second being the argument that I want to send in:
    test('can send back replies', (){
      // SendPort sender = spawnFunction(Isolate.findDoom);
      var res = new ReceivePort();
      var sender = Isolate.spawnUri(
        Uri.parse('isolates/main.dart'),
        [2013],
        res.sendPort
      );
      // Get response here...
    });
Next, I need a chain of two futures. The first future is complete when then Isolate is ready. Once the isolate is ready, then I return another future—one that will complete when the first response is ready:
    test('can send back replies', (){
      // SendPort sender = spawnFunction(Isolate.findDoom);
      var res = new ReceivePort();
      var sender = Isolate.spawnUri(
        Uri.parse('isolates/main.dart'),
        [2013],
        res.sendPort
      );
      sender.
        then((_)=> res.first).
        then(expectAsync1((message) {
          expect(message, 'Thurs');
        }));
    });
I set my asynchronous expectation inside the then() attached to the second future. When the first message is available, then I check my expectation.

The contents of my isolates/main.dart is almost anti-climatic from there. The spawnUri() method invokes main.dart with the argument list (a single argument of 2013) and the port on which the isolate can reply. I already have a “long” running dayOfDoom() function in there, so I can simply reply with the result of that function call:
main(args, replyTo) {
  replyTo.send(dayOfDoom(args[0]));
}
With that, I have my test passing again:
PASS: [isolates] can send back replies
I'm still not sure I can easily incorporate that in the book, but I have to admit that is fairly simple to use.


Day #925

3 comments:

  1. "I would guess that Isolate.spawnUri() only works in a browser "
    it works on the server too. But it's not multithreading still and not even async (at least on the server).
    Try this gist https://gist.github.com/jamm/ae279f23cbd106e1d0fd - it will wait 5 seconds before output response of spawned isolate.

    ReplyDelete
  2. In that gist I use Isolate.spawn, but spawnUri has same problem.

    ReplyDelete
    Replies
    1. Good to know, thanks!

      I think the sleep in the main isolate blocks everything in that thread. That is, I *think* the code in the spawned isolate can still run async, but since the main thread is blocked, its messages are not being received.

      It sounds similar to problems that I found starting at: http://japhr.blogspot.com/2014/07/isolated-reactors-in-dart.html

      Delete