mirror of
https://github.com/nodejs/node-v0.x-archive.git
synced 2026-04-28 03:01:10 -04:00
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:
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)
|
||||
Reference in New Issue
Block a user