node/lib/domain.js

234 lines
6.3 KiB
JavaScript
Raw Normal View History

Domain feature This is a squashed commit of the main work done on the domains-wip branch. The original commit messages are preserved for posterity: * Implicitly add EventEmitters to active domain * Implicitly add timers to active domain * domain: add members, remove ctor cb * Don't hijack bound callbacks for Domain error events * Add dispose method * Add domain.remove(ee) method * A test of multiple domains in process at once * Put the active domain on the process object * Only intercept error arg if explicitly requested * Typo * Don't auto-add new domains to the current domain While an automatic parent/child relationship is sort of neat, and leads to some nice error-bubbling characteristics, it also results in keeping a reference to every EE and timer created, unless domains are explicitly disposed of. * Explicitly adding one domain to another is still fine, of course. * Don't allow circular domain->domain memberships * Disposing of a domain removes it from its parent * Domain disposal turns functions into no-ops * More documentation of domains * More thorough dispose() semantics * An example using domains in an HTTP server * Don't handle errors on a disposed domain * Need to push, even if the same domain is entered multiple times * Array.push is too slow for the EE Ctor * lint domain * domain: docs * Also call abort and destroySoon to clean up event emitters * domain: Wrap destroy methods in a try/catch * Attach tick callbacks to active domain * domain: Only implicitly bind timers, not explicitly * domain: Don't fire timers when disposed. * domain: Simplify naming so that MakeCallback works on Timers * Add setInterval and nextTick to domain test * domain: Make stack private
2012-04-06 23:26:18 +00:00
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var util = require('util');
var events = require('events');
var EventEmitter = events.EventEmitter;
var inherits = util.inherits;
// methods that are called when trying to shut down expliclitly bound EEs
2012-04-17 22:52:57 +00:00
var endMethods = ['end', 'abort', 'destroy', 'destroySoon'];
Domain feature This is a squashed commit of the main work done on the domains-wip branch. The original commit messages are preserved for posterity: * Implicitly add EventEmitters to active domain * Implicitly add timers to active domain * domain: add members, remove ctor cb * Don't hijack bound callbacks for Domain error events * Add dispose method * Add domain.remove(ee) method * A test of multiple domains in process at once * Put the active domain on the process object * Only intercept error arg if explicitly requested * Typo * Don't auto-add new domains to the current domain While an automatic parent/child relationship is sort of neat, and leads to some nice error-bubbling characteristics, it also results in keeping a reference to every EE and timer created, unless domains are explicitly disposed of. * Explicitly adding one domain to another is still fine, of course. * Don't allow circular domain->domain memberships * Disposing of a domain removes it from its parent * Domain disposal turns functions into no-ops * More documentation of domains * More thorough dispose() semantics * An example using domains in an HTTP server * Don't handle errors on a disposed domain * Need to push, even if the same domain is entered multiple times * Array.push is too slow for the EE Ctor * lint domain * domain: docs * Also call abort and destroySoon to clean up event emitters * domain: Wrap destroy methods in a try/catch * Attach tick callbacks to active domain * domain: Only implicitly bind timers, not explicitly * domain: Don't fire timers when disposed. * domain: Simplify naming so that MakeCallback works on Timers * Add setInterval and nextTick to domain test * domain: Make stack private
2012-04-06 23:26:18 +00:00
// communicate with events module, but don't require that
// module to have to load this one, since this module has
// a few side effects.
events.usingDomains = true;
exports.Domain = Domain;
exports.create = exports.createDomain = function(cb) {
return new Domain(cb);
};
// it's possible to enter one domain while already inside
// another one. the stack is each entered domain.
var stack = [];
// the active domain is always the one that we're currently in.
exports.active = null;
// loading this file the first time sets up the global
// uncaughtException handler.
process.on('uncaughtException', uncaughtHandler);
function uncaughtHandler(er) {
// if there's an active domain, then handle this there.
// Note that if this error emission throws, then it'll just crash.
if (exports.active && !exports.active._disposed) {
decorate(er, {
domain: exports.active,
domain_thrown: true
});
exports.active.emit('error', er);
} else if (process.listeners('uncaughtException').length === 1) {
// if there are other handlers, then they'll take care of it.
// but if not, then we need to crash now.
throw er;
}
}
inherits(Domain, EventEmitter);
function Domain() {
EventEmitter.apply(this);
this.members = [];
}
Domain.prototype.enter = function() {
if (this._disposed) return;
// note that this might be a no-op, but we still need
// to push it onto the stack so that we can pop it later.
exports.active = process.domain = this;
stack.push(this);
};
Domain.prototype.exit = function() {
if (this._disposed) return;
// exit all domains until this one.
var d;
do {
d = stack.pop();
} while (d && d !== this);
exports.active = stack[stack.length - 1];
process.domain = exports.active;
};
// note: this works for timers as well.
Domain.prototype.add = function(ee) {
// disposed domains can't be used for new things.
if (this._disposed) return;
// already added to this domain.
if (ee.domain === this) return;
// has a domain already - remove it first.
if (ee.domain) {
ee.domain.remove(ee);
}
// check for circular Domain->Domain links.
// This causes bad insanity!
//
// For example:
// var d = domain.create();
// var e = domain.create();
// d.add(e);
// e.add(d);
// e.emit('error', er); // RangeError, stack overflow!
if (this.domain && (ee instanceof Domain)) {
for (var d = this.domain; d; d = d.domain) {
if (ee === d) return;
}
}
ee.domain = this;
this.members.push(ee);
};
Domain.prototype.remove = function(ee) {
ee.domain = null;
var index = this.members.indexOf(ee);
if (index !== -1) {
this.members.splice(index, 1);
}
};
Domain.prototype.run = function(fn) {
this.bind(fn)();
};
Domain.prototype.intercept = function(cb) {
return this.bind(cb, true);
};
Domain.prototype.bind = function(cb, interceptError) {
// if cb throws, catch it here.
var self = this;
var b = function() {
// disposing turns functions into no-ops
if (self._disposed) return;
if (this instanceof Domain) {
return cb.apply(this, arguments);
}
// only intercept first-arg errors if explicitly requested.
if (interceptError && arguments[0] &&
(arguments[0] instanceof Error)) {
var er = arguments[0];
decorate(er, {
domain_bound: cb,
domain_thrown: false,
domain: self
});
self.emit('error', er);
return;
}
self.enter();
var ret = cb.apply(this, arguments);
self.exit();
return ret;
};
b.domain = this;
return b;
};
Domain.prototype.dispose = function() {
if (this._disposed) return;
this.emit('dispose');
// remove error handlers.
this.removeAllListeners();
this.on('error', function() {});
// try to kill all the members.
// XXX There should be more consistent ways
// to shut down things!
this.members.forEach(function(m) {
// if it's a timeout or interval, cancel it.
clearTimeout(m);
// drop all event listeners.
if (m instanceof EventEmitter) {
m.removeAllListeners();
// swallow errors
m.on('error', function() {});
}
// Be careful!
// By definition, we're likely in error-ridden territory here,
// so it's quite possible that calling some of these methods
// might cause additional exceptions to be thrown.
endMethods.forEach(function(method) {
if (typeof m[method] === 'function') {
try {
m[method]();
} catch (er) {}
}
});
});
// remove from parent domain, if there is one.
if (this.domain) this.domain.remove(this);
// kill the references so that they can be properly gc'ed.
this.members.length = 0;
// finally, mark this domain as 'no longer relevant'
// so that it can't be entered or activated.
this._disposed = true;
};
function decorate(er, props) {
Object.keys(props).forEach(function(k, _, __) {
if (er.hasOwnProperty(k)) return;
er[k] = props[k];
});
}