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:
parent
a26bee8fa1
commit
963459d736
@ -8,6 +8,7 @@
|
|||||||
* [Process](process.html)
|
* [Process](process.html)
|
||||||
* [Utilities](util.html)
|
* [Utilities](util.html)
|
||||||
* [Events](events.html)
|
* [Events](events.html)
|
||||||
|
* [Domain](domain.html)
|
||||||
* [Buffer](buffer.html)
|
* [Buffer](buffer.html)
|
||||||
* [Stream](stream.html)
|
* [Stream](stream.html)
|
||||||
* [Crypto](crypto.html)
|
* [Crypto](crypto.html)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
@include process
|
@include process
|
||||||
@include util
|
@include util
|
||||||
@include events
|
@include events
|
||||||
|
@include domain
|
||||||
@include buffer
|
@include buffer
|
||||||
@include stream
|
@include stream
|
||||||
@include crypto
|
@include crypto
|
||||||
|
181
doc/api/domain.markdown
Normal file
181
doc/api/domain.markdown
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
# Domain
|
||||||
|
|
||||||
|
Stability: 1 - Experimental
|
||||||
|
|
||||||
|
Domains provide a way to handle multiple different IO operations as a
|
||||||
|
single group. If any of the event emitters or callbacks registered to a
|
||||||
|
domain emit an `error` event, or throw an error, then the domain object
|
||||||
|
will be notified, rather than losing the context of the error in the
|
||||||
|
`process.on('uncaughtException')` handler, or causing the program to
|
||||||
|
exit with an error code.
|
||||||
|
|
||||||
|
This feature is new in Node version 0.8. It is a first pass, and is
|
||||||
|
expected to change significantly in future versions. Please use it and
|
||||||
|
provide feedback.
|
||||||
|
|
||||||
|
Due to their experimental nature, the Domains features are disabled unless
|
||||||
|
the `domain` module is loaded at least once. No domains are created or
|
||||||
|
registered by default. This is by design, to prevent adverse effects on
|
||||||
|
current programs. It is expected to be enabled by default in future
|
||||||
|
Node.js versions.
|
||||||
|
|
||||||
|
## Additions to Error objects
|
||||||
|
|
||||||
|
<!-- type=misc -->
|
||||||
|
|
||||||
|
Any time an Error object is routed through a domain, a few extra fields
|
||||||
|
are added to it.
|
||||||
|
|
||||||
|
* `error.domain` The domain that first handled the error.
|
||||||
|
* `error.domain_emitter` The event emitter that emitted an 'error' event
|
||||||
|
with the error object.
|
||||||
|
* `error.domain_bound` The callback function which was bound to the
|
||||||
|
domain, and passed an error as its first argument.
|
||||||
|
* `error.domain_thrown` A boolean indicating whether the error was
|
||||||
|
thrown, emitted, or passed to a bound callback function.
|
||||||
|
|
||||||
|
## Implicit Binding
|
||||||
|
|
||||||
|
<!--type=misc-->
|
||||||
|
|
||||||
|
If domains are in use, then all new EventEmitter objects (including
|
||||||
|
Stream objects, requests, responses, etc.) will be implicitly bound to
|
||||||
|
the active domain at the time of their creation.
|
||||||
|
|
||||||
|
Additionally, callbacks passed to lowlevel event loop requests (such as
|
||||||
|
to fs.open, or other callback-taking methods) will automatically be
|
||||||
|
bound to the active domain. If they throw, then the domain will catch
|
||||||
|
the error.
|
||||||
|
|
||||||
|
In order to prevent excessive memory usage, Domain objects themselves
|
||||||
|
are not implicitly added as children of the active domain. If they
|
||||||
|
were, then it would be too easy to prevent request and response objects
|
||||||
|
from being properly garbage collected.
|
||||||
|
|
||||||
|
If you *want* to nest Domain objects as children of a parent Domain,
|
||||||
|
then you must explicitly add them, and then dispose of them later.
|
||||||
|
|
||||||
|
Implicit binding routes thrown errors and `'error'` events to the
|
||||||
|
Domain's `error` event, but does not register the EventEmitter on the
|
||||||
|
Domain, so `domain.dispose()` will not shut down the EventEmitter.
|
||||||
|
Implicit binding only takes care of thrown errors and `'error'` events.
|
||||||
|
|
||||||
|
## domain.create()
|
||||||
|
|
||||||
|
* return: {Domain}
|
||||||
|
|
||||||
|
Returns a new Domain object.
|
||||||
|
|
||||||
|
## Class: Domain
|
||||||
|
|
||||||
|
The Domain class encapsulates the functionality of routing errors and
|
||||||
|
uncaught exceptions to the active Domain object.
|
||||||
|
|
||||||
|
Domain is a child class of EventEmitter. To handle the errors that it
|
||||||
|
catches, listen to its `error` event.
|
||||||
|
|
||||||
|
### domain.members
|
||||||
|
|
||||||
|
* {Array}
|
||||||
|
|
||||||
|
An array of timers and event emitters that have been explicitly added
|
||||||
|
to the domain.
|
||||||
|
|
||||||
|
### domain.add(emitter)
|
||||||
|
|
||||||
|
* `emitter` {EventEmitter | Timer} emitter or timer to be added to the domain
|
||||||
|
|
||||||
|
Explicitly adds an emitter to the domain. If any event handlers called by
|
||||||
|
the emitter throw an error, or if the emitter emits an `error` event, it
|
||||||
|
will be routed to the domain's `error` event, just like with implicit
|
||||||
|
binding.
|
||||||
|
|
||||||
|
This also works with timers that are returned from `setInterval` and
|
||||||
|
`setTimeout`. If their callback function throws, it will be caught by
|
||||||
|
the domain 'error' handler.
|
||||||
|
|
||||||
|
If the Timer or EventEmitter was already bound to a domain, it is removed
|
||||||
|
from that one, and bound to this one instead.
|
||||||
|
|
||||||
|
### domain.remove(emitter)
|
||||||
|
|
||||||
|
* `emitter` {EventEmitter | Timer} emitter or timer to be removed from the domain
|
||||||
|
|
||||||
|
The opposite of `domain.add(emitter)`. Removes domain handling from the
|
||||||
|
specified emitter.
|
||||||
|
|
||||||
|
### domain.bind(cb)
|
||||||
|
|
||||||
|
* `cb` {Function} The callback function
|
||||||
|
* return: {Function} The bound function
|
||||||
|
|
||||||
|
The returned function will be a wrapper around the supplied callback
|
||||||
|
function. When the returned function is called, any errors that are
|
||||||
|
thrown will be routed to the domain's `error` event.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
var d = domain.create();
|
||||||
|
|
||||||
|
function readSomeFile(filename, cb) {
|
||||||
|
fs.readFile(filename, d.bind(function(er, data) {
|
||||||
|
// if this throws, it will also be passed to the domain
|
||||||
|
return cb(er, JSON.parse(data));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
d.on('error', function(er) {
|
||||||
|
// an error occurred somewhere.
|
||||||
|
// if we throw it now, it will crash the program
|
||||||
|
// with the normal line number and stack message.
|
||||||
|
});
|
||||||
|
|
||||||
|
### domain.intercept(cb)
|
||||||
|
|
||||||
|
* `cb` {Function} The callback function
|
||||||
|
* return: {Function} The intercepted function
|
||||||
|
|
||||||
|
This method is almost identical to `domain.bind(cb)`. However, in
|
||||||
|
addition to catching thrown errors, it will also intercept `Error`
|
||||||
|
objects sent as the first argument to the function.
|
||||||
|
|
||||||
|
In this way, the common `if (er) return cb(er);` pattern can be replaced
|
||||||
|
with a single error handler in a single place.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
var d = domain.create();
|
||||||
|
|
||||||
|
function readSomeFile(filename, cb) {
|
||||||
|
fs.readFile(filename, d.intercept(function(er, data) {
|
||||||
|
// if this throws, it will also be passed to the domain
|
||||||
|
// additionally, we know that 'er' will always be null,
|
||||||
|
// so the error-handling logic can be moved to the 'error'
|
||||||
|
// event on the domain instead of being repeated throughout
|
||||||
|
// the program.
|
||||||
|
return cb(er, JSON.parse(data));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
d.on('error', function(er) {
|
||||||
|
// an error occurred somewhere.
|
||||||
|
// if we throw it now, it will crash the program
|
||||||
|
// with the normal line number and stack message.
|
||||||
|
});
|
||||||
|
|
||||||
|
### domain.dispose()
|
||||||
|
|
||||||
|
The dispose method destroys a domain, and makes a best effort attempt to
|
||||||
|
clean up any and all IO that is associated with the domain. Streams are
|
||||||
|
aborted, ended, closed, and/or destroyed. Timers are cleared.
|
||||||
|
Explicitly bound callbacks are no longer called. Any error events that
|
||||||
|
are raised as a result of this are ignored.
|
||||||
|
|
||||||
|
The intention of calling `dispose` is generally to prevent cascading
|
||||||
|
errors when a critical part of the Domain context is found to be in an
|
||||||
|
error state.
|
||||||
|
|
||||||
|
Note that IO might still be performed. However, to the highest degree
|
||||||
|
possible, once a domain is disposed, further errors from the emitters in
|
||||||
|
that set will be ignored. So, even if some remaining actions are still
|
||||||
|
in flight, Node.js will not communicate further about them.
|
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;
|
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;
|
exports.EventEmitter = EventEmitter;
|
||||||
|
|
||||||
// By default EventEmitters will print a warning if more than
|
// By default EventEmitters will print a warning if more than
|
||||||
@ -44,6 +52,15 @@ EventEmitter.prototype.emit = function() {
|
|||||||
if (!this._events || !this._events.error ||
|
if (!this._events || !this._events.error ||
|
||||||
(isArray(this._events.error) && !this._events.error.length))
|
(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) {
|
if (arguments[1] instanceof Error) {
|
||||||
throw arguments[1]; // Unhandled 'error' event
|
throw arguments[1]; // Unhandled 'error' event
|
||||||
} else {
|
} else {
|
||||||
@ -58,6 +75,9 @@ EventEmitter.prototype.emit = function() {
|
|||||||
if (!handler) return false;
|
if (!handler) return false;
|
||||||
|
|
||||||
if (typeof handler == 'function') {
|
if (typeof handler == 'function') {
|
||||||
|
if (this.domain) {
|
||||||
|
this.domain.enter();
|
||||||
|
}
|
||||||
switch (arguments.length) {
|
switch (arguments.length) {
|
||||||
// fast cases
|
// fast cases
|
||||||
case 1:
|
case 1:
|
||||||
@ -76,9 +96,15 @@ EventEmitter.prototype.emit = function() {
|
|||||||
for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
|
for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
|
||||||
handler.apply(this, args);
|
handler.apply(this, args);
|
||||||
}
|
}
|
||||||
|
if (this.domain) {
|
||||||
|
this.domain.exit();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else if (isArray(handler)) {
|
} else if (isArray(handler)) {
|
||||||
|
if (this.domain) {
|
||||||
|
this.domain.enter();
|
||||||
|
}
|
||||||
var l = arguments.length;
|
var l = arguments.length;
|
||||||
var args = new Array(l - 1);
|
var args = new Array(l - 1);
|
||||||
for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
|
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++) {
|
for (var i = 0, l = listeners.length; i < l; i++) {
|
||||||
listeners[i].apply(this, args);
|
listeners[i].apply(this, args);
|
||||||
}
|
}
|
||||||
|
if (this.domain) {
|
||||||
|
this.domain.exit();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -93,12 +93,17 @@ function insert(item, msecs) {
|
|||||||
// hack should be removed.
|
// hack should be removed.
|
||||||
//
|
//
|
||||||
// https://github.com/joyent/node/issues/2631
|
// https://github.com/joyent/node/issues/2631
|
||||||
|
if (first.domain) {
|
||||||
|
if (first.domain._disposed) continue;
|
||||||
|
first.domain.enter();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
first._onTimeout();
|
first._onTimeout();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!process.listeners('uncaughtException').length) throw e;
|
if (!process.listeners('uncaughtException').length) throw e;
|
||||||
process.emit('uncaughtException', 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);
|
exports.active(timer);
|
||||||
|
|
||||||
return timer;
|
return timer;
|
||||||
@ -213,6 +220,8 @@ exports.clearTimeout = function(timer) {
|
|||||||
exports.setInterval = function(callback, repeat) {
|
exports.setInterval = function(callback, repeat) {
|
||||||
var timer = new Timer();
|
var timer = new Timer();
|
||||||
|
|
||||||
|
if (process.domain) timer.domain = process.domain;
|
||||||
|
|
||||||
repeat = ~~repeat;
|
repeat = ~~repeat;
|
||||||
if (repeat < 1 || repeat > TIMEOUT_MAX) {
|
if (repeat < 1 || repeat > TIMEOUT_MAX) {
|
||||||
repeat = 1; // schedule on next tick, follows browser behaviour
|
repeat = 1; // schedule on next tick, follows browser behaviour
|
||||||
|
1
node.gyp
1
node.gyp
@ -23,6 +23,7 @@
|
|||||||
'lib/cluster.js',
|
'lib/cluster.js',
|
||||||
'lib/dgram.js',
|
'lib/dgram.js',
|
||||||
'lib/dns.js',
|
'lib/dns.js',
|
||||||
|
'lib/domain.js',
|
||||||
'lib/events.js',
|
'lib/events.js',
|
||||||
'lib/freelist.js',
|
'lib/freelist.js',
|
||||||
'lib/fs.js',
|
'lib/fs.js',
|
||||||
|
12
src/node.js
12
src/node.js
@ -235,7 +235,13 @@
|
|||||||
nextTickQueue = [];
|
nextTickQueue = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (var i = 0; i < l; i++) q[i]();
|
for (var i = 0; i < l; i++) {
|
||||||
|
var tock = q[i];
|
||||||
|
var callback = tock.callback;
|
||||||
|
if (tock.domain) tock.domain.enter();
|
||||||
|
callback();
|
||||||
|
if (tock.domain) tock.domain.exit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (i + 1 < l) {
|
if (i + 1 < l) {
|
||||||
@ -249,7 +255,9 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
process.nextTick = function(callback) {
|
process.nextTick = function(callback) {
|
||||||
nextTickQueue.push(callback);
|
var tock = { callback: callback };
|
||||||
|
if (process.domain) tock.domain = process.domain;
|
||||||
|
nextTickQueue.push(tock);
|
||||||
process._needTickCallback();
|
process._needTickCallback();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
115
test/simple/test-domain-http-server.js
Normal file
115
test/simple/test-domain-http-server.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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 domain = require('domain');
|
||||||
|
var http = require('http');
|
||||||
|
var assert = require('assert');
|
||||||
|
var common = require('../common.js');
|
||||||
|
|
||||||
|
var objects = { foo: 'bar', baz: {}, num: 42, arr: [1,2,3] };
|
||||||
|
objects.baz.asdf = objects;
|
||||||
|
|
||||||
|
var serverCaught = 0;
|
||||||
|
var clientCaught = 0
|
||||||
|
|
||||||
|
var server = http.createServer(function(req, res) {
|
||||||
|
var dom = domain.create();
|
||||||
|
dom.add(req);
|
||||||
|
dom.add(res);
|
||||||
|
|
||||||
|
dom.on('error', function(er) {
|
||||||
|
serverCaught++;
|
||||||
|
console.log('server error', er);
|
||||||
|
// try to send a 500. If that fails, oh well.
|
||||||
|
res.writeHead(500, {'content-type':'text/plain'});
|
||||||
|
res.end(er.stack || er.message || 'Unknown error');
|
||||||
|
});
|
||||||
|
|
||||||
|
var data;
|
||||||
|
dom.run(function() {
|
||||||
|
// Now, an action that has the potential to fail!
|
||||||
|
// if you request 'baz', then it'll throw a JSON circular ref error.
|
||||||
|
data = JSON.stringify(objects[req.url.replace(/[^a-z]/g, '')]);
|
||||||
|
|
||||||
|
// this line will throw if you pick an unknown key
|
||||||
|
assert(data !== undefined, 'Data should not be undefined');
|
||||||
|
|
||||||
|
res.writeHead(200);
|
||||||
|
res.end(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(common.PORT, next);
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
console.log('listening on localhost:%d', common.PORT);
|
||||||
|
|
||||||
|
// now hit it a few times
|
||||||
|
var dom = domain.create();
|
||||||
|
var requests = 0;
|
||||||
|
var responses = 0;
|
||||||
|
|
||||||
|
makeReq('/');
|
||||||
|
makeReq('/foo');
|
||||||
|
makeReq('/arr');
|
||||||
|
makeReq('/baz');
|
||||||
|
makeReq('/num');
|
||||||
|
|
||||||
|
function makeReq(p) {
|
||||||
|
requests++;
|
||||||
|
|
||||||
|
var dom = domain.create();
|
||||||
|
dom.on('error', function(er) {
|
||||||
|
clientCaught++;
|
||||||
|
console.log('client error', er);
|
||||||
|
// kill everything.
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
var req = http.get({ host: 'localhost', port: common.PORT, path: p });
|
||||||
|
dom.add(req);
|
||||||
|
req.on('response', function(res) {
|
||||||
|
responses++;
|
||||||
|
console.error('requests=%d responses=%d', requests, responses);
|
||||||
|
if (responses === requests) {
|
||||||
|
console.error('done, closing server');
|
||||||
|
// no more coming.
|
||||||
|
server.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
dom.add(res);
|
||||||
|
var d = '';
|
||||||
|
res.on('data', function(c) {
|
||||||
|
d += c;
|
||||||
|
});
|
||||||
|
res.on('end', function() {
|
||||||
|
d = JSON.parse(d);
|
||||||
|
console.log('json!', d);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('exit', function() {
|
||||||
|
assert.equal(serverCaught, 2);
|
||||||
|
assert.equal(clientCaught, 2);
|
||||||
|
console.log('ok');
|
||||||
|
});
|
100
test/simple/test-domain-multi.js
Normal file
100
test/simple/test-domain-multi.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
|
||||||
|
// Tests of multiple domains happening at once.
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var assert = require('assert');
|
||||||
|
var domain = require('domain');
|
||||||
|
var events = require('events');
|
||||||
|
|
||||||
|
var caughtA = false;
|
||||||
|
var caughtB = false;
|
||||||
|
var caughtC = false;
|
||||||
|
|
||||||
|
|
||||||
|
var a = domain.create();
|
||||||
|
a.enter(); // this will be our "root" domain
|
||||||
|
a.on('error', function(er) {
|
||||||
|
caughtA = true;
|
||||||
|
console.log('This should not happen');
|
||||||
|
throw er;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var http = require('http');
|
||||||
|
var server = http.createServer(function (req, res) {
|
||||||
|
// child domain.
|
||||||
|
// implicitly added to a, because we're in a when
|
||||||
|
// it is created.
|
||||||
|
var b = domain.create();
|
||||||
|
|
||||||
|
// treat these EE objects as if they are a part of the b domain
|
||||||
|
// so, an 'error' event on them propagates to the domain, rather
|
||||||
|
// than being thrown.
|
||||||
|
b.add(req);
|
||||||
|
b.add(res);
|
||||||
|
|
||||||
|
b.on('error', function (er) {
|
||||||
|
caughtB = true;
|
||||||
|
console.error('Error encountered', er)
|
||||||
|
if (res) {
|
||||||
|
res.writeHead(500);
|
||||||
|
res.end('An error occurred');
|
||||||
|
}
|
||||||
|
// res.writeHead(500), res.destroy, etc.
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// XXX this bind should not be necessary.
|
||||||
|
// the write cb behavior in http/net should use an
|
||||||
|
// event so that it picks up the domain handling.
|
||||||
|
res.write('HELLO\n', b.bind(function() {
|
||||||
|
throw new Error('this kills domain B, not A');
|
||||||
|
}));
|
||||||
|
|
||||||
|
}).listen(common.PORT);
|
||||||
|
|
||||||
|
var c = domain.create();
|
||||||
|
var req = http.get({ host: 'localhost', port: common.PORT })
|
||||||
|
|
||||||
|
// add the request to the C domain
|
||||||
|
c.add(req);
|
||||||
|
|
||||||
|
req.on('response', function(res) {
|
||||||
|
console.error('got response');
|
||||||
|
// add the response object to the C domain
|
||||||
|
c.add(res);
|
||||||
|
res.pipe(process.stdout);
|
||||||
|
});
|
||||||
|
|
||||||
|
c.on('error', function(er) {
|
||||||
|
caughtC = true;
|
||||||
|
console.error('Error on c', er.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('exit', function() {
|
||||||
|
assert.equal(caughtA, false);
|
||||||
|
assert.equal(caughtB, true)
|
||||||
|
assert.equal(caughtC, true)
|
||||||
|
console.log('ok - Errors went where they were supposed to go');
|
||||||
|
});
|
198
test/simple/test-domain.js
Normal file
198
test/simple/test-domain.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
|
||||||
|
// Simple tests of most basic domain functionality.
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var assert = require('assert');
|
||||||
|
var domain = require('domain');
|
||||||
|
var events = require('events');
|
||||||
|
var caught = 0;
|
||||||
|
var expectCaught = 8;
|
||||||
|
|
||||||
|
var d = new domain.Domain();
|
||||||
|
var e = new events.EventEmitter();
|
||||||
|
|
||||||
|
d.on('error', function(er) {
|
||||||
|
console.error('caught', er);
|
||||||
|
switch (er.message) {
|
||||||
|
case 'emitted':
|
||||||
|
assert.equal(er.domain, d);
|
||||||
|
assert.equal(er.domain_emitter, e);
|
||||||
|
assert.equal(er.domain_thrown, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'bound':
|
||||||
|
assert.ok(!er.domain_emitter);
|
||||||
|
assert.equal(er.domain, d);
|
||||||
|
assert.equal(er.domain_bound, fn);
|
||||||
|
assert.equal(er.domain_thrown, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'thrown':
|
||||||
|
assert.ok(!er.domain_emitter);
|
||||||
|
assert.equal(er.domain, d);
|
||||||
|
assert.equal(er.domain_thrown, true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ENOENT, open 'this file does not exist'":
|
||||||
|
assert.equal(er.domain, d);
|
||||||
|
assert.equal(er.domain_thrown, false);
|
||||||
|
assert.equal(typeof er.domain_bound, 'function');
|
||||||
|
assert.ok(!er.domain_emitter);
|
||||||
|
assert.equal(er.code, 'ENOENT');
|
||||||
|
assert.equal(er.path, 'this file does not exist');
|
||||||
|
assert.equal(typeof er.errno, 'number');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ENOENT, open 'stream for nonexistent file'":
|
||||||
|
assert.equal(typeof er.errno, 'number');
|
||||||
|
assert.equal(er.code, 'ENOENT');
|
||||||
|
assert.equal(er.path, 'stream for nonexistent file');
|
||||||
|
assert.equal(er.domain, d);
|
||||||
|
assert.equal(er.domain_emitter, fst);
|
||||||
|
assert.ok(!er.domain_bound);
|
||||||
|
assert.equal(er.domain_thrown, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'implicit':
|
||||||
|
assert.equal(er.domain_emitter, implicit);
|
||||||
|
assert.equal(er.domain, d);
|
||||||
|
assert.equal(er.domain_thrown, false);
|
||||||
|
assert.ok(!er.domain_bound);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'implicit timer':
|
||||||
|
assert.equal(er.domain, d);
|
||||||
|
assert.equal(er.domain_thrown, true);
|
||||||
|
assert.ok(!er.domain_emitter);
|
||||||
|
assert.ok(!er.domain_bound);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Cannot call method \'isDirectory\' of undefined':
|
||||||
|
assert.equal(er.domain, d);
|
||||||
|
assert.ok(!er.domain_emitter);
|
||||||
|
assert.ok(!er.domain_bound);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error('unexpected error, throwing %j', er.message);
|
||||||
|
throw er;
|
||||||
|
}
|
||||||
|
|
||||||
|
caught++;
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('exit', function() {
|
||||||
|
console.error('exit');
|
||||||
|
assert.equal(caught, expectCaught);
|
||||||
|
console.log('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Event emitters added to the domain have their errors routed.
|
||||||
|
d.add(e);
|
||||||
|
e.emit('error', new Error('emitted'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// get rid of the `if (er) return cb(er)` malarky, by intercepting
|
||||||
|
// the cb functions to the domain, and using the intercepted function
|
||||||
|
// as a callback instead.
|
||||||
|
function fn(er) {
|
||||||
|
throw new Error('This function should never be called!');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bound = d.intercept(fn);
|
||||||
|
bound(new Error('bound'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// throwing in a bound fn is also caught,
|
||||||
|
// even if it's asynchronous, by hitting the
|
||||||
|
// global uncaughtException handler. This doesn't
|
||||||
|
// require interception, since throws are always
|
||||||
|
// caught by the domain.
|
||||||
|
function thrower() {
|
||||||
|
throw new Error('thrown');
|
||||||
|
}
|
||||||
|
setTimeout(d.bind(thrower), 100);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Pass an intercepted function to an fs operation that fails.
|
||||||
|
var fs = require('fs');
|
||||||
|
fs.open('this file does not exist', 'r', d.intercept(function(er) {
|
||||||
|
console.error('should not get here!', er);
|
||||||
|
throw new Error('should not get here!');
|
||||||
|
}, true));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// catch thrown errors no matter how many times we enter the event loop
|
||||||
|
// this only uses implicit binding, except for the first function
|
||||||
|
// passed to d.run(). The rest are implicitly bound by virtue of being
|
||||||
|
// set up while in the scope of the d domain.
|
||||||
|
d.run(function() {
|
||||||
|
process.nextTick(function() {
|
||||||
|
var i = setInterval(function () {
|
||||||
|
clearInterval(i);
|
||||||
|
setTimeout(function() {
|
||||||
|
fs.stat('this file does not exist', function(er, stat) {
|
||||||
|
// uh oh! stat isn't set!
|
||||||
|
// pretty common error.
|
||||||
|
console.log(stat.isDirectory());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// implicit addition by being created within a domain-bound context.
|
||||||
|
var implicit;
|
||||||
|
|
||||||
|
d.run(function() {
|
||||||
|
implicit = new events.EventEmitter;
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
// escape from the domain, but implicit is still bound to it.
|
||||||
|
implicit.emit('error', new Error('implicit'));
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// implicit addition of a timer created within a domain-bound context.
|
||||||
|
d.run(function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
throw new Error('implicit timer');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var fst = fs.createReadStream('stream for nonexistent file')
|
||||||
|
d.add(fst)
|
Loading…
x
Reference in New Issue
Block a user