Skip to content

Commit 59f99df

Browse files
committed
debugger: wait for initial break before starting REPL
Create the initial-break deferred before resuming the target. A delayed Debugger.paused notification can no longer arrive after the REPL prompt. Resolve the deferred on client close when no pause occurs. Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent c558c8e commit 59f99df

2 files changed

Lines changed: 96 additions & 32 deletions

File tree

lib/internal/debugger/inspect_repl.js

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,6 @@ function createRepl(inspector) {
386386
let exitDebugRepl;
387387
let contextLineNumber = 2;
388388
let initialBreakRender;
389-
let waitForInitialBreakRender = false;
390389

391390
function resetOnStart() {
392391
knownScripts = {};
@@ -897,13 +896,6 @@ function createRepl(inspector) {
897896
});
898897
}
899898

900-
function createInitialBreakRenderPromise(pauseRender) {
901-
const { promise, resolve, reject } = PromiseWithResolvers();
902-
initialBreakRender = promise;
903-
PromisePrototypeThen(pauseRender, resolve, reject);
904-
return promise;
905-
}
906-
907899
Debugger.on('paused', ({ callFrames, reason /* , hitBreakpoints */ }) => {
908900
if (process.env.NODE_INSPECT_RESUME_ON_START === '1' &&
909901
reason === 'Break on start') {
@@ -934,8 +926,10 @@ function createRepl(inspector) {
934926
print(`${header}\n${breakContext}`);
935927
}));
936928

937-
if (waitForInitialBreakRender && !initialBreakRender) {
938-
createInitialBreakRenderPromise(pauseRender);
929+
if (initialBreakRender) {
930+
const { resolve, reject } = initialBreakRender;
931+
initialBreakRender = null;
932+
PromisePrototypeThen(pauseRender, resolve, reject);
939933
}
940934
});
941935

@@ -1204,23 +1198,36 @@ function createRepl(inspector) {
12041198
}
12051199

12061200
async function initAfterStart() {
1207-
waitForInitialBreakRender =
1201+
const waitForInitialBreakRender =
12081202
!!inspector.options?.script &&
12091203
process.env.NODE_INSPECT_RESUME_ON_START !== '1';
1210-
await Runtime.enable();
1211-
await Profiler.enable();
1212-
await Profiler.setSamplingInterval({ interval: 100 });
1213-
await Debugger.enable();
1214-
await Debugger.setAsyncCallStackDepth({ maxDepth: 0 });
1215-
await Debugger.setBlackboxPatterns({ patterns: [] });
1216-
await Debugger.setPauseOnExceptions({ state: pauseOnExceptionState });
1217-
await restoreBreakpoints();
1218-
await Runtime.runIfWaitingForDebugger();
1219-
await PromiseResolve();
1220-
waitForInitialBreakRender = false;
1221-
const initialBreakRenderPromise = initialBreakRender;
1222-
initialBreakRender = null;
1223-
await initialBreakRenderPromise;
1204+
const initialBreak = waitForInitialBreakRender ?
1205+
PromiseWithResolvers() : null;
1206+
initialBreakRender = initialBreak;
1207+
const onClientClose = initialBreak?.resolve;
1208+
if (onClientClose) {
1209+
inspector.client.once('close', onClientClose);
1210+
}
1211+
1212+
try {
1213+
await Runtime.enable();
1214+
await Profiler.enable();
1215+
await Profiler.setSamplingInterval({ interval: 100 });
1216+
await Debugger.enable();
1217+
await Debugger.setAsyncCallStackDepth({ maxDepth: 0 });
1218+
await Debugger.setBlackboxPatterns({ patterns: [] });
1219+
await Debugger.setPauseOnExceptions({ state: pauseOnExceptionState });
1220+
await restoreBreakpoints();
1221+
await Runtime.runIfWaitingForDebugger();
1222+
await initialBreak?.promise;
1223+
} finally {
1224+
if (onClientClose) {
1225+
inspector.client.removeListener('close', onClientClose);
1226+
}
1227+
if (initialBreakRender === initialBreak) {
1228+
initialBreakRender = null;
1229+
}
1230+
}
12241231
}
12251232

12261233
return async function startRepl() {

test/parallel/test-debugger-run-restart-init.js

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ function evalCommand(repl, command) {
5656
});
5757
}
5858

59-
async function assertCommandWaitsForInit(repl, command, gate, calls) {
59+
async function assertCommandWaitsForInit(
60+
repl, command, initGate, pauseGate, renderGate, calls) {
6061
let settled = false;
6162
const promise = evalCommand(repl, command).then(() => {
6263
settled = true;
@@ -69,7 +70,23 @@ async function assertCommandWaitsForInit(repl, command, gate, calls) {
6970
`${command} resolved before post-connect initialization completed: ${calls}`,
7071
);
7172

72-
gate.resolve();
73+
initGate.resolve();
74+
await new Promise(setImmediate);
75+
assert.strictEqual(
76+
settled,
77+
false,
78+
`${command} resolved before the initial break was rendered: ${calls}`,
79+
);
80+
81+
pauseGate.resolve();
82+
await new Promise(setImmediate);
83+
assert.strictEqual(
84+
settled,
85+
false,
86+
`${command} resolved before the initial break render completed: ${calls}`,
87+
);
88+
89+
renderGate.resolve();
7390
await promise;
7491
assert.strictEqual(settled, true);
7592
}
@@ -79,27 +96,67 @@ async function assertCommandWaitsForInit(repl, command, gate, calls) {
7996
const runGate = createGate();
8097
const restartGate = createGate();
8198
const gates = [null, runGate, restartGate];
99+
const initialRenderGate = createGate();
100+
const runRenderGate = createGate();
101+
const restartRenderGate = createGate();
102+
const renderGates = [initialRenderGate, runRenderGate, restartRenderGate];
82103
const inspector = {
83104
client: new EventEmitter(),
84105
domainNames: ['Debugger', 'HeapProfiler', 'Profiler', 'Runtime'],
106+
options: { script: 'fixture.js' },
85107
stdin: new PassThrough(),
86108
stdout: new PassThrough(),
87109
run: common.mustCall(async () => {
88110
calls.push('inspector.run');
89111
}, 2),
112+
print(text, addNewline = true) {
113+
this.stdout.write(`${text}${addNewline ? '\n' : ''}`);
114+
},
90115
suspendReplWhile(fn) {
91116
return fn();
92117
},
93118
};
94119

120+
const pausedEvent = {
121+
callFrames: [{
122+
functionName: '',
123+
location: { scriptId: '1', lineNumber: 0, columnNumber: 0 },
124+
scopeChain: [],
125+
}],
126+
reason: 'other',
127+
};
95128
for (const domain of inspector.domainNames) {
96129
inspector[domain] = createAgent(domain, calls, gates);
97130
}
98-
99-
const repl = await createRepl(inspector)();
100-
101-
await assertCommandWaitsForInit(repl, 'run', runGate, calls);
102-
await assertCommandWaitsForInit(repl, 'restart', restartGate, calls);
131+
inspector.Debugger.getScriptSource = async () => {
132+
await renderGates.shift().promise;
133+
return { scriptSource: 'const value = 1;\n' };
134+
};
135+
const emitPause = common.mustCall(() => {
136+
inspector.Debugger.emit('paused', pausedEvent);
137+
}, 3);
138+
const initialPauseGate = { resolve: emitPause };
139+
const runPauseGate = { resolve: emitPause };
140+
const restartPauseGate = { resolve: emitPause };
141+
142+
let replSettled = false;
143+
const replPromise = createRepl(inspector)().then((repl) => {
144+
replSettled = true;
145+
return repl;
146+
});
147+
await new Promise(setImmediate);
148+
assert.strictEqual(replSettled, false);
149+
initialPauseGate.resolve();
150+
await new Promise(setImmediate);
151+
assert.strictEqual(replSettled, false);
152+
initialRenderGate.resolve();
153+
const repl = await replPromise;
154+
155+
await assertCommandWaitsForInit(
156+
repl, 'run', runGate, runPauseGate, runRenderGate, calls);
157+
await assertCommandWaitsForInit(
158+
repl, 'restart', restartGate, restartPauseGate,
159+
restartRenderGate, calls);
103160

104161
assert.deepStrictEqual(
105162
calls.filter((call) => (

0 commit comments

Comments
 (0)