diff --git a/lib/domain.js b/lib/domain.js index f4cb9ce7db5907..83d2e9be7f53f6 100644 --- a/lib/domain.js +++ b/lib/domain.js @@ -36,6 +36,7 @@ const { } = primordials; const EventEmitter = require('events'); +const { ERR_UNHANDLED_ERROR } = require('internal/errors').codes; const { createHook } = require('async_hooks'); const { useDomainTrampoline } = require('internal/async_hooks'); @@ -128,7 +129,7 @@ function topLevelDomainCallback(cb, ...args) { // It's possible to enter one domain while already inside another one. The stack // is each entered domain. -const stack = process._domainStack; +let stack = []; exports._stack = stack; useDomainTrampoline(topLevelDomainCallback); @@ -438,3 +439,101 @@ Domain.prototype.bind = function(cb) { 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; +};