2020-11-10 22:12:02 +00:00
'use strict' ;
const os = require ( 'os' ) ;
const onExit = require ( 'signal-exit' ) ;
const DEFAULT _FORCE _KILL _TIMEOUT = 1000 * 5 ;
// Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior
const spawnedKill = ( kill , signal = 'SIGTERM' , options = { } ) => {
const killResult = kill ( signal ) ;
setKillTimeout ( kill , signal , options , killResult ) ;
return killResult ;
} ;
const setKillTimeout = ( kill , signal , options , killResult ) => {
if ( ! shouldForceKill ( signal , options , killResult ) ) {
return ;
}
const timeout = getForceKillAfterTimeout ( options ) ;
const t = setTimeout ( ( ) => {
kill ( 'SIGKILL' ) ;
} , timeout ) ;
// Guarded because there's no `.unref()` when `execa` is used in the renderer
// process in Electron. This cannot be tested since we don't run tests in
// Electron.
// istanbul ignore else
if ( t . unref ) {
t . unref ( ) ;
}
} ;
const shouldForceKill = ( signal , { forceKillAfterTimeout } , killResult ) => {
return isSigterm ( signal ) && forceKillAfterTimeout !== false && killResult ;
} ;
const isSigterm = signal => {
return signal === os . constants . signals . SIGTERM ||
( typeof signal === 'string' && signal . toUpperCase ( ) === 'SIGTERM' ) ;
} ;
const getForceKillAfterTimeout = ( { forceKillAfterTimeout = true } ) => {
if ( forceKillAfterTimeout === true ) {
return DEFAULT _FORCE _KILL _TIMEOUT ;
}
if ( ! Number . isFinite ( forceKillAfterTimeout ) || forceKillAfterTimeout < 0 ) {
throw new TypeError ( ` Expected the \` forceKillAfterTimeout \` option to be a non-negative integer, got \` ${ forceKillAfterTimeout } \` ( ${ typeof forceKillAfterTimeout } ) ` ) ;
}
return forceKillAfterTimeout ;
} ;
// `childProcess.cancel()`
const spawnedCancel = ( spawned , context ) => {
const killResult = spawned . kill ( ) ;
if ( killResult ) {
context . isCanceled = true ;
}
} ;
const timeoutKill = ( spawned , signal , reject ) => {
spawned . kill ( signal ) ;
reject ( Object . assign ( new Error ( 'Timed out' ) , { timedOut : true , signal } ) ) ;
} ;
// `timeout` option handling
const setupTimeout = ( spawned , { timeout , killSignal = 'SIGTERM' } , spawnedPromise ) => {
if ( timeout === 0 || timeout === undefined ) {
return spawnedPromise ;
}
let timeoutId ;
const timeoutPromise = new Promise ( ( resolve , reject ) => {
timeoutId = setTimeout ( ( ) => {
timeoutKill ( spawned , killSignal , reject ) ;
} , timeout ) ;
} ) ;
const safeSpawnedPromise = spawnedPromise . finally ( ( ) => {
clearTimeout ( timeoutId ) ;
} ) ;
return Promise . race ( [ timeoutPromise , safeSpawnedPromise ] ) ;
} ;
2021-10-15 20:41:21 +00:00
const validateTimeout = ( { timeout } ) => {
if ( timeout !== undefined && ( ! Number . isFinite ( timeout ) || timeout < 0 ) ) {
throw new TypeError ( ` Expected the \` timeout \` option to be a non-negative integer, got \` ${ timeout } \` ( ${ typeof timeout } ) ` ) ;
}
} ;
2020-11-10 22:12:02 +00:00
// `cleanup` option handling
const setExitHandler = async ( spawned , { cleanup , detached } , timedPromise ) => {
if ( ! cleanup || detached ) {
return timedPromise ;
}
const removeExitHandler = onExit ( ( ) => {
spawned . kill ( ) ;
} ) ;
return timedPromise . finally ( ( ) => {
removeExitHandler ( ) ;
} ) ;
} ;
module . exports = {
spawnedKill ,
spawnedCancel ,
setupTimeout ,
2021-10-15 20:41:21 +00:00
validateTimeout ,
2020-11-10 22:12:02 +00:00
setExitHandler
} ;