diff --git a/lib/child_process.js b/lib/child_process.js index 0e3e04af0d6..d70790c3c8f 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -824,12 +824,16 @@ function spawn(file, args, options) { } }, options.timeout); - child.once('exit', () => { + const clearSpawnTimeout = () => { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } - }); + }; + + // A spawn-time failure only emits 'error', never 'exit'. + child.once('exit', clearSpawnTimeout); + child.once('error', clearSpawnTimeout); } if (options.signal) { diff --git a/test/parallel/test-child-process-spawn-timeout-clear-on-error.js b/test/parallel/test-child-process-spawn-timeout-clear-on-error.js new file mode 100644 index 00000000000..74bf40afc3d --- /dev/null +++ b/test/parallel/test-child-process-spawn-timeout-clear-on-error.js @@ -0,0 +1,21 @@ +'use strict'; + +// The timeout timer must clear on a spawn-time 'error', not just 'exit'. + +const common = require('../common'); +const assert = require('assert'); +const { spawn } = require('child_process'); + +const start = Date.now(); + +const cp = spawn(process.execPath, ['--version'], { + cwd: '/nonexistent/path/that/should/never/exist', + timeout: common.platformTimeout(10000), +}); + +cp.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOENT'); + assert.ok(Date.now() - start < 2000); +})); + +cp.on('exit', common.mustNotCall());