child_process: clear timeout timer on spawn-time error too

When spawn() fails at the OS level (ENOENT, EACCES, EAGAIN, EMFILE,
ENFILE), the resulting ChildProcess only ever emits 'error', never
'exit'. The `timeout` option's cleanup only listened for 'exit', so
the timer stayed armed for the full `timeout` duration on any
spawn-time failure, holding the event loop open well after the
promise/callback had already settled via 'error'.

Clear the timer on 'error' as well as 'exit'.

Fixes: https://github.com/nodejs/node/issues/65504
Signed-off-by: kishore280 <maheshwarankishore@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65506
Reviewed-By: Aviv Keller <me@aviv.sh>
Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
kishore280 authored and Antoine du Hamel committed 2026-09-18 15:33:33 +02:00
1 parent b7406b0a9b
commit c4381765fe
2 files changed
+21 -1

No files matched your search

+2 -1
View File
@@ -824,7 +824,8 @@ function spawn(file, args, options) {
}
}, options.timeout);
child.once('exit', () => {
// 'close' always fires after 'exit', or after 'error' on a spawn failure.
child.once('close', () => {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
@@ -0,0 +1,19 @@
'use strict';
// Measures the child's actual exit time, not just its 'error' event.
// The outer spawnSync timeout catches a leaked inner timer.
const common = require('../common');
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
const bugStallMs = common.platformTimeout(10000);
const outerTimeoutMs = common.platformTimeout(2000);
spawnSyncAndExitWithoutError(process.execPath, ['-e', `
const { spawn } = require('child_process');
const cp = spawn(process.execPath, ['--version'], {
cwd: '/nonexistent/path/that/should/never/exist',
timeout: ${bugStallMs},
});
cp.on('error', () => {});
`], { timeout: outerTimeoutMs });