Port the domain module from createHook (async_hooks) to AsyncLocalStorage using the AsyncContextFrame-based implementation. Key changes: - Use AsyncLocalStorage for domain context propagation instead of async_hooks.createHook() - Lazy initialization that triggers AsyncContextFrame prototype swap on first domain use - Use enterWith instead of ALS.run() so domain context is NOT automatically restored on exception - this matches the original domain.run() behavior where exit() only runs on success - Add ERR_ASYNC_RESOURCE_DOMAIN_REMOVED error for AsyncResource.domain - Update DEP0097 to End-of-Life status - Remove tests that relied on the removed MakeCallback domain property The domain module now uses the AsyncContextFrame version of AsyncLocalStorage directly for proper context propagation across async boundaries. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: https://github.com/nodejs/node/pull/61095 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
661 lines
21 KiB
JavaScript
661 lines
21 KiB
JavaScript
// 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 {
|
|
ArrayPrototypeIndexOf,
|
|
ArrayPrototypeLastIndexOf,
|
|
ArrayPrototypePush,
|
|
ArrayPrototypeSlice,
|
|
ArrayPrototypeSplice,
|
|
Error,
|
|
FunctionPrototypeCall,
|
|
ObjectDefineProperty,
|
|
ReflectApply,
|
|
SafeSet,
|
|
} = primordials;
|
|
|
|
const EventEmitter = require('events');
|
|
const {
|
|
ERR_UNHANDLED_ERROR,
|
|
} = require('internal/errors').codes;
|
|
|
|
// Use AsyncLocalStorage for domain context propagation
|
|
// Lazy initialization - ALS is created on first domain use
|
|
let domainStorage;
|
|
|
|
function initDomainStorage() {
|
|
if (domainStorage === undefined) {
|
|
const { AsyncLocalStorage } = require('async_hooks');
|
|
|
|
// Single AsyncLocalStorage instance for all domain context
|
|
// Store structure: { domain: Domain | null, stack: Domain[] }
|
|
domainStorage = new AsyncLocalStorage();
|
|
}
|
|
return domainStorage;
|
|
}
|
|
|
|
// Helper functions to get/set domain store
|
|
function getDomainStore() {
|
|
return initDomainStorage().getStore();
|
|
}
|
|
|
|
function setDomainStore(store) {
|
|
initDomainStorage().enterWith(store);
|
|
}
|
|
|
|
// Overwrite process.domain with a getter/setter backed by native context
|
|
ObjectDefineProperty(process, 'domain', {
|
|
__proto__: null,
|
|
enumerable: true,
|
|
get: function() {
|
|
const store = getDomainStore();
|
|
// Return undefined when no domain is active (store doesn't exist OR domain is null)
|
|
// This makes the API consistent: undefined = no domain, Domain object = domain active
|
|
const domain = store?.domain;
|
|
return domain === null ? undefined : domain;
|
|
},
|
|
set: function(arg) {
|
|
const store = getDomainStore();
|
|
if (store) {
|
|
// Create new store to avoid affecting other async contexts
|
|
setDomainStore({ domain: arg, stack: store.stack });
|
|
}
|
|
// If no store exists, setting is a no-op (will be set when domain is entered)
|
|
return arg;
|
|
},
|
|
});
|
|
|
|
// Domain uses the stacking capability of setUncaughtExceptionCaptureCallback
|
|
// to coexist with other callbacks (e.g., REPL).
|
|
const { setUncaughtExceptionCaptureCallback } = process;
|
|
|
|
|
|
// Helper to get the current stack from native context
|
|
function getStack() {
|
|
const store = getDomainStore();
|
|
return store?.stack ?? [];
|
|
}
|
|
|
|
// Helper to set the stack in native context
|
|
function setStack(newStack) {
|
|
const store = getDomainStore();
|
|
if (store) {
|
|
// Create new store to avoid affecting other async contexts
|
|
setDomainStore({ domain: store.domain, stack: newStack });
|
|
}
|
|
}
|
|
|
|
// It's possible to enter one domain while already inside
|
|
// another one. The stack is each entered domain.
|
|
// For backward compatibility, exports._stack is a getter/setter
|
|
ObjectDefineProperty(exports, '_stack', {
|
|
__proto__: null,
|
|
enumerable: true,
|
|
get: getStack,
|
|
set: setStack,
|
|
});
|
|
|
|
// Module-level tracking of the current domain and stack for exception handling.
|
|
// This is needed because when exceptions propagate out of domainStorage.run(),
|
|
// the ALS context is restored before the exception capture callback runs.
|
|
// For async callbacks, ALS context is still available, but for synchronous
|
|
// exceptions we need this fallback.
|
|
let currentDomain = null;
|
|
let currentStack = null; // The stack at the time of the current sync operation
|
|
|
|
// Track all domains that have error listeners. This is needed because async
|
|
// callbacks may fire with a domain context even when the domain is not on the
|
|
// current stack. We install the exception handler when ANY domain has listeners.
|
|
const domainsWithListeners = new SafeSet();
|
|
|
|
// The domain exception handler - only installed when a domain with error listeners
|
|
// exists. This allows uncaughtException event to fire when no handler.
|
|
function domainExceptionHandler(er) {
|
|
// For sync exceptions (from run/bound/intercepted), currentDomain is set to the
|
|
// domain that was active when the exception was thrown. Check it first because
|
|
// by the time we get here, the ALS context may have already been restored to the
|
|
// outer domain.
|
|
// For async callbacks, currentDomain is null and process.domain reads from ALS.
|
|
const domain = currentDomain || process.domain;
|
|
|
|
// For sync exceptions, we need to restore the ALS context before calling _errorHandler
|
|
// because ALS unwinds during exception propagation. This ensures exit() works correctly.
|
|
if (currentDomain !== null && currentStack !== null) {
|
|
// Restore the ALS context and exports.active to match the state at the time of the throw
|
|
setDomainStore({ domain: currentDomain, stack: currentStack });
|
|
exports.active = currentDomain;
|
|
}
|
|
|
|
// First check the current domain
|
|
if (domain && domain.listenerCount('error') > 0) {
|
|
return domain._errorHandler(er);
|
|
}
|
|
|
|
// For SYNC exceptions only (currentDomain is set), check parent domains on the stack.
|
|
// This handles the case where d2 (no handler) throws synchronously inside d (has handler).
|
|
// For ASYNC exceptions (currentDomain is null), only the domain from ALS matters -
|
|
// the exception should NOT bubble up to parent domains that were on the stack when
|
|
// the async callback was scheduled.
|
|
if (currentDomain !== null && currentStack !== null) {
|
|
for (let i = currentStack.length - 1; i >= 0; i--) {
|
|
const parentDomain = currentStack[i];
|
|
if (parentDomain !== domain && parentDomain.listenerCount('error') > 0) {
|
|
return parentDomain._errorHandler(er);
|
|
}
|
|
}
|
|
}
|
|
|
|
// No domain with error handler found - let the exception propagate.
|
|
// Since setUncaughtExceptionCaptureCallback ignores our return value,
|
|
// we must emit 'uncaughtException' manually to give other handlers a chance.
|
|
// We temporarily uninstall ourselves to avoid infinite recursion if someone
|
|
// throws from their uncaughtException handler.
|
|
uninstallExceptionHandler();
|
|
|
|
// Emit uncaughtException to give process.on('uncaughtException') handlers a chance
|
|
const handled = process.emit('uncaughtException', er, 'uncaughtException');
|
|
|
|
if (handled) {
|
|
// Reinstall ourselves for future exceptions
|
|
installExceptionHandler();
|
|
} else {
|
|
// No handler caught the exception - we need to exit.
|
|
// Throw it to let the C++ side handle the exit properly (respects --abort-on-uncaught).
|
|
// Don't reinstall the callback - we want the throw to propagate directly to C++.
|
|
throw er;
|
|
}
|
|
}
|
|
|
|
// Track if exception handler is currently installed
|
|
let exceptionHandlerInstalled = false;
|
|
|
|
function installExceptionHandler() {
|
|
if (!exceptionHandlerInstalled) {
|
|
setUncaughtExceptionCaptureCallback(domainExceptionHandler);
|
|
exceptionHandlerInstalled = true;
|
|
}
|
|
}
|
|
|
|
function uninstallExceptionHandler() {
|
|
if (exceptionHandlerInstalled) {
|
|
setUncaughtExceptionCaptureCallback(null);
|
|
exceptionHandlerInstalled = false;
|
|
}
|
|
}
|
|
|
|
function updateExceptionCapture() {
|
|
// Install exception handler if ANY domain has error listeners
|
|
if (domainsWithListeners.size > 0) {
|
|
installExceptionHandler();
|
|
} else {
|
|
uninstallExceptionHandler();
|
|
}
|
|
}
|
|
|
|
// Called when an 'error' listener is being added
|
|
// 'newListener' is called BEFORE listener is added, so count is still 0
|
|
function domainNewListener(type) {
|
|
if (type === 'error') {
|
|
domainsWithListeners.add(this);
|
|
updateExceptionCapture();
|
|
}
|
|
}
|
|
|
|
// Called when an 'error' listener is being removed
|
|
// 'removeListener' is called AFTER listener is removed, so count reflects new state
|
|
function domainRemoveListener(type) {
|
|
if (type === 'error' && this.listenerCount('error') === 0) {
|
|
domainsWithListeners.delete(this);
|
|
updateExceptionCapture();
|
|
}
|
|
}
|
|
|
|
|
|
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() {
|
|
// Clear the stack and active domain in the CURRENT context only
|
|
// Don't mutate the store in-place as it would affect other async contexts
|
|
// that share the same store reference
|
|
setDomainStore({ domain: null, stack: [] });
|
|
exports.active = null;
|
|
currentDomain = null;
|
|
currentStack = null;
|
|
updateExceptionCapture();
|
|
}
|
|
|
|
|
|
class Domain extends EventEmitter {
|
|
constructor() {
|
|
super();
|
|
|
|
this.members = [];
|
|
|
|
this.on('newListener', domainNewListener);
|
|
this.on('removeListener', domainRemoveListener);
|
|
}
|
|
}
|
|
|
|
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', {
|
|
__proto__: null,
|
|
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();
|
|
}
|
|
|
|
const stack = getStack();
|
|
|
|
// 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 (this.listenerCount('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
|
|
uninstallExceptionHandler();
|
|
try {
|
|
caught = this.emit('error', er);
|
|
} finally {
|
|
installExceptionHandler();
|
|
}
|
|
}
|
|
} 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();
|
|
const currentStack = getStack();
|
|
if (currentStack.length) {
|
|
const parentDomain = currentStack[currentStack.length - 1];
|
|
const store = getDomainStore();
|
|
if (store) {
|
|
// Create new store to avoid affecting other async contexts
|
|
setDomainStore({ domain: parentDomain, stack: store.stack });
|
|
}
|
|
exports.active = parentDomain;
|
|
caught = parentDomain._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.
|
|
const currentStore = getDomainStore() || { domain: null, stack: [] };
|
|
const newStack = ArrayPrototypeSlice(currentStore.stack);
|
|
ArrayPrototypePush(newStack, this);
|
|
setDomainStore({ domain: this, stack: newStack });
|
|
exports.active = this;
|
|
currentDomain = this;
|
|
currentStack = newStack;
|
|
updateExceptionCapture();
|
|
};
|
|
|
|
|
|
Domain.prototype.exit = function() {
|
|
// Don't do anything if this domain is not on the stack.
|
|
const store = getDomainStore();
|
|
if (!store) return;
|
|
|
|
const stack = store.stack;
|
|
const index = ArrayPrototypeLastIndexOf(stack, this);
|
|
if (index === -1) return;
|
|
|
|
// Exit all domains until this one.
|
|
const newStack = ArrayPrototypeSlice(stack, 0, index);
|
|
const parentDomain = newStack.length === 0 ? null : newStack[newStack.length - 1];
|
|
setDomainStore({ domain: parentDomain, stack: newStack });
|
|
exports.active = parentDomain;
|
|
currentDomain = parentDomain;
|
|
currentStack = newStack.length === 0 ? null : newStack;
|
|
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.
|
|
// They cause big issues.
|
|
//
|
|
// 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', {
|
|
__proto__: null,
|
|
configurable: true,
|
|
enumerable: false,
|
|
value: this,
|
|
writable: true,
|
|
});
|
|
ArrayPrototypePush(this.members, ee);
|
|
};
|
|
|
|
|
|
Domain.prototype.remove = function(ee) {
|
|
ee.domain = null;
|
|
const index = ArrayPrototypeIndexOf(this.members, ee);
|
|
if (index !== -1)
|
|
ArrayPrototypeSplice(this.members, index, 1);
|
|
};
|
|
|
|
|
|
// Helper to run a function within a domain context.
|
|
// Uses enterWith instead of ALS.run() so the context is NOT automatically
|
|
// restored on exception - this matches original domain behavior where
|
|
// exit() only runs on success.
|
|
function runInDomain(domain, thisArg, fn, args) {
|
|
const currentStore = getDomainStore() || { domain: null, stack: [] };
|
|
const newStack = ArrayPrototypeSlice(currentStore.stack);
|
|
ArrayPrototypePush(newStack, domain);
|
|
const previousDomain = currentDomain;
|
|
const previousStack = currentStack;
|
|
|
|
currentDomain = domain;
|
|
currentStack = newStack;
|
|
updateExceptionCapture();
|
|
|
|
setDomainStore({ domain, stack: newStack });
|
|
exports.active = domain;
|
|
|
|
const result = ReflectApply(fn, thisArg, args);
|
|
|
|
// On success, restore context (if exception thrown, context stays for catch blocks)
|
|
setDomainStore(currentStore);
|
|
exports.active = currentStore.domain;
|
|
currentDomain = previousDomain;
|
|
currentStack = previousStack;
|
|
updateExceptionCapture();
|
|
|
|
return result;
|
|
}
|
|
|
|
Domain.prototype.run = function(fn) {
|
|
return runInDomain(this, this, fn, ArrayPrototypeSlice(arguments, 1));
|
|
};
|
|
|
|
|
|
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', {
|
|
__proto__: null,
|
|
configurable: true,
|
|
enumerable: false,
|
|
value: self,
|
|
writable: true,
|
|
});
|
|
self.emit('error', er);
|
|
return;
|
|
}
|
|
|
|
return runInDomain(self, _this, cb, ArrayPrototypeSlice(fnargs, 1));
|
|
}
|
|
|
|
|
|
Domain.prototype.intercept = function(cb) {
|
|
const self = this;
|
|
|
|
function runIntercepted() {
|
|
return intercepted(this, self, cb, arguments);
|
|
}
|
|
|
|
return runIntercepted;
|
|
};
|
|
|
|
|
|
function bound(_this, self, cb, fnargs) {
|
|
return runInDomain(self, _this, cb, fnargs);
|
|
}
|
|
|
|
|
|
Domain.prototype.bind = function(cb) {
|
|
const self = this;
|
|
|
|
function runBound() {
|
|
return bound(this, self, cb, arguments);
|
|
}
|
|
|
|
ObjectDefineProperty(runBound, 'domain', {
|
|
__proto__: null,
|
|
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(opts) {
|
|
ObjectDefineProperty(this, 'domain', {
|
|
__proto__: null,
|
|
configurable: true,
|
|
enumerable: false,
|
|
value: null,
|
|
writable: true,
|
|
});
|
|
if (exports.active && !(this instanceof exports.Domain)) {
|
|
this.domain = exports.active;
|
|
}
|
|
|
|
return FunctionPrototypeCall(eventInit, this, opts);
|
|
};
|
|
|
|
const eventEmit = EventEmitter.prototype.emit;
|
|
EventEmitter.prototype.emit = function emit(...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', {
|
|
__proto__: null,
|
|
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 store = getDomainStore();
|
|
const stack = store?.stack ?? [];
|
|
const origDomainsStack = ArrayPrototypeSlice(stack);
|
|
const origActiveDomain = store?.domain ?? null;
|
|
|
|
// 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 && origActiveDomain === stack[idx]) {
|
|
--idx;
|
|
}
|
|
|
|
// Change the stack to not contain the current active domain, and only the
|
|
// domains above it on the stack.
|
|
let newStack;
|
|
if (idx < 0) {
|
|
newStack = [];
|
|
} else {
|
|
newStack = ArrayPrototypeSlice(stack, 0, idx + 1);
|
|
}
|
|
|
|
// Change the current active domain
|
|
const newActiveDomain = newStack.length > 0 ? newStack[newStack.length - 1] : null;
|
|
const prevCurrentDomain = currentDomain;
|
|
const prevCurrentStack = currentStack;
|
|
|
|
if (store) {
|
|
setDomainStore({ domain: newActiveDomain, stack: newStack });
|
|
}
|
|
exports.active = newActiveDomain;
|
|
currentDomain = newActiveDomain;
|
|
currentStack = newStack;
|
|
|
|
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.
|
|
if (store) {
|
|
setDomainStore({ domain: origActiveDomain, stack: origDomainsStack });
|
|
}
|
|
exports.active = origActiveDomain;
|
|
currentDomain = prevCurrentDomain;
|
|
currentStack = prevCurrentStack;
|
|
updateExceptionCapture();
|
|
|
|
return false;
|
|
}
|
|
|
|
domain.enter();
|
|
const ret = ReflectApply(eventEmit, this, args);
|
|
domain.exit();
|
|
|
|
return ret;
|
|
};
|