diff --git a/index.js b/index.js index b937e39c75..fe0d0a2148 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,7 @@ const onExit = require('signal-exit'); const stdio = require('./lib/stdio'); const TEN_MEGABYTES = 1000 * 1000 * 10; +const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5; const SPACES_REGEXP = / +/g; @@ -224,18 +225,27 @@ function setKillTimeout(kill, signal, options, killResult) { return; } - const forceKillAfter = Number.isInteger(options.forceKillAfter) ? - options.forceKillAfter : - 5000; - setTimeout(() => kill('SIGKILL'), forceKillAfter).unref(); + const timeout = getForceKillAfterTimeout(options); + setTimeout(() => { + kill('SIGKILL'); + }, timeout).unref(); } -function shouldForceKill(signal, options, killResult) { - return ((typeof signal === 'string' && - signal.toUpperCase() === 'SIGTERM') || - signal === os.constants.signals.SIGTERM) && - options.forceKill !== false && - killResult; +function shouldForceKill(signal, {forceKill}, killResult) { + return isSigterm(signal) && forceKill !== false && killResult; +} + +function isSigterm(signal) { + return signal === os.constants.signals.SIGTERM || + (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM'); +} + +function getForceKillAfterTimeout({forceKillAfter = DEFAULT_FORCE_KILL_TIMEOUT}) { + if (!Number.isInteger(forceKillAfter) || forceKillAfter < 0) { + throw new TypeError(`Expected the \`forceKillAfter\` option to be a non-negative integer, got \`${forceKillAfter}\` (${typeof forceKillAfter})`); + } + + return forceKillAfter; } const execa = (file, args, options) => { diff --git a/test.js b/test.js index a392ca2403..718e32a0fb 100644 --- a/test.js +++ b/test.js @@ -175,6 +175,18 @@ if (process.platform !== 'win32') { const {signal} = await t.throwsAsync(subprocess); t.is(signal, 'SIGKILL'); }); + + test('.kill() `forceKillAfter` should not be a float', t => { + t.throws(() => { + execa('noop').kill('SIGTERM', {forceKillAfter: 0.5}); + }, {instanceOf: TypeError, message: /non-negative integer/}); + }); + + test('.kill() `forceKillAfter` should not be negative', t => { + t.throws(() => { + execa('noop').kill('SIGTERM', {forceKillAfter: -1}); + }, {instanceOf: TypeError, message: /non-negative integer/}); + }); } test('stripFinalNewline: true', async t => { @@ -289,8 +301,8 @@ test('execa() returns a promise with kill() and pid', t => { }); test('child_process.spawn() errors are propagated', async t => { - const {exitCodeName} = await t.throwsAsync(execa('noop', {uid: -1})); - t.is(exitCodeName, process.platform === 'win32' ? 'ENOTSUP' : 'EINVAL'); + const {failed} = await t.throwsAsync(execa('noop', {uid: -1})); + t.true(failed); }); test('child_process.spawnSync() errors are propagated with a correct shape', t => {