mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
The test sends Debugger.setAsyncCallStackDepth in the same batch as Runtime.enable and Debugger.enable, and treats the command response as proof that async stack recording is on. It is not. While the target runs JS the command is dispatched from a V8 interrupt, where Agent::SyncAsyncHookState() cannot call into JS and defers enabling the async hook to an immediate, answering the command anyway. The `debugger` in the polling interval then pauses the target before that immediate runs, and the nested message loop does not process immediates, so setupTimeoutWithBreak() schedules the timer with the hook still disabled and Debugger.paused arrives without asyncStackTrace. Send the command after the first pause instead. Messages dispatched while the target is paused come from the nested message loop rather than an interrupt, so the hook is enabled inline and the response is the barrier the test assumed it to be. Deferring is deliberate: calling into JS from a V8 interrupt is not safe. This corrects an assumption of the test, not the runtime. Signed-off-by: Christian Aurich <christian.aurichzm@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65584 Refs: https://github.com/nodejs/reliability/blob/main/reports/2026-08-26.md Refs: https://github.com/nodejs/node/pull/65028 Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
79 lines
2.9 KiB
JavaScript
79 lines
2.9 KiB
JavaScript
'use strict';
|
|
const common = require('../common');
|
|
common.skipIfInspectorDisabled();
|
|
common.skipIf32Bits();
|
|
const { NodeInstance } = require('../common/inspector-helper.js');
|
|
const assert = require('assert');
|
|
|
|
// Even with --inspect, the default async call stack depth is 0. We need a
|
|
// chance to call Debugger.setAsyncCallStackDepth *before* activating the timer
|
|
// for async stack traces to work.
|
|
const script = `
|
|
process._rawDebug('Waiting until the inspector is activated...');
|
|
const waiting = setInterval(() => { debugger; }, 50);
|
|
|
|
// This function is called by the inspector client (session)
|
|
function setupTimeoutWithBreak() {
|
|
clearInterval(waiting);
|
|
process._rawDebug('Debugger ready, setting up timeout with a break');
|
|
setTimeout(() => { debugger; }, 50);
|
|
}
|
|
`;
|
|
|
|
async function waitForInitialSetup(session) {
|
|
console.error('[test]', 'Waiting for initial setup');
|
|
await session.waitForBreakOnLine(2, '[eval]');
|
|
}
|
|
|
|
// Sent only once the target is paused. While the target runs JS the command
|
|
// is dispatched from a V8 interrupt, where Agent::SyncAsyncHookState() cannot
|
|
// call into JS and defers enabling the async hook to an immediate that the
|
|
// pause then blocks, answering the command anyway. Dispatching from the paused
|
|
// message loop enables the hook inline, so the response is a real barrier.
|
|
async function enableAsyncStackTraces(session) {
|
|
console.error('[test]', 'Enabling async stack traces');
|
|
await session.send({ 'method': 'Debugger.setAsyncCallStackDepth',
|
|
'params': { 'maxDepth': 10 } });
|
|
}
|
|
|
|
async function setupTimeoutForStackTrace(session) {
|
|
console.error('[test]', 'Setting up timeout for async stack trace');
|
|
await session.send([
|
|
{ 'method': 'Runtime.evaluate',
|
|
'params': { expression: 'setupTimeoutWithBreak()' } },
|
|
{ 'method': 'Debugger.resume' },
|
|
]);
|
|
}
|
|
|
|
async function checkAsyncStackTrace(session) {
|
|
console.error('[test]', 'Verify basic properties of asyncStackTrace');
|
|
const paused = await session.waitForBreakOnLine(8, '[eval]');
|
|
assert(paused.params.asyncStackTrace,
|
|
`${Object.keys(paused.params)} contains "asyncStackTrace" property`);
|
|
assert(paused.params.asyncStackTrace.description, 'Timeout');
|
|
assert(paused.params.asyncStackTrace.callFrames
|
|
.some((frame) => frame.functionName === 'setupTimeoutWithBreak'));
|
|
}
|
|
|
|
async function runTests() {
|
|
const instance = new NodeInstance(['--inspect=0'], script);
|
|
const session = await instance.connectInspectorSession();
|
|
await session.send([
|
|
{ 'method': 'Runtime.enable' },
|
|
{ 'method': 'Debugger.enable' },
|
|
{ 'method': 'Debugger.setBlackboxPatterns',
|
|
'params': { 'patterns': [] } },
|
|
]);
|
|
|
|
await waitForInitialSetup(session);
|
|
await enableAsyncStackTraces(session);
|
|
await setupTimeoutForStackTrace(session);
|
|
await checkAsyncStackTrace(session);
|
|
|
|
console.error('[test]', 'Stopping child instance');
|
|
session.disconnect();
|
|
instance.kill();
|
|
}
|
|
|
|
runTests();
|