mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
The inspector can accept a connection before an --inspect-brk target enters its frontend wait. Runtime.runIfWaitingForDebugger can then be handled too early, allowing the target to subsequently block forever. Wait for NodeRuntime.waitingForDebugger before initializing and releasing launched targets. Race the handshake against disconnects and apply it to both interactive and probe startup. Refs: https://github.com/nodejs/node/issues/64116 Assisted-by: codex:gpt-5.6-sol Co-authored-by: Archkon <180910180+Archkon@users.noreply.github.com> Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com> Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65194 Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
184 lines
4.8 KiB
JavaScript
184 lines
4.8 KiB
JavaScript
// Flags: --expose-internals
|
|
'use strict';
|
|
|
|
const common = require('../common');
|
|
|
|
common.skipIfInspectorDisabled();
|
|
|
|
const assert = require('assert');
|
|
const { EventEmitter } = require('events');
|
|
const { PassThrough } = require('stream');
|
|
const createRepl = require('internal/debugger/inspect_repl');
|
|
|
|
function createGate() {
|
|
let resolve;
|
|
const promise = new Promise((res) => {
|
|
resolve = res;
|
|
});
|
|
return { promise, resolve };
|
|
}
|
|
|
|
function createAgent(domain, calls, gates) {
|
|
const agent = new EventEmitter();
|
|
const method = (name) => async () => {
|
|
calls.push(`${domain}.${name}`);
|
|
if (domain === 'Runtime' && name === 'runIfWaitingForDebugger') {
|
|
const gate = gates.shift();
|
|
if (gate) {
|
|
await gate.promise;
|
|
}
|
|
}
|
|
};
|
|
|
|
agent.enable = method('enable');
|
|
agent.setSamplingInterval = method('setSamplingInterval');
|
|
agent.setAsyncCallStackDepth = method('setAsyncCallStackDepth');
|
|
agent.setBlackboxPatterns = method('setBlackboxPatterns');
|
|
agent.setPauseOnExceptions = method('setPauseOnExceptions');
|
|
agent.runIfWaitingForDebugger = method('runIfWaitingForDebugger');
|
|
return agent;
|
|
}
|
|
|
|
function evalCommand(repl, command) {
|
|
return new Promise((resolve, reject) => {
|
|
repl.eval(
|
|
command,
|
|
repl.context,
|
|
'debugger-repl-test',
|
|
(error, result) => {
|
|
if (error) {
|
|
reject(error);
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
async function assertCommandWaitsForInit(repl, command, gate, calls) {
|
|
let settled = false;
|
|
const promise = evalCommand(repl, command).then(() => {
|
|
settled = true;
|
|
});
|
|
|
|
await new Promise(setImmediate);
|
|
assert.strictEqual(
|
|
settled,
|
|
false,
|
|
`${command} resolved before post-connect initialization completed: ${calls}`,
|
|
);
|
|
|
|
gate.resolve();
|
|
await promise;
|
|
assert.strictEqual(settled, true);
|
|
}
|
|
|
|
(async () => {
|
|
const calls = [];
|
|
const runGate = createGate();
|
|
const restartGate = createGate();
|
|
const gates = [null, runGate, restartGate];
|
|
const client = new EventEmitter();
|
|
let nodeRuntimeEnableCount = 0;
|
|
client.callMethod = common.mustCall(async (method) => {
|
|
calls.push(method);
|
|
if (method === 'NodeRuntime.enable') {
|
|
const emitWaiting = () => {
|
|
calls.push('NodeRuntime.waitingForDebugger');
|
|
client.emit('NodeRuntime.waitingForDebugger');
|
|
};
|
|
// Cover notifications arriving both before and after the enable reply.
|
|
if (nodeRuntimeEnableCount++ % 2 === 0) {
|
|
emitWaiting();
|
|
} else {
|
|
setImmediate(emitWaiting);
|
|
}
|
|
} else {
|
|
assert.strictEqual(method, 'NodeRuntime.disable');
|
|
}
|
|
}, 6);
|
|
const inspector = {
|
|
client,
|
|
domainNames: ['Debugger', 'HeapProfiler', 'Profiler', 'Runtime'],
|
|
options: { script: 'debugger-target.js' },
|
|
stdin: new PassThrough(),
|
|
stdout: new PassThrough(),
|
|
run: common.mustCall(async () => {
|
|
calls.push('inspector.run');
|
|
}, 2),
|
|
suspendReplWhile(fn) {
|
|
return fn();
|
|
},
|
|
};
|
|
|
|
for (const domain of inspector.domainNames) {
|
|
inspector[domain] = createAgent(domain, calls, gates);
|
|
}
|
|
|
|
const repl = await createRepl(inspector)();
|
|
|
|
await assertCommandWaitsForInit(repl, 'run', runGate, calls);
|
|
await assertCommandWaitsForInit(repl, 'restart', restartGate, calls);
|
|
|
|
assert.deepStrictEqual(
|
|
calls.filter((call) => (
|
|
call === 'NodeRuntime.enable' ||
|
|
call === 'NodeRuntime.waitingForDebugger' ||
|
|
call === 'NodeRuntime.disable' ||
|
|
call === 'Runtime.runIfWaitingForDebugger'
|
|
)),
|
|
[
|
|
'NodeRuntime.enable',
|
|
'NodeRuntime.waitingForDebugger',
|
|
'NodeRuntime.disable',
|
|
'Runtime.runIfWaitingForDebugger',
|
|
'NodeRuntime.enable',
|
|
'NodeRuntime.waitingForDebugger',
|
|
'NodeRuntime.disable',
|
|
'Runtime.runIfWaitingForDebugger',
|
|
'NodeRuntime.enable',
|
|
'NodeRuntime.waitingForDebugger',
|
|
'NodeRuntime.disable',
|
|
'Runtime.runIfWaitingForDebugger',
|
|
],
|
|
);
|
|
|
|
assert.deepStrictEqual(
|
|
calls.filter((call) => (
|
|
call === 'inspector.run' ||
|
|
call === 'Runtime.runIfWaitingForDebugger'
|
|
)),
|
|
[
|
|
'Runtime.runIfWaitingForDebugger',
|
|
'inspector.run',
|
|
'Runtime.runIfWaitingForDebugger',
|
|
'inspector.run',
|
|
'Runtime.runIfWaitingForDebugger',
|
|
],
|
|
);
|
|
|
|
repl.close();
|
|
|
|
const attachCalls = [];
|
|
const attachClient = new EventEmitter();
|
|
attachClient.callMethod = common.mustNotCall();
|
|
const attachInspector = {
|
|
client: attachClient,
|
|
domainNames: ['Debugger', 'HeapProfiler', 'Profiler', 'Runtime'],
|
|
options: {},
|
|
stdin: new PassThrough(),
|
|
stdout: new PassThrough(),
|
|
suspendReplWhile(fn) {
|
|
return fn();
|
|
},
|
|
};
|
|
|
|
for (const domain of attachInspector.domainNames) {
|
|
attachInspector[domain] = createAgent(domain, attachCalls, []);
|
|
}
|
|
|
|
const attachRepl = await createRepl(attachInspector)();
|
|
attachRepl.close();
|
|
})().then(common.mustCall());
|