node/test/parallel/test-domain-emit-error-handler-stack.js
Matteo Collina 2c4356cedf
domain: port to AsyncLocalStorage
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>
2026-06-21 19:42:03 +00:00

161 lines
5 KiB
JavaScript

'use strict';
const common = require('../common');
const assert = require('assert');
const domain = require('domain');
const EventEmitter = require('events');
// Make sure that the domains stack and the active domain is setup properly when
// a domain's error handler is called due to an error event being emitted.
// More specifically, we want to test that:
// - the active domain in the domain's error handler is//not* that domain,//but*
// the active domain is a any direct parent domain at the time the error was
// emitted.
// - the domains stack in the domain's error handler does//not* include that
// domain, *but* it includes all parents of that domain when the error was
// emitted.
const d1 = domain.create();
const d2 = domain.create();
const d3 = domain.create();
function checkExpectedDomains(err) {
// First, check that domains stack and active domain is as expected when the
// event handler is called synchronously via ee.emit('error').
if (domain._stack.length !== err.expectedStackLength) {
console.error('expected domains stack length of %d, but instead is %d',
err.expectedStackLength, domain._stack.length);
process.exit(1);
}
if (process.domain !== err.expectedActiveDomain) {
console.error('expected active domain to be %j, but instead is %j',
err.expectedActiveDomain, process.domain);
process.exit(1);
}
// Then make sure that the domains stack and active domain is setup as
// expected when executing a callback scheduled via nextTick from the error
// handler.
// Note: With AsyncLocalStorage-based domains, async callbacks inherit the
// full stack from when they were scheduled, so stack length matches the
// sync context.
process.nextTick(() => {
const expectedStackLengthInNextTickCb = err.expectedStackLength;
if (domain._stack.length !== expectedStackLengthInNextTickCb) {
console.error('expected stack length in nextTick cb to be %d, ' +
'but instead is %d', expectedStackLengthInNextTickCb,
domain._stack.length);
process.exit(1);
}
const expectedActiveDomainInNextTickCb =
expectedStackLengthInNextTickCb === 0 ? undefined :
err.expectedActiveDomain;
if (process.domain !== expectedActiveDomainInNextTickCb) {
console.error('expected active domain in nextTick cb to be %j, ' +
'but instead is %j', expectedActiveDomainInNextTickCb,
process.domain);
process.exit(1);
}
});
}
d1.on('error', common.mustCall((err) => {
checkExpectedDomains(err);
}, 2));
d2.on('error', common.mustCall((err) => {
checkExpectedDomains(err);
}, 2));
d3.on('error', common.mustCall((err) => {
checkExpectedDomains(err);
}, 1));
d1.run(common.mustCall(() => {
const ee = new EventEmitter();
assert.strictEqual(process.domain, d1);
assert.strictEqual(domain._stack.length, 1);
const err = new Error('oops');
err.expectedStackLength = 0;
err.expectedActiveDomain = undefined;
ee.emit('error', err);
assert.strictEqual(process.domain, d1);
assert.strictEqual(domain._stack.length, 1);
}));
d1.run(common.mustCall(() => {
d1.run(common.mustCall(() => {
const ee = new EventEmitter();
assert.strictEqual(process.domain, d1);
assert.strictEqual(domain._stack.length, 2);
const err = new Error('oops');
err.expectedStackLength = 0;
err.expectedActiveDomain = undefined;
ee.emit('error', err);
assert.strictEqual(process.domain, d1);
assert.strictEqual(domain._stack.length, 2);
}));
}));
d1.run(common.mustCall(() => {
d2.run(common.mustCall(() => {
const ee = new EventEmitter();
assert.strictEqual(process.domain, d2);
assert.strictEqual(domain._stack.length, 2);
const err = new Error('oops');
err.expectedStackLength = 1;
err.expectedActiveDomain = d1;
ee.emit('error', err);
assert.strictEqual(process.domain, d2);
assert.strictEqual(domain._stack.length, 2);
}));
}));
d1.run(common.mustCall(() => {
d2.run(common.mustCall(() => {
d2.run(common.mustCall(() => {
const ee = new EventEmitter();
assert.strictEqual(process.domain, d2);
assert.strictEqual(domain._stack.length, 3);
const err = new Error('oops');
err.expectedStackLength = 1;
err.expectedActiveDomain = d1;
ee.emit('error', err);
assert.strictEqual(process.domain, d2);
assert.strictEqual(domain._stack.length, 3);
}));
}));
}));
d3.run(common.mustCall(() => {
d1.run(common.mustCall(() => {
d3.run(common.mustCall(() => {
d3.run(common.mustCall(() => {
const ee = new EventEmitter();
assert.strictEqual(process.domain, d3);
assert.strictEqual(domain._stack.length, 4);
const err = new Error('oops');
err.expectedStackLength = 2;
err.expectedActiveDomain = d1;
ee.emit('error', err);
assert.strictEqual(process.domain, d3);
assert.strictEqual(domain._stack.length, 4);
}));
}));
}));
}));