2017-01-03 22:16:48 +01:00
|
|
|
// 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.
|
|
|
|
|
2014-11-22 16:59:48 +01:00
|
|
|
'use strict';
|
|
|
|
|
2014-12-10 23:03:27 +01:00
|
|
|
// WARNING: THIS MODULE IS PENDING DEPRECATION.
|
|
|
|
//
|
|
|
|
// No new pull requests targeting this module will be accepted
|
|
|
|
// unless they address existing, critical bugs.
|
|
|
|
|
2015-01-21 17:36:59 +01:00
|
|
|
const util = require('util');
|
|
|
|
const EventEmitter = require('events');
|
2017-11-20 19:57:20 +01:00
|
|
|
const errors = require('internal/errors');
|
2017-10-10 22:40:43 +02:00
|
|
|
const { createHook } = require('async_hooks');
|
2012-04-07 01:26:18 +02:00
|
|
|
|
|
|
|
// communicate with events module, but don't require that
|
|
|
|
// module to have to load this one, since this module has
|
|
|
|
// a few side effects.
|
2013-10-30 00:35:32 +01:00
|
|
|
EventEmitter.usingDomains = true;
|
2013-02-27 20:46:35 +01:00
|
|
|
|
2014-01-09 20:11:40 +01:00
|
|
|
// overwrite process.domain with a getter/setter that will allow for more
|
|
|
|
// effective optimizations
|
|
|
|
var _domain = [null];
|
|
|
|
Object.defineProperty(process, 'domain', {
|
|
|
|
enumerable: true,
|
|
|
|
get: function() {
|
|
|
|
return _domain[0];
|
|
|
|
},
|
|
|
|
set: function(arg) {
|
|
|
|
return _domain[0] = arg;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-10-10 22:40:43 +02:00
|
|
|
const pairing = new Map();
|
|
|
|
const asyncHook = createHook({
|
|
|
|
init(asyncId, type, triggerAsyncId, resource) {
|
|
|
|
if (process.domain !== null && process.domain !== undefined) {
|
|
|
|
// if this operation is created while in a domain, let's mark it
|
|
|
|
pairing.set(asyncId, process.domain);
|
|
|
|
resource.domain = process.domain;
|
|
|
|
if (resource.promise !== undefined &&
|
|
|
|
resource.promise instanceof Promise) {
|
|
|
|
// resource.promise instanceof Promise make sure that the
|
|
|
|
// promise comes from the same context
|
|
|
|
// see https://github.com/nodejs/node/issues/15673
|
|
|
|
resource.promise.domain = process.domain;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
before(asyncId) {
|
|
|
|
const current = pairing.get(asyncId);
|
|
|
|
if (current !== undefined) { // enter domain for this cb
|
|
|
|
current.enter();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
after(asyncId) {
|
|
|
|
const current = pairing.get(asyncId);
|
|
|
|
if (current !== undefined) { // exit domain for this cb
|
|
|
|
current.exit();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
destroy(asyncId) {
|
|
|
|
pairing.delete(asyncId); // cleaning up
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-11-20 19:57:20 +01:00
|
|
|
// When domains are in use, they claim full ownership of the
|
|
|
|
// uncaught exception capture callback.
|
|
|
|
if (process.hasUncaughtExceptionCaptureCallback()) {
|
|
|
|
throw new errors.Error('ERR_DOMAIN_CALLBACK_NOT_AVAILABLE');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the stack trace at the point where `domain` was required.
|
|
|
|
const domainRequireStack = new Error('require(`domain`) at this point').stack;
|
|
|
|
|
|
|
|
const { setUncaughtExceptionCaptureCallback } = process;
|
|
|
|
process.setUncaughtExceptionCaptureCallback = function(fn) {
|
|
|
|
const err =
|
|
|
|
new errors.Error('ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE');
|
|
|
|
err.stack = err.stack + '\n' + '-'.repeat(40) + '\n' + domainRequireStack;
|
|
|
|
throw err;
|
|
|
|
};
|
|
|
|
|
2015-11-03 02:56:24 +01:00
|
|
|
// It's possible to enter one domain while already inside
|
|
|
|
// another one. The stack is each entered domain.
|
|
|
|
const stack = [];
|
|
|
|
exports._stack = stack;
|
2017-11-20 19:57:20 +01:00
|
|
|
process._setupDomainUse();
|
2017-10-10 22:40:43 +02:00
|
|
|
|
2017-11-20 19:57:20 +01:00
|
|
|
function updateExceptionCapture() {
|
|
|
|
if (stack.every((domain) => domain.listenerCount('error') === 0)) {
|
|
|
|
setUncaughtExceptionCaptureCallback(null);
|
|
|
|
} else {
|
|
|
|
setUncaughtExceptionCaptureCallback(null);
|
|
|
|
setUncaughtExceptionCaptureCallback((er) => {
|
|
|
|
return process.domain._errorHandler(er);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
process.on('newListener', (name, listener) => {
|
|
|
|
if (name === 'uncaughtException' &&
|
|
|
|
listener !== domainUncaughtExceptionClear) {
|
|
|
|
// Make sure the first listener for `uncaughtException` always clears
|
|
|
|
// the domain stack.
|
|
|
|
process.removeListener(name, domainUncaughtExceptionClear);
|
|
|
|
process.prependListener(name, domainUncaughtExceptionClear);
|
|
|
|
}
|
|
|
|
});
|
2015-11-03 02:56:24 +01:00
|
|
|
|
2017-11-20 19:57:20 +01:00
|
|
|
process.on('removeListener', (name, listener) => {
|
|
|
|
if (name === 'uncaughtException' &&
|
|
|
|
listener !== domainUncaughtExceptionClear) {
|
|
|
|
// If the domain listener would be the only remaining one, remove it.
|
|
|
|
const listeners = process.listeners('uncaughtException');
|
|
|
|
if (listeners.length === 1 && listeners[0] === domainUncaughtExceptionClear)
|
|
|
|
process.removeListener(name, domainUncaughtExceptionClear);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function domainUncaughtExceptionClear() {
|
|
|
|
stack.length = 0;
|
|
|
|
exports.active = process.domain = null;
|
|
|
|
updateExceptionCapture();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Domain extends EventEmitter {
|
2017-10-10 22:40:43 +02:00
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
|
|
|
|
this.members = [];
|
|
|
|
asyncHook.enable();
|
2017-11-20 19:57:20 +01:00
|
|
|
|
|
|
|
this.on('removeListener', updateExceptionCapture);
|
|
|
|
this.on('newListener', updateExceptionCapture);
|
2017-10-10 22:40:43 +02:00
|
|
|
}
|
|
|
|
}
|
2014-01-09 20:11:40 +01:00
|
|
|
|
2012-04-07 01:26:18 +02:00
|
|
|
exports.Domain = Domain;
|
|
|
|
|
2013-08-22 01:12:17 +02:00
|
|
|
exports.create = exports.createDomain = function() {
|
|
|
|
return new Domain();
|
2012-04-07 01:26:18 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// the active domain is always the one that we're currently in.
|
|
|
|
exports.active = null;
|
2013-08-22 01:12:17 +02:00
|
|
|
Domain.prototype.members = undefined;
|
2014-01-09 20:11:40 +01:00
|
|
|
|
|
|
|
// Called by process._fatalException in case an error was thrown.
|
2016-10-16 00:02:43 +02:00
|
|
|
Domain.prototype._errorHandler = function _errorHandler(er) {
|
2014-01-09 20:11:40 +01:00
|
|
|
var caught = false;
|
2015-10-05 22:08:53 +02:00
|
|
|
|
2013-12-24 13:33:03 +01:00
|
|
|
if (!util.isPrimitive(er)) {
|
|
|
|
er.domain = this;
|
|
|
|
er.domainThrown = true;
|
|
|
|
}
|
2014-01-09 20:11:40 +01:00
|
|
|
|
2015-10-05 22:08:53 +02:00
|
|
|
// The top-level domain-handler is handled separately.
|
|
|
|
//
|
|
|
|
// The reason is that if V8 was passed a command line option
|
|
|
|
// asking it to abort on an uncaught exception (currently
|
|
|
|
// "--abort-on-uncaught-exception"), we want an uncaught exception
|
|
|
|
// in the top-level domain error handler to make the
|
|
|
|
// process abort. Using try/catch here would always make V8 think
|
|
|
|
// that these exceptions are caught, and thus would prevent it from
|
|
|
|
// aborting in these cases.
|
|
|
|
if (stack.length === 1) {
|
2015-11-03 02:56:24 +01:00
|
|
|
// If there's no error handler, do not emit an 'error' event
|
|
|
|
// as this would throw an error, make the process exit, and thus
|
|
|
|
// prevent the process 'uncaughtException' event from being emitted
|
|
|
|
// if a listener is set.
|
2016-02-15 05:53:17 +01:00
|
|
|
if (EventEmitter.listenerCount(this, 'error') > 0) {
|
2017-11-20 19:57:20 +01:00
|
|
|
// Clear the uncaughtExceptionCaptureCallback so that we know that, even
|
|
|
|
// if technically the top-level domain is still active, it would
|
|
|
|
// be ok to abort on an uncaught exception at this point
|
|
|
|
setUncaughtExceptionCaptureCallback(null);
|
2015-11-03 02:56:24 +01:00
|
|
|
try {
|
2016-02-15 05:53:17 +01:00
|
|
|
caught = this.emit('error', er);
|
2015-11-03 02:56:24 +01:00
|
|
|
} finally {
|
2017-11-20 19:57:20 +01:00
|
|
|
updateExceptionCapture();
|
2015-11-03 02:56:24 +01:00
|
|
|
}
|
2014-01-09 20:11:40 +01:00
|
|
|
}
|
2015-10-05 22:08:53 +02:00
|
|
|
} else {
|
|
|
|
// wrap this in a try/catch so we don't get infinite throwing
|
|
|
|
try {
|
|
|
|
// One of three things will happen here.
|
|
|
|
//
|
|
|
|
// 1. There is a handler, caught = true
|
|
|
|
// 2. There is no handler, caught = false
|
|
|
|
// 3. It throws, caught = false
|
|
|
|
//
|
|
|
|
// If caught is false after this, then there's no need to exit()
|
|
|
|
// the domain, because we're going to crash the process anyway.
|
2016-02-15 05:53:17 +01:00
|
|
|
caught = this.emit('error', er);
|
2015-10-05 22:08:53 +02:00
|
|
|
} catch (er2) {
|
|
|
|
// The domain error handler threw! oh no!
|
|
|
|
// See if another domain can catch THIS error,
|
|
|
|
// or else crash on the original one.
|
|
|
|
// If the user already exited it, then don't double-exit.
|
|
|
|
if (this === exports.active) {
|
|
|
|
stack.pop();
|
|
|
|
}
|
2017-11-20 19:57:20 +01:00
|
|
|
updateExceptionCapture();
|
2015-10-05 22:08:53 +02:00
|
|
|
if (stack.length) {
|
|
|
|
exports.active = process.domain = stack[stack.length - 1];
|
2017-11-20 19:57:20 +01:00
|
|
|
caught = process.domain._errorHandler(er2);
|
2015-10-05 22:08:53 +02:00
|
|
|
} else {
|
2017-11-20 19:57:20 +01:00
|
|
|
// Pass on to the next exception handler.
|
|
|
|
throw er2;
|
2015-10-05 22:08:53 +02:00
|
|
|
}
|
2014-01-09 20:11:40 +01:00
|
|
|
}
|
|
|
|
}
|
2016-01-13 02:48:58 +01:00
|
|
|
|
|
|
|
// Exit all domains on the stack. Uncaught exceptions end the
|
|
|
|
// current tick and no domains should be left on the stack
|
|
|
|
// between ticks.
|
2017-11-20 19:57:20 +01:00
|
|
|
domainUncaughtExceptionClear();
|
2016-01-13 02:48:58 +01:00
|
|
|
|
2014-01-09 20:11:40 +01:00
|
|
|
return caught;
|
|
|
|
};
|
2013-08-22 01:12:17 +02:00
|
|
|
|
|
|
|
|
2012-04-07 01:26:18 +02:00
|
|
|
Domain.prototype.enter = function() {
|
|
|
|
// 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);
|
2017-11-20 19:57:20 +01:00
|
|
|
updateExceptionCapture();
|
2012-04-07 01:26:18 +02:00
|
|
|
};
|
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
|
2012-04-07 01:26:18 +02:00
|
|
|
Domain.prototype.exit = function() {
|
2017-09-14 17:58:53 +02:00
|
|
|
// don't do anything if this domain is not on the stack.
|
2014-01-09 06:19:31 +01:00
|
|
|
var index = stack.lastIndexOf(this);
|
2017-09-14 17:58:53 +02:00
|
|
|
if (index === -1) return;
|
2012-04-07 01:26:18 +02:00
|
|
|
|
|
|
|
// exit all domains until this one.
|
2014-01-09 06:19:31 +01:00
|
|
|
stack.splice(index);
|
2012-04-07 01:26:18 +02:00
|
|
|
|
|
|
|
exports.active = stack[stack.length - 1];
|
|
|
|
process.domain = exports.active;
|
2017-11-20 19:57:20 +01:00
|
|
|
updateExceptionCapture();
|
2012-04-07 01:26:18 +02:00
|
|
|
};
|
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
|
2012-04-07 01:26:18 +02:00
|
|
|
// note: this works for timers as well.
|
|
|
|
Domain.prototype.add = function(ee) {
|
2017-09-14 17:58:53 +02:00
|
|
|
// If the domain is already added, then nothing left to do.
|
|
|
|
if (ee.domain === this)
|
2013-10-30 00:35:32 +01:00
|
|
|
return;
|
2012-04-07 01:26:18 +02:00
|
|
|
|
|
|
|
// has a domain already - remove it first.
|
2013-10-30 00:35:32 +01:00
|
|
|
if (ee.domain)
|
2012-04-07 01:26:18 +02:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
|
2012-04-07 01:26:18 +02:00
|
|
|
Domain.prototype.remove = function(ee) {
|
|
|
|
ee.domain = null;
|
|
|
|
var index = this.members.indexOf(ee);
|
2013-10-30 00:35:32 +01:00
|
|
|
if (index !== -1)
|
2012-04-07 01:26:18 +02:00
|
|
|
this.members.splice(index, 1);
|
|
|
|
};
|
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
|
2012-04-07 01:26:18 +02:00
|
|
|
Domain.prototype.run = function(fn) {
|
2014-11-29 13:50:29 +01:00
|
|
|
var ret;
|
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
this.enter();
|
2014-11-29 13:50:29 +01:00
|
|
|
if (arguments.length >= 2) {
|
|
|
|
var len = arguments.length;
|
|
|
|
var args = new Array(len - 1);
|
|
|
|
|
|
|
|
for (var i = 1; i < len; i++)
|
|
|
|
args[i - 1] = arguments[i];
|
|
|
|
|
|
|
|
ret = fn.apply(this, args);
|
|
|
|
} else {
|
|
|
|
ret = fn.call(this);
|
|
|
|
}
|
2013-10-30 00:35:32 +01:00
|
|
|
this.exit();
|
2014-11-29 13:50:29 +01:00
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
return ret;
|
2012-04-07 01:26:18 +02:00
|
|
|
};
|
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
|
|
|
|
function intercepted(_this, self, cb, fnargs) {
|
|
|
|
if (fnargs[0] && fnargs[0] instanceof Error) {
|
|
|
|
var er = fnargs[0];
|
|
|
|
util._extend(er, {
|
|
|
|
domainBound: cb,
|
|
|
|
domainThrown: false,
|
|
|
|
domain: self
|
|
|
|
});
|
|
|
|
self.emit('error', er);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var args = [];
|
|
|
|
var i, ret;
|
|
|
|
|
|
|
|
self.enter();
|
|
|
|
if (fnargs.length > 1) {
|
|
|
|
for (i = 1; i < fnargs.length; i++)
|
|
|
|
args.push(fnargs[i]);
|
|
|
|
ret = cb.apply(_this, args);
|
|
|
|
} else {
|
|
|
|
ret = cb.call(_this);
|
|
|
|
}
|
|
|
|
self.exit();
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-07 01:26:18 +02:00
|
|
|
Domain.prototype.intercept = function(cb) {
|
2013-10-30 00:35:32 +01:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
function runIntercepted() {
|
|
|
|
return intercepted(this, self, cb, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
return runIntercepted;
|
2012-04-07 01:26:18 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
function bound(_this, self, cb, fnargs) {
|
2014-06-11 21:16:48 +02:00
|
|
|
var ret;
|
2012-04-07 01:26:18 +02:00
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
self.enter();
|
|
|
|
if (fnargs.length > 0)
|
|
|
|
ret = cb.apply(_this, fnargs);
|
|
|
|
else
|
|
|
|
ret = cb.call(_this);
|
|
|
|
self.exit();
|
2012-06-06 17:17:01 +02:00
|
|
|
|
2013-10-30 00:35:32 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Domain.prototype.bind = function(cb) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
function runBound() {
|
|
|
|
return bound(this, self, cb, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
runBound.domain = this;
|
|
|
|
|
|
|
|
return runBound;
|
2012-04-07 01:26:18 +02:00
|
|
|
};
|