Files
node/test/parallel/test-inspector-async-hook-after-done.js
Joyee Cheung 3a35e48ea7 inspector: avoid calling into JS from V8 interrupts
Our inspector implementation dispatches inspector messages from
a V8 interrupt handler, so they could be handled during an arbitrary
point of JS execution where re-calling into another irrelevant JS
code is not safe.

This patch tracks V8 interrupt state in this case and rewrite the
async hook toggling as state reconciliation, so requests only record
the desired state, which is applied once calling into JS is possible
and safe, and the actual invocation is deferred to an immediate when
inside an interrupt. This simplifies the previous mechanism and
makes re-entracy and early termination safer.

Drive-by: skip installing command line API extensions during
teardown when calling into JS is no longer safe.

Signed-off-by: Joyee Cheung <joyeec9h3@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65028
Refs: https://issues.chromium.org/u/1/issues/42212250
Refs: https://chromium-review.googlesource.com/c/v8/v8/+/8173727
Refs: https://github.com/nodejs/node/pull/26935
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
2026-08-14 16:14:48 +00:00

69 lines
2.1 KiB
JavaScript

'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('This test only works on a main thread');
}
const assert = require('assert');
const { Worker } = require('worker_threads');
const { Session } = require('inspector');
const session = new Session();
let done = false;
function onAttachToWorker({ params: { sessionId } }) {
let id = 1;
function postToWorkerInspector(method, params) {
session.post('NodeWorker.sendMessageToWorker', {
sessionId,
message: JSON.stringify({ id: id++, method, params })
}, () => console.log(`Message ${method} received the response`));
}
// Wait for the notification
function onMessageReceived({ params: { message } }) {
if (!message ||
JSON.parse(message).method !== 'NodeRuntime.waitingForDisconnect') {
session.once('NodeWorker.receivedMessageFromWorker', onMessageReceived);
return;
}
// Force a call to node::inspector::Agent::SyncAsyncHookState by changing
// the async call stack depth
postToWorkerInspector('Debugger.setAsyncCallStackDepth', { maxDepth: 1 });
// This is were the original crash happened
session.post('NodeWorker.detach', { sessionId }, () => {
done = true;
});
}
onMessageReceived({ params: { message: null } });
// Enable the debugger, otherwise setAsyncCallStackDepth does nothing
postToWorkerInspector('Debugger.enable');
// Start waiting for disconnect notification
postToWorkerInspector('NodeRuntime.notifyWhenWaitingForDisconnect',
{ enabled: true });
// start worker
postToWorkerInspector('Runtime.runIfWaitingForDebugger');
}
session.connect();
session.on('NodeWorker.attachedToWorker', common.mustCall(onAttachToWorker));
session.post('NodeWorker.enable', { waitForDebuggerOnStart: true }, common.mustCall(() => {
new Worker('console.log("Worker is done")', { eval: true })
.once('exit', common.mustCall(() => {
setTimeout(common.mustCall(() => {
assert.strictEqual(done, true);
console.log('Test is done');
}), 0);
}));
}));