test: fix flaky test-watch-mode-inspect timeout

This test randomly times out (~120s) on CI due to a race condition
between child-process restart (triggered by touching the watched file)
and the second inspector-session connection.

The old code used an interval-based restart (write every 500ms) and
a 'gettingDebuggedPid' flag to pause writes during a session. This
still left a race window where getDebuggedPid() would attempt to
connect the inspector via HTTP GET /json/list + WebSocket upgrade
either before the new child was ready (empty target list) or after
the old session was being destroyed, causing the promise to hang.

Fix: Replace the interval with a single write that triggers exactly
one restart, then wait for the restarted child's 'safe to debug now'
stdout line before connecting the second inspector session. This
eliminates the race by ensuring the new child process and its
inspector session are fully ready before any connection attempt.

Removes the now-unused gettingDebuggedPid flag and the pending
setTimeout delay that was needed as a backstop for the interval.

Fixes: https://github.com/nodejs/node/issues/44898
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/63361
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
This commit is contained in:
Matteo Collina authored and GitHub committed 2026-05-18 17:45:55 +00:00
1 parent 3b19867caa
commit f2aaf63846
1 file changed
+12 -15
+12 -15
View File
@@ -3,7 +3,6 @@ import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { describe, it } from 'node:test';
import { writeFileSync, readFileSync } from 'node:fs';
import { setTimeout } from 'node:timers/promises';
import { NodeInstance } from '../common/inspector-helper.js';
@@ -12,10 +11,7 @@ if (common.isIBMi)
common.skipIfInspectorDisabled();
let gettingDebuggedPid = false;
async function getDebuggedPid(instance, waitForLog = true) {
gettingDebuggedPid = true;
const session = await instance.connectInspectorSession();
await session.send({ method: 'Runtime.enable' });
if (waitForLog) {
@@ -25,20 +21,23 @@ async function getDebuggedPid(instance, waitForLog = true) {
'method': 'Runtime.evaluate', 'params': { 'expression': 'process.pid' },
})).result;
session.disconnect();
gettingDebuggedPid = false;
return innerPid;
}
function restart(file) {
// Triggers a single restart and resolves when the restarted child prints "safe to debug now".
function restartAndWaitForReady(file, instance) {
const ready = new Promise((resolve) => {
instance.on('stdout', (data) => {
if (data?.includes('safe to debug now')) {
resolve();
}
});
});
writeFileSync(file, readFileSync(file));
const interval = setInterval(() => {
if (!gettingDebuggedPid) {
writeFileSync(file, readFileSync(file));
}
}, common.platformTimeout(500));
return () => clearInterval(interval);
return ready;
}
describe('watch mode - inspect', () => {
it('should start debugger on inner process', async () => {
const file = fixtures.path('watch-mode/inspect.js');
@@ -51,11 +50,9 @@ describe('watch mode - inspect', () => {
const pids = [instance.pid];
pids.push(await getDebuggedPid(instance));
instance.resetPort();
const stopRestarting = restart(file);
await restartAndWaitForReady(file, instance);
pids.push(await getDebuggedPid(instance));
stopRestarting();
await setTimeout(common.platformTimeout(500));
await instance.kill();
// There should be a process per restart and one per parent process.