mirror of https://github.com/lukechilds/node.git
isaacs
13 years ago
31 changed files with 1131 additions and 103 deletions
@ -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. |
@ -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]; |
||||
|
}); |
||||
|
} |
@ -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'); |
||||
|
}); |
@ -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'); |
||||
|
}); |
@ -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…
Reference in new issue