mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 21:58:48 +02:00
async_hooks: fix AsyncLocalStorage in unhandledRejection cases
PR-URL: https://github.com/nodejs/node/pull/41202 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Vladimir de Turckheim <vlad2t@hotmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
This commit is contained in:
parent
eee1a6f42a
commit
c3866b09c1
3 changed files with 187 additions and 64 deletions
|
@ -1,31 +1,118 @@
|
|||
'use strict';
|
||||
require('../common');
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { AsyncLocalStorage } = require('async_hooks');
|
||||
const vm = require('vm');
|
||||
|
||||
// err1 is emitted sync as a control - no events
|
||||
// err2 is emitted after a timeout - uncaughtExceptionMonitor
|
||||
// + uncaughtException
|
||||
// err3 is emitted after some awaits - unhandledRejection
|
||||
// err4 is emitted during handling err3 - uncaughtExceptionMonitor
|
||||
// err5 is emitted after err4 from a VM lacking hooks - unhandledRejection
|
||||
// + uncaughtException
|
||||
|
||||
// case 2 using *AndReturn calls (dual behaviors)
|
||||
const asyncLocalStorage = new AsyncLocalStorage();
|
||||
const callbackToken = { callbackToken: true };
|
||||
const awaitToken = { awaitToken: true };
|
||||
|
||||
let i = 0;
|
||||
process.setUncaughtExceptionCaptureCallback((err) => {
|
||||
++i;
|
||||
assert.strictEqual(err.message, 'err2');
|
||||
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'node');
|
||||
});
|
||||
|
||||
try {
|
||||
asyncLocalStorage.run(new Map(), () => {
|
||||
const store = asyncLocalStorage.getStore();
|
||||
store.set('hello', 'node');
|
||||
setTimeout(() => {
|
||||
process.nextTick(() => {
|
||||
assert.strictEqual(i, 1);
|
||||
});
|
||||
throw new Error('err2');
|
||||
}, 0);
|
||||
throw new Error('err1');
|
||||
});
|
||||
} catch (e) {
|
||||
assert.strictEqual(e.message, 'err1');
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
|
||||
// Redefining the uncaughtExceptionHandler is a bit odd, so we just do this
|
||||
// so we can track total invocations
|
||||
let underlyingExceptionHandler;
|
||||
const exceptionHandler = common.mustCall(function(...args) {
|
||||
return underlyingExceptionHandler.call(this, ...args);
|
||||
}, 2);
|
||||
process.setUncaughtExceptionCaptureCallback(exceptionHandler);
|
||||
|
||||
const exceptionMonitor = common.mustCall((err, origin) => {
|
||||
if (err.message === 'err2') {
|
||||
assert.strictEqual(origin, 'uncaughtException');
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), callbackToken);
|
||||
} else if (err.message === 'err4') {
|
||||
assert.strictEqual(origin, 'unhandledRejection');
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
|
||||
} else {
|
||||
assert.fail('unknown error ' + err);
|
||||
}
|
||||
}, 2);
|
||||
process.on('uncaughtExceptionMonitor', exceptionMonitor);
|
||||
|
||||
function fireErr1() {
|
||||
underlyingExceptionHandler = common.mustCall(function(err) {
|
||||
++i;
|
||||
assert.strictEqual(err.message, 'err2');
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), callbackToken);
|
||||
}, 1);
|
||||
try {
|
||||
asyncLocalStorage.run(callbackToken, () => {
|
||||
setTimeout(fireErr2, 0);
|
||||
throw new Error('err1');
|
||||
});
|
||||
} catch (e) {
|
||||
assert.strictEqual(e.message, 'err1');
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function fireErr2() {
|
||||
process.nextTick(() => {
|
||||
assert.strictEqual(i, 1);
|
||||
fireErr3();
|
||||
});
|
||||
throw new Error('err2');
|
||||
}
|
||||
|
||||
function fireErr3() {
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), callbackToken);
|
||||
const rejectionHandler3 = common.mustCall((err) => {
|
||||
assert.strictEqual(err.message, 'err3');
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
|
||||
process.off('unhandledRejection', rejectionHandler3);
|
||||
|
||||
fireErr4();
|
||||
}, 1);
|
||||
process.on('unhandledRejection', rejectionHandler3);
|
||||
async function awaitTest() {
|
||||
await null;
|
||||
throw new Error('err3');
|
||||
}
|
||||
asyncLocalStorage.run(awaitToken, awaitTest);
|
||||
}
|
||||
|
||||
const uncaughtExceptionHandler4 = common.mustCall(
|
||||
function(err) {
|
||||
assert.strictEqual(err.message, 'err4');
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
|
||||
fireErr5();
|
||||
}, 1);
|
||||
function fireErr4() {
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
|
||||
underlyingExceptionHandler = uncaughtExceptionHandler4;
|
||||
// re-entrant check
|
||||
Promise.reject(new Error('err4'));
|
||||
}
|
||||
|
||||
function fireErr5() {
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
|
||||
underlyingExceptionHandler = () => {};
|
||||
const rejectionHandler5 = common.mustCall((err) => {
|
||||
assert.strictEqual(err.message, 'err5');
|
||||
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
|
||||
process.off('unhandledRejection', rejectionHandler5);
|
||||
}, 1);
|
||||
process.on('unhandledRejection', rejectionHandler5);
|
||||
const makeOrphan = vm.compileFunction(`(${String(() => {
|
||||
async function main() {
|
||||
await null;
|
||||
Promise.resolve().then(() => {
|
||||
throw new Error('err5');
|
||||
});
|
||||
}
|
||||
main();
|
||||
})})()`);
|
||||
makeOrphan();
|
||||
}
|
||||
|
||||
fireErr1();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue