Sunday, July 27, 2014

Isolated Reactors in Dart


I am having a devil of a time implementing the Reactor Pattern in Dart.

Truth be told, I do not have to implement it in Dart since Dart is itself built on the Reactor Pattern. Even so, I would like to try my level best to get a reasonable facsimile of it going. The effort itself so far has been illuminating. I had not previously really understood it—that is I could not teach it to someone else. But I would like to get it running for a more pragmatic reason: it would provide insight on how (and if) to include concurrency patterns in the forthcoming Design Patterns in Dart. And, for better or worse, I opted for the Reactor Pattern as my test case.

The problem is not the implementation of the reactor loop:
main() {
  _startRandomMessageSender();

  // Create (and register in constructor) event handler
  new WordAcceptor();

  // Reactor loop...
  for(;;) {
    new InitiationDispatcher().handleEvents();
  }
}
The problem is not even event handlers like the WordAcceptor above, which follows along with the original Reactor Pattern paper by creating new object that then listen for the actual words being sent into the reactor loop.

The problem is getting the messages into the reactor loop. Messages sent before the loop are properly queued and properly processed by the loop. But, once the loop is running, nothing else in the Dart program will run. The loop is so tight that asynchronous code is never given a chance to be executed by Dart's own reactor loop. This, it would seem, is one of the hazards of attempting to build a Reactor Pattern on top of another Reactor Pattern.

But I think the problem might be better phrased as that of a single threaded execution environment, which Dart is. Or, more precisely, Dart's isolates are single threaded. But nothing is preventing me from spawning a second isolate. So that is just what I do. Spawn the message sending function in its own isolate so that it can send messages regardless of what the main isolate is doing:
// ...
import 'dart:isolate';
main() {
  var res = new ReceivePort();
  select().from = res;
  Isolate.
    spawn(messageSender, res.sendPort).
    then((_){
      // Create (and register in constructor) event handler
      new WordAcceptor();

      // Reactor loop...
      for(;;) {
        new InitiationDispatcher().handleEvents();
      }
    });
}
And… this has no effect whatsoever.

Well, it has some effect. The message sending code is now reached and can even be delayed. But the problem remains back in the main code isolate. The reactor loop is so tight that it prevents any other activity from reaching the top of Dart's internal reactor loop.

Oh, and in case anyone thinks like me, no, scheduleMicrotask() is not the answer. Unless the question was, ”how do I exhaust VM memory really, really fast?” Again, the main thread of execution never stops evaluating the for loop long enough to ask what the next microtask or regular event task might be.

So, I think I have to abandon trying to implement this exactly as done in the paper. The for loop prevents any events from outside the loop from being executed, which means that any asynchronous code (like the Future-based isolates) will never been seen.

So I convert the handleEvents() call that used to be inside the loop to a recursive function call. I call handleEvents() once:
main() {
  var res = new ReceivePort();
  Isolate.
    spawn(messageSender, res.sendPort).
    then((_){
      // Create (and register in constructor) event handler
      new WordAcceptor();

      // Reactor “loop”...
      new InitiationDispatcher().handleEvents();
    });
}
Then change handleEvents() to a recursive call (adding a Timer delay for good measure):
class InitiationDispatcher {
  // ...
  handleEvents([int timeout=0]) {
    var event = select().fetch();
    if (event == null) {
      Timer.run((){this.handleEvents();});
      return;
    }
    events[event.type].forEach((h)=> h.handleEvent(event));
  }
}
And that does the trick. I can now send any messages in from the message sending isolate that I like. I probably do not even need the isolate any more.

I think this still honors the spirit of the Reactor Pattern, but I am not 100% positive. I am also not entirely sure what all of this would buy me. The code is hard to follow as-is—hardly sufficiently illustrative to belong in a book. But it feel as though it add a lot of ceremony on top of something that Dart just gives us out of the box. Perhaps that is the point. If nothing else, this has given me additional information on which to ruminate.


Day #135

No comments:

Post a Comment