123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- '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]);
- };
- 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})`);
- }
- };
- // `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,
- validateTimeout,
- setExitHandler
- };
|