mirror of https://github.com/lukechilds/node.git
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