mirror of
https://github.com/nodejs/node-v0.x-archive.git
synced 2026-04-28 03:01:10 -04:00
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
This commit is contained in:
233
lib/domain.js
Normal file
233
lib/domain.js
Normal file
@@ -0,0 +1,233 @@
|
||||
// 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
|
||||
var endMethods = [ 'end', 'abort', 'destroy', 'destroySoon' ];
|
||||
|
||||
// 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];
|
||||
});
|
||||
}
|
||||
@@ -21,7 +21,15 @@
|
||||
|
||||
var isArray = Array.isArray;
|
||||
|
||||
function EventEmitter() { }
|
||||
function EventEmitter() {
|
||||
if (exports.usingDomains) {
|
||||
// if there is an active domain, then attach to it.
|
||||
var domain = require('domain');
|
||||
if (domain.active && !(this instanceof domain.Domain)) {
|
||||
this.domain = domain.active;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.EventEmitter = EventEmitter;
|
||||
|
||||
// By default EventEmitters will print a warning if more than
|
||||
@@ -44,6 +52,15 @@ EventEmitter.prototype.emit = function() {
|
||||
if (!this._events || !this._events.error ||
|
||||
(isArray(this._events.error) && !this._events.error.length))
|
||||
{
|
||||
if (this.domain) {
|
||||
var er = arguments[1];
|
||||
er.domain_emitter = this;
|
||||
er.domain = this.domain;
|
||||
er.domain_thrown = false;
|
||||
this.domain.emit('error', er);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (arguments[1] instanceof Error) {
|
||||
throw arguments[1]; // Unhandled 'error' event
|
||||
} else {
|
||||
@@ -58,6 +75,9 @@ EventEmitter.prototype.emit = function() {
|
||||
if (!handler) return false;
|
||||
|
||||
if (typeof handler == 'function') {
|
||||
if (this.domain) {
|
||||
this.domain.enter();
|
||||
}
|
||||
switch (arguments.length) {
|
||||
// fast cases
|
||||
case 1:
|
||||
@@ -76,9 +96,15 @@ EventEmitter.prototype.emit = function() {
|
||||
for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
|
||||
handler.apply(this, args);
|
||||
}
|
||||
if (this.domain) {
|
||||
this.domain.exit();
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if (isArray(handler)) {
|
||||
if (this.domain) {
|
||||
this.domain.enter();
|
||||
}
|
||||
var l = arguments.length;
|
||||
var args = new Array(l - 1);
|
||||
for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
|
||||
@@ -87,6 +113,9 @@ EventEmitter.prototype.emit = function() {
|
||||
for (var i = 0, l = listeners.length; i < l; i++) {
|
||||
listeners[i].apply(this, args);
|
||||
}
|
||||
if (this.domain) {
|
||||
this.domain.exit();
|
||||
}
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
@@ -93,12 +93,17 @@ function insert(item, msecs) {
|
||||
// hack should be removed.
|
||||
//
|
||||
// https://github.com/joyent/node/issues/2631
|
||||
if (first.domain) {
|
||||
if (first.domain._disposed) continue;
|
||||
first.domain.enter();
|
||||
}
|
||||
try {
|
||||
first._onTimeout();
|
||||
} catch (e) {
|
||||
if (!process.listeners('uncaughtException').length) throw e;
|
||||
process.emit('uncaughtException', e);
|
||||
}
|
||||
if (first.domain) first.domain.exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +197,8 @@ exports.setTimeout = function(callback, after) {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.domain) timer.domain = process.domain;
|
||||
|
||||
exports.active(timer);
|
||||
|
||||
return timer;
|
||||
@@ -213,6 +220,8 @@ exports.clearTimeout = function(timer) {
|
||||
exports.setInterval = function(callback, repeat) {
|
||||
var timer = new Timer();
|
||||
|
||||
if (process.domain) timer.domain = process.domain;
|
||||
|
||||
repeat = ~~repeat;
|
||||
if (repeat < 1 || repeat > TIMEOUT_MAX) {
|
||||
repeat = 1; // schedule on next tick, follows browser behaviour
|
||||
|
||||
Reference in New Issue
Block a user