Browse Source

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
v0.9.1-release
isaacs 13 years ago
parent
commit
963459d736
  1. 1
      doc/api/_toc.markdown
  2. 1
      doc/api/all.markdown
  3. 181
      doc/api/domain.markdown
  4. 233
      lib/domain.js
  5. 31
      lib/events.js
  6. 9
      lib/timers.js
  7. 1
      node.gyp
  8. 12
      src/node.js
  9. 115
      test/simple/test-domain-http-server.js
  10. 100
      test/simple/test-domain-multi.js
  11. 198
      test/simple/test-domain.js

1
doc/api/_toc.markdown

@ -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)

1
doc/api/all.markdown

@ -8,6 +8,7 @@
@include process
@include util
@include events
@include domain
@include buffer
@include stream
@include crypto

181
doc/api/domain.markdown

@ -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

@ -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];
});
}

31
lib/events.js

@ -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 {

9
lib/timers.js

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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…
Cancel
Save