Thursday, January 5, 2012

Isolate Doomsday in Dart

‹prev | My Chain | next›

Tonight I continue my efforts to wrap my brain around some of the more interesting features of Dart. Having looked at Futures and ReceivePort / SendPort pairs, I think I am ready to try my hand at Isolates.

Now exactly what an Isolate is, I am not sure. The class documentation for Isolate only says that it spawns new Isolates and is an entry point for the newly spawned Isolate. Until I am disabused of the idea, I plan to think of Isolates as separate processes with their own objects and variables. Furthermore, I am going to assume that they are entirely new processes and do not have access to the calling context. That is, if main() defines a variable foo, then Isolates spawned should not be able to access the value of foo. Let's see if that is accurate (it's not quite right, see below).

For the purpose of exploration, I am going to calculate the "doomsday" of a particular year. The Doomsday isn't all that ominous—if you know the doomsday, then you know the day of week of any date in that year (e.g. if doomsday is Monday, then Dec 12, October 10, August 8, June 6, April 4, November 7, July 11, September 5, and May 9 are all Mondays). It is a trivial computation, so I will not be taxing processes—just exploring.

The doomsday is generally identified as the last day of February. I can calculate that by finding March 1, and then subtracting one day:
main() {
  var year = 2012
    , march1 = new Date(year, 3, 1, 0, 0, 0, 0)
    , doom = march1.subtract(new Duration(1)).weekday
    , dow = ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun']
    , doom_dow = dow[doom];

  print("Doom in $year is on a $doom_dow.");
}
(try.dartlang.org)

It is so nice to be able to work with dates. That is a definite win for Dart. But, hopefully not the only one...

Isolates are sub-classes of Isolate. They need to define a main() method that will be invoked once a ReceivePort has been created (taken care of by spawn()). In this case, I define a DoomsDay isolate that receives a year inside a message and returns the associated Doomsday:
class DoomsDay extends Isolate {
  int _year;

  main () {
    port.receive((message, replyTo) {
      _year = message['year'];
      replyTo.send(this.day());
    });
  }

  day() {
    var march1 = new Date(_year, 3, 1, 0, 0, 0, 0)
      , doom = march1.subtract(new Duration(1)).weekday
      , dow = ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun'];

    return dow[doom];
  }
}
The _year is a private instance variable that I use to communicate state between the ReceivePort and the day() method. I do this as a sanity check that newly spawned isolates will not share that value, not because it is good practice (sending the year as an argument to day() would likely be more appropriate).

From here, the rest is pretty straight-forward. I create an instance of DoomsDay and then spawn futures. The then() callbacks of these Futures fire when then SendPort is ready, at which point I can then immediately request the doomsday for a given year:
main() {
  final doom = new DoomsDay();

  print('Certain doom awaits...');

  doom.spawn().then((port) {
    var year = 2012;
    port.call({'year':year}).receive((message, replyTo) {
      print("Doom in $year is on a $message.");
    });
  });
}
(try.dartlang.org)

Running this, I find:
Certain doom awaits...
Doom in 2012 is on a Wed.
Nice!

And, no matter how many isolates I spawn, it still works:
main() {
  final doom = new DoomsDay();

  print('Certain doom awaits...');

  doom.spawn().then((port) {
    var year = 2011;
    port.call({'year':year}).receive((message, replyTo) {
      print("Doom in $year is on a $message.");
    });
  });

  doom.spawn().then((port) {
    var year = 2012;
    port.call({'year':year}).receive((message, replyTo) {
      print("Doom in $year is on a $message.");
    });
  });

  doom.spawn().then((port) {
    var year = 2015;
    port.call({'year':year}).receive((message, replyTo) {
      print("Doom in $year is on a $message.");
    });
  });

  doom.spawn().then((port) {
    var year = 2020;
    port.call({'year':year}).receive((message, replyTo) {
      print("Doom in $year is on a $message.");
    });
  });
}
(try.dartlang.org)

That is a lot of ceremony for a simple calculation, but I think I am beginning to appreciate the power of all of these parts.

I have definitely solidified my thinking of Isolates. I think I had originally thought they were isolated functions with variables and calculations taking place. I see now that they are objects with special properties and methods that facilitate communication between calling context and spawns isolates / processes.




Day #256

1 comment: