diff --git a/lib/internal/debugger/inspect_repl.js b/lib/internal/debugger/inspect_repl.js index b6f8e32e806dfe..630069f9acfc4c 100644 --- a/lib/internal/debugger/inspect_repl.js +++ b/lib/internal/debugger/inspect_repl.js @@ -380,6 +380,7 @@ function createRepl(inspector) { let selectedFrame; let exitDebugRepl; let contextLineNumber = 2; + let pendingInitialPauseDisplay; function resetOnStart() { knownScripts = {}; @@ -890,11 +891,32 @@ function createRepl(inspector) { }); } + function shouldWaitForInitialPauseDisplay() { + return inspector.child !== null && inspector.child !== undefined; + } + + function waitForInitialPauseDisplay() { + let resolve; + const promise = new Promise((res) => { + resolve = res; + }); + pendingInitialPauseDisplay = { promise, resolve }; + return pendingInitialPauseDisplay; + } + + function resolveInitialPauseDisplay() { + if (pendingInitialPauseDisplay) { + pendingInitialPauseDisplay.resolve(); + pendingInitialPauseDisplay = null; + } + } + Debugger.on('paused', ({ callFrames, reason /* , hitBreakpoints */ }) => { if (process.env.NODE_INSPECT_RESUME_ON_START === '1' && reason === 'Break on start') { debuglog('Paused on start, but NODE_INSPECT_RESUME_ON_START' + ' environment variable is set to 1, resuming'); + resolveInitialPauseDisplay(); inspector.client.callMethod('Debugger.resume'); return; } @@ -910,7 +932,7 @@ function createRepl(inspector) { const header = `${breakType} in ${scriptUrl}:${lineNumber + 1}`; - inspector.suspendReplWhile(() => + const pauseDisplay = inspector.suspendReplWhile(() => PromisePrototypeThen( SafePromiseAllReturnArrayLike([formatWatchers(true), selectedFrame.list(contextLineNumber)]), ({ 0: watcherList, 1: context }) => { @@ -919,6 +941,11 @@ function createRepl(inspector) { inspect(context); print(`${header}\n${breakContext}`); })); + PromisePrototypeThen( + pauseDisplay, + resolveInitialPauseDisplay, + resolveInitialPauseDisplay, + ); }); function handleResumed() { @@ -947,7 +974,18 @@ function createRepl(inspector) { async function runAndInit() { await inspector.run(); - await initAfterStart(); + const initialPauseDisplay = shouldWaitForInitialPauseDisplay() ? + waitForInitialPauseDisplay() : null; + try { + await initAfterStart(); + if (initialPauseDisplay) { + await initialPauseDisplay.promise; + } + } finally { + if (pendingInitialPauseDisplay === initialPauseDisplay) { + pendingInitialPauseDisplay = null; + } + } } function initializeContext(context) { diff --git a/test/parallel/test-debugger-run-restart-init.js b/test/parallel/test-debugger-run-restart-init.js index 78f237353baf8c..8d8354b0d471ae 100644 --- a/test/parallel/test-debugger-run-restart-init.js +++ b/test/parallel/test-debugger-run-restart-init.js @@ -36,6 +36,10 @@ function createAgent(domain, calls, gates) { agent.setBlackboxPatterns = method('setBlackboxPatterns'); agent.setPauseOnExceptions = method('setPauseOnExceptions'); agent.runIfWaitingForDebugger = method('runIfWaitingForDebugger'); + agent.getScriptSource = async () => { + calls.push(`${domain}.getScriptSource`); + return { scriptSource: 'let x = 1;\nx = x + 1;\n' }; + }; return agent; } @@ -56,7 +60,30 @@ function evalCommand(repl, command) { }); } -async function assertCommandWaitsForInit(repl, command, gate, calls) { +function emitInitialPause(inspector) { + inspector.Debugger.emit('paused', { + reason: 'Break on start', + callFrames: [{ + callFrameId: 'call-frame-id', + functionName: '', + location: { + scriptId: '1', + lineNumber: 0, + columnNumber: 0, + }, + scopeChain: [], + }], + }); +} + +async function assertCommandWaitsForInitAndPauseDisplay( + repl, + command, + gate, + pauseDisplayGate, + calls, + inspector, +) { let settled = false; const promise = evalCommand(repl, command).then(() => { settled = true; @@ -70,6 +97,22 @@ async function assertCommandWaitsForInit(repl, command, gate, calls) { ); gate.resolve(); + await new Promise(setImmediate); + assert.strictEqual( + settled, + false, + `${command} resolved before initial pause was displayed: ${calls}`, + ); + + emitInitialPause(inspector); + await new Promise(setImmediate); + assert.strictEqual( + settled, + false, + `${command} resolved before pause display completed: ${calls}`, + ); + + pauseDisplayGate.resolve(); await promise; assert.strictEqual(settled, true); } @@ -78,17 +121,26 @@ async function assertCommandWaitsForInit(repl, command, gate, calls) { const calls = []; const runGate = createGate(); const restartGate = createGate(); + const runPauseDisplayGate = createGate(); + const restartPauseDisplayGate = createGate(); const gates = [null, runGate, restartGate]; + const pauseDisplayGates = [runPauseDisplayGate, restartPauseDisplayGate]; const inspector = { + child: null, client: new EventEmitter(), domainNames: ['Debugger', 'HeapProfiler', 'Profiler', 'Runtime'], stdin: new PassThrough(), stdout: new PassThrough(), run: common.mustCall(async () => { calls.push('inspector.run'); + inspector.child = {}; }, 2), + print(text, addNewline = true) { + inspector.stdout.write(addNewline ? `${text}\n` : text); + }, suspendReplWhile(fn) { - return fn(); + const pauseDisplayGate = pauseDisplayGates.shift(); + return Promise.resolve(fn()).then(() => pauseDisplayGate.promise); }, }; @@ -98,8 +150,22 @@ async function assertCommandWaitsForInit(repl, command, gate, calls) { const repl = await createRepl(inspector)(); - await assertCommandWaitsForInit(repl, 'run', runGate, calls); - await assertCommandWaitsForInit(repl, 'restart', restartGate, calls); + await assertCommandWaitsForInitAndPauseDisplay( + repl, + 'run', + runGate, + runPauseDisplayGate, + calls, + inspector, + ); + await assertCommandWaitsForInitAndPauseDisplay( + repl, + 'restart', + restartGate, + restartPauseDisplayGate, + calls, + inspector, + ); assert.deepStrictEqual( calls.filter((call) => (