node/test/parallel/test-repl-multiple-instances-async-error.js
Matteo Collina a9da9ffc04
repl: remove dependency on domain module
Replace the domain-based error handling with AsyncLocalStorage and
setUncaughtExceptionCaptureCallback. This removes the REPL's dependency
on the deprecated domain module while preserving all existing behavior:

- Synchronous errors during eval are caught and displayed
- Async errors (setTimeout, promises, etc.) are caught via the
  uncaught exception capture callback
- Top-level await errors are caught and displayed
- The REPL continues operating after errors
- Multiple REPL instances can coexist with errors routed correctly

Changes:
- Use AsyncLocalStorage to track which REPL instance owns an async
  context, replacing domain's automatic async tracking
- Add setupExceptionCapture() to install
  setUncaughtExceptionCaptureCallback for catching async errors and
  routing them to the correct REPL
- Extract error handling logic into REPLServer.prototype._handleError()
- Wrap eval execution in replContext.run() for async context tracking
- Update newListener protection to check AsyncLocalStorage context
- Throw ERR_INVALID_ARG_VALUE if options.domain is passed

PR-URL: https://github.com/nodejs/node/pull/61227
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
2026-03-06 15:38:36 +00:00

69 lines
1.8 KiB
JavaScript

'use strict';
// This test verifies that when multiple REPL instances exist concurrently,
// async errors are correctly routed to the REPL instance that created them.
const common = require('../common');
const assert = require('assert');
const repl = require('repl');
const { Writable, PassThrough } = require('stream');
// Create two REPLs with separate inputs and outputs
let output1 = '';
let output2 = '';
const input1 = new PassThrough();
const input2 = new PassThrough();
const writable1 = new Writable({
write(chunk, encoding, callback) {
output1 += chunk.toString();
callback();
}
});
const writable2 = new Writable({
write(chunk, encoding, callback) {
output2 += chunk.toString();
callback();
}
});
const r1 = repl.start({
input: input1,
output: writable1,
terminal: false,
prompt: 'R1> ',
});
const r2 = repl.start({
input: input2,
output: writable2,
terminal: false,
prompt: 'R2> ',
});
// Create async error in REPL 1
input1.write('setTimeout(() => { throw new Error("error from repl1") }, 10)\n');
// Create async error in REPL 2
input2.write('setTimeout(() => { throw new Error("error from repl2") }, 20)\n');
setTimeout(common.mustCall(() => {
r1.close();
r2.close();
// Verify error from REPL 1 went to REPL 1's output
assert.match(output1, /error from repl1/,
'REPL 1 should have received its own async error');
// Verify error from REPL 2 went to REPL 2's output
assert.match(output2, /error from repl2/,
'REPL 2 should have received its own async error');
// Verify errors did not cross over to wrong REPL
assert.doesNotMatch(output1, /error from repl2/,
'REPL 1 should not have received REPL 2\'s error');
assert.doesNotMatch(output2, /error from repl1/,
'REPL 2 should not have received REPL 1\'s error');
}), 100);