Skip to content

Instantly share code, notes, and snippets.

@piscisaureus
Last active December 28, 2015 06:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save piscisaureus/7454729 to your computer and use it in GitHub Desktop.
Save piscisaureus/7454729 to your computer and use it in GitHub Desktop.
Domain spec

Basic tasks

When you are in a Domain and you spawn off an async call (like fs.stat), the callhack for that async call gets executed in the domain. With tasks this is the same, so far, nothing new.

However, there is no 'end' to a domain. At some point the Domain object might become unreachable and gets GC'ed. That's all.

Think of a Task as an extension to a domain, namely a domain that has a callback. That callback is called - automatically - in the context of it's parent domain, whenever, by the end of the current tick, we know that the domain will never be entered again.

So the basic strategy to make this work.

  • Keeping a count of the number of future callbacks (or sources thereof) that can happen within the domain.
  • After the tick in which a domain is established we check if the reference count is zero
  • After every tick in which the reference count goes to zero we schedule another check.

so, a simple case:

  domain.createTask(function(callback) {
    // Do nothing
  }).setCallback(function(err) {
    // This will be executing immediately after the createTask tick ends.
  });

and another simple case:

  domain.createTask(function(callback) {
    setTimeout(function() {
      console.log('boo!');
      // The reference count is now zero, or about to become zero.
      // So at the end of this tick we'll make the callback.
    }, 100);
    // The reference count is now 1.
  }).setCallback(function(err) {
    // Here gets called in the tick immediately after the setTimeout callback.
  });

and another simple case:

  domain.createTask(function(callback) {
    var timer = setInterval(function() {}, 100);
    // The reference count is now 1.
    clearInterval(timer);
    // The reference count is now 0 again.
  }).setCallback(function(err) {
    // This gets called at the end of the createTask tick.
  });

EventEmitters

EventEmitters complicate the matter. Think of.

  var that;
  
  domain.createTask(function(done) {
    that = bla.createBlaStream();
  });

  domain.createTask(function(done) {
    that.on('blerg', function() {
      // ...
    });
    // The reference count is now 1. But how will we know when to down the refcount back to 0?
  }).setCallback(function(err) {
    // ...
  });

This is pretty hard :) So we're going to define a couple of rules that describe when an EventEmitter adds a reference to a task. Note that an EventEmitter is not a domain in itself, but it does have a 'parent domain'.

An EventEmitter adds a reference to a domain in the following cases:

  • the domain has one or more listeners established on the EventEmitter, AND
  • the domain is not the parent domain of the EventEmitter, AND
  • the EventEmitter is not considered 'destroyed' (more on that later).

That means that an EventEmitter stops referencing a domain whenever this happens:

  • A domain uses removeListener / removeAllListeners to remove it's last event handler from the emitter.
  • As soon as the EventEmitter is destroyed.
  • The EventEmitter is reparented to the domain (this should be really rare! we don't really have to support that except for backwards compat)

An EventEmitter is considered destroyed whenever:

  • It's parent domain has exited either succesfully or with an error.
  • A magic method __destroy() is called on it. Naming open for debate.

Error handling magic

So here's what happens when an error is thrown within a domain:

  • We close Handles (and FDWraps which don't exist yet)

    • Note that handles add a reference to a domain, so when we close them and the close callback comes we must decrease the reference count of the parent domain.
    • In order to be able to clean up on error a Domain must keep a list of all the Handles within.
  • We signal all EventEmitters that we are related to (e.g. both those that we have listeners on, and those that we are parent to) that an error has happened.

    • The EE walks through all it's related domains (parent and listeners) and does the following:
      • See if that specific domain has an 'error' listener, and call that
      • If there is no error listener, throw inside the domain.
    • Therefore an EventEmitter must know for each listener which domain it belongs to. Also each Domain must know which EventEmitters are related to it.
  • We signal our sub-domains to perform the same cleanup - __throw() method?

    • So a domain must keep a list of all it's sub-domains.
  • ReqWraps can't generally be cancelled so we just wait for them to finish by themselves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment