| // 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. |
| |
| 'use strict'; |
| |
| // WARNING: THIS MODULE IS PENDING DEPRECATION. |
| // |
| // No new pull requests targeting this module will be accepted |
| // unless they address existing, critical bugs. |
| |
| const { |
| Array, |
| Error, |
| Map, |
| ObjectDefineProperty, |
| ReflectApply, |
| Symbol, |
| } = primordials; |
| |
| const EventEmitter = require('events'); |
| const { |
| ERR_DOMAIN_CALLBACK_NOT_AVAILABLE, |
| ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE, |
| ERR_UNHANDLED_ERROR |
| } = require('internal/errors').codes; |
| const { createHook } = require('async_hooks'); |
| |
| // TODO(addaleax): Use a non-internal solution for this. |
| const kWeak = Symbol('kWeak'); |
| const { WeakReference } = internalBinding('util'); |
| |
| // Overwrite process.domain with a getter/setter that will allow for more |
| // effective optimizations |
| const _domain = [null]; |
| ObjectDefineProperty(process, 'domain', { |
| enumerable: true, |
| get: function() { |
| return _domain[0]; |
| }, |
| set: function(arg) { |
| return _domain[0] = arg; |
| } |
| }); |
| |
| 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[kWeak]); |
| ObjectDefineProperty(resource, 'domain', { |
| configurable: true, |
| enumerable: false, |
| value: process.domain, |
| writable: true |
| }); |
| } |
| }, |
| before(asyncId) { |
| const current = pairing.get(asyncId); |
| if (current !== undefined) { // Enter domain for this cb |
| // We will get the domain through current.get(), because the resource |
| // object's .domain property makes sure it is not garbage collected. |
| // However, we do need to make the reference to the domain non-weak, |
| // so that it cannot be garbage collected before the after() hook. |
| current.incRef(); |
| current.get().enter(); |
| } |
| }, |
| after(asyncId) { |
| const current = pairing.get(asyncId); |
| if (current !== undefined) { // Exit domain for this cb |
| const domain = current.get(); |
| current.decRef(); |
| domain.exit(); |
| } |
| }, |
| destroy(asyncId) { |
| pairing.delete(asyncId); // cleaning up |
| } |
| }); |
| |
| // When domains are in use, they claim full ownership of the |
| // uncaught exception capture callback. |
| if (process.hasUncaughtExceptionCaptureCallback()) { |
| throw new ERR_DOMAIN_CALLBACK_NOT_AVAILABLE(); |
| } |
| |
| // Get the stack trace at the point where `domain` was required. |
| // eslint-disable-next-line no-restricted-syntax |
| const domainRequireStack = new Error('require(`domain`) at this point').stack; |
| |
| const { setUncaughtExceptionCaptureCallback } = process; |
| process.setUncaughtExceptionCaptureCallback = function(fn) { |
| const err = new ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE(); |
| err.stack = err.stack + '\n' + '-'.repeat(40) + '\n' + domainRequireStack; |
| throw err; |
| }; |
| |
| |
| let sendMakeCallbackDeprecation = false; |
| function emitMakeCallbackDeprecation() { |
| if (!sendMakeCallbackDeprecation) { |
| process.emitWarning( |
| 'Using a domain property in MakeCallback is deprecated. Use the ' + |
| 'async_context variant of MakeCallback or the AsyncResource class ' + |
| 'instead.', 'DeprecationWarning', 'DEP0097'); |
| sendMakeCallbackDeprecation = true; |
| } |
| } |
| |
| function topLevelDomainCallback(cb, ...args) { |
| const domain = this.domain; |
| if (exports.active && domain) |
| emitMakeCallbackDeprecation(); |
| |
| if (domain) |
| domain.enter(); |
| const ret = ReflectApply(cb, this, args); |
| if (domain) |
| domain.exit(); |
| |
| return ret; |
| } |
| |
| // It's possible to enter one domain while already inside |
| // another one. The stack is each entered domain. |
| let stack = []; |
| exports._stack = stack; |
| internalBinding('domain').enable(topLevelDomainCallback); |
| |
| 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); |
| } |
| }); |
| |
| 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 { |
| constructor() { |
| super(); |
| |
| this.members = []; |
| this[kWeak] = new WeakReference(this); |
| asyncHook.enable(); |
| |
| this.on('removeListener', updateExceptionCapture); |
| this.on('newListener', updateExceptionCapture); |
| } |
| } |
| |
| exports.Domain = Domain; |
| |
| exports.create = exports.createDomain = function createDomain() { |
| return new Domain(); |
| }; |
| |
| // The active domain is always the one that we're currently in. |
| exports.active = null; |
| Domain.prototype.members = undefined; |
| |
| // Called by process._fatalException in case an error was thrown. |
| Domain.prototype._errorHandler = function(er) { |
| let caught = false; |
| |
| if ((typeof er === 'object' && er !== null) || typeof er === 'function') { |
| ObjectDefineProperty(er, 'domain', { |
| configurable: true, |
| enumerable: false, |
| value: this, |
| writable: true |
| }); |
| er.domainThrown = true; |
| } |
| // Pop all adjacent duplicates of the currently active domain from the stack. |
| // This is done to prevent a domain's error handler to run within the context |
| // of itself, and re-entering itself recursively handler as a result of an |
| // exception thrown in its context. |
| while (exports.active === this) { |
| this.exit(); |
| } |
| |
| // 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 === 0) { |
| // 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. |
| if (EventEmitter.listenerCount(this, 'error') > 0) { |
| // Clear the uncaughtExceptionCaptureCallback so that we know that, since |
| // the top-level domain is not active anymore, it would be ok to abort on |
| // an uncaught exception at this point |
| setUncaughtExceptionCaptureCallback(null); |
| try { |
| caught = this.emit('error', er); |
| } finally { |
| updateExceptionCapture(); |
| } |
| } |
| } 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. |
| caught = this.emit('error', er); |
| } catch (er2) { |
| // The domain error handler threw! oh no! |
| // See if another domain can catch THIS error, |
| // or else crash on the original one. |
| updateExceptionCapture(); |
| if (stack.length) { |
| exports.active = process.domain = stack[stack.length - 1]; |
| caught = process.domain._errorHandler(er2); |
| } else { |
| // Pass on to the next exception handler. |
| throw er2; |
| } |
| } |
| } |
| |
| // Exit all domains on the stack. Uncaught exceptions end the |
| // current tick and no domains should be left on the stack |
| // between ticks. |
| domainUncaughtExceptionClear(); |
| |
| return caught; |
| }; |
| |
| |
| 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); |
| updateExceptionCapture(); |
| }; |
| |
| |
| Domain.prototype.exit = function() { |
| // Don't do anything if this domain is not on the stack. |
| const index = stack.lastIndexOf(this); |
| if (index === -1) return; |
| |
| // Exit all domains until this one. |
| stack.splice(index); |
| |
| exports.active = stack[stack.length - 1]; |
| process.domain = exports.active; |
| updateExceptionCapture(); |
| }; |
| |
| |
| // note: this works for timers as well. |
| Domain.prototype.add = function(ee) { |
| // If the domain is already added, then nothing left to do. |
| 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 (let d = this.domain; d; d = d.domain) { |
| if (ee === d) return; |
| } |
| } |
| |
| ObjectDefineProperty(ee, 'domain', { |
| configurable: true, |
| enumerable: false, |
| value: this, |
| writable: true |
| }); |
| this.members.push(ee); |
| }; |
| |
| |
| Domain.prototype.remove = function(ee) { |
| ee.domain = null; |
| const index = this.members.indexOf(ee); |
| if (index !== -1) |
| this.members.splice(index, 1); |
| }; |
| |
| |
| Domain.prototype.run = function(fn) { |
| let ret; |
| |
| this.enter(); |
| if (arguments.length >= 2) { |
| const len = arguments.length; |
| const args = new Array(len - 1); |
| |
| for (let i = 1; i < len; i++) |
| args[i - 1] = arguments[i]; |
| |
| ret = fn.apply(this, args); |
| } else { |
| ret = fn.call(this); |
| } |
| this.exit(); |
| |
| return ret; |
| }; |
| |
| |
| function intercepted(_this, self, cb, fnargs) { |
| if (fnargs[0] && fnargs[0] instanceof Error) { |
| const er = fnargs[0]; |
| er.domainBound = cb; |
| er.domainThrown = false; |
| ObjectDefineProperty(er, 'domain', { |
| configurable: true, |
| enumerable: false, |
| value: self, |
| writable: true |
| }); |
| self.emit('error', er); |
| return; |
| } |
| |
| const args = []; |
| let ret; |
| |
| self.enter(); |
| if (fnargs.length > 1) { |
| for (let i = 1; i < fnargs.length; i++) |
| args.push(fnargs[i]); |
| ret = cb.apply(_this, args); |
| } else { |
| ret = cb.call(_this); |
| } |
| self.exit(); |
| |
| return ret; |
| } |
| |
| |
| Domain.prototype.intercept = function(cb) { |
| const self = this; |
| |
| function runIntercepted() { |
| return intercepted(this, self, cb, arguments); |
| } |
| |
| return runIntercepted; |
| }; |
| |
| |
| function bound(_this, self, cb, fnargs) { |
| let ret; |
| |
| self.enter(); |
| if (fnargs.length > 0) |
| ret = cb.apply(_this, fnargs); |
| else |
| ret = cb.call(_this); |
| self.exit(); |
| |
| return ret; |
| } |
| |
| |
| Domain.prototype.bind = function(cb) { |
| const self = this; |
| |
| function runBound() { |
| return bound(this, self, cb, arguments); |
| } |
| |
| ObjectDefineProperty(runBound, 'domain', { |
| configurable: true, |
| enumerable: false, |
| value: this, |
| writable: true |
| }); |
| |
| return runBound; |
| }; |
| |
| // Override EventEmitter methods to make it domain-aware. |
| EventEmitter.usingDomains = true; |
| |
| const eventInit = EventEmitter.init; |
| EventEmitter.init = function() { |
| ObjectDefineProperty(this, 'domain', { |
| configurable: true, |
| enumerable: false, |
| value: null, |
| writable: true |
| }); |
| if (exports.active && !(this instanceof exports.Domain)) { |
| this.domain = exports.active; |
| } |
| |
| return eventInit.call(this); |
| }; |
| |
| const eventEmit = EventEmitter.prototype.emit; |
| EventEmitter.prototype.emit = function(...args) { |
| const domain = this.domain; |
| |
| const type = args[0]; |
| const shouldEmitError = type === 'error' && |
| this.listenerCount(type) > 0; |
| |
| // Just call original `emit` if current EE instance has `error` |
| // handler, there's no active domain or this is process |
| if (shouldEmitError || domain === null || domain === undefined || |
| this === process) { |
| return ReflectApply(eventEmit, this, args); |
| } |
| |
| if (type === 'error') { |
| const er = args.length > 1 && args[1] ? |
| args[1] : new ERR_UNHANDLED_ERROR(); |
| |
| if (typeof er === 'object') { |
| er.domainEmitter = this; |
| ObjectDefineProperty(er, 'domain', { |
| configurable: true, |
| enumerable: false, |
| value: domain, |
| writable: true |
| }); |
| er.domainThrown = false; |
| } |
| |
| // Remove the current domain (and its duplicates) from the domains stack and |
| // set the active domain to its parent (if any) so that the domain's error |
| // handler doesn't run in its own context. This prevents any event emitter |
| // created or any exception thrown in that error handler from recursively |
| // executing that error handler. |
| const origDomainsStack = stack.slice(); |
| const origActiveDomain = process.domain; |
| |
| // Travel the domains stack from top to bottom to find the first domain |
| // instance that is not a duplicate of the current active domain. |
| let idx = stack.length - 1; |
| while (idx > -1 && process.domain === stack[idx]) { |
| --idx; |
| } |
| |
| // Change the stack to not contain the current active domain, and only the |
| // domains above it on the stack. |
| if (idx < 0) { |
| stack.length = 0; |
| } else { |
| stack.splice(idx + 1); |
| } |
| |
| // Change the current active domain |
| if (stack.length > 0) { |
| exports.active = process.domain = stack[stack.length - 1]; |
| } else { |
| exports.active = process.domain = null; |
| } |
| |
| updateExceptionCapture(); |
| |
| domain.emit('error', er); |
| |
| // Now that the domain's error handler has completed, restore the domains |
| // stack and the active domain to their original values. |
| exports._stack = stack = origDomainsStack; |
| exports.active = process.domain = origActiveDomain; |
| updateExceptionCapture(); |
| |
| return false; |
| } |
| |
| domain.enter(); |
| const ret = ReflectApply(eventEmit, this, args); |
| domain.exit(); |
| |
| return ret; |
| }; |