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)
|
||||
* [Utilities](util.html)
|
||||
* [Events](events.html)
|
||||
* [Domain](domain.html)
|
||||
* [Buffer](buffer.html)
|
||||
* [Stream](stream.html)
|
||||
* [Crypto](crypto.html)
|
||||
|
@ -8,6 +8,7 @@
|
||||
@include process
|
||||
@include util
|
||||
@include events
|
||||
@include domain
|
||||
@include buffer
|
||||
@include stream
|
||||
@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;
|
||||
|
||||
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
|
||||
|
1
node.gyp
1
node.gyp
@ -23,6 +23,7 @@
|
||||
'lib/cluster.js',
|
||||
'lib/dgram.js',
|
||||
'lib/dns.js',
|
||||
'lib/domain.js',
|
||||
'lib/events.js',
|
||||
'lib/freelist.js',
|
||||
'lib/fs.js',
|
||||
|
12
src/node.js
12
src/node.js
@ -235,7 +235,13 @@
|
||||
nextTickQueue = [];
|
||||
|
||||
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) {
|
||||
if (i + 1 < l) {
|
||||
@ -249,7 +255,9 @@
|
||||
};
|
||||
|
||||
process.nextTick = function(callback) {
|
||||
nextTickQueue.push(callback);
|
||||
var tock = { callback: callback };
|
||||
if (process.domain) tock.domain = process.domain;
|
||||
nextTickQueue.push(tock);
|
||||
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