time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

kill.js (2983B)


      1 import os from 'node:os';
      2 import onExit from 'signal-exit';
      3 
      4 const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5;
      5 
      6 // Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior
      7 export const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => {
      8 	const killResult = kill(signal);
      9 	setKillTimeout(kill, signal, options, killResult);
     10 	return killResult;
     11 };
     12 
     13 const setKillTimeout = (kill, signal, options, killResult) => {
     14 	if (!shouldForceKill(signal, options, killResult)) {
     15 		return;
     16 	}
     17 
     18 	const timeout = getForceKillAfterTimeout(options);
     19 	const t = setTimeout(() => {
     20 		kill('SIGKILL');
     21 	}, timeout);
     22 
     23 	// Guarded because there's no `.unref()` when `execa` is used in the renderer
     24 	// process in Electron. This cannot be tested since we don't run tests in
     25 	// Electron.
     26 	// istanbul ignore else
     27 	if (t.unref) {
     28 		t.unref();
     29 	}
     30 };
     31 
     32 const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => isSigterm(signal) && forceKillAfterTimeout !== false && killResult;
     33 
     34 const isSigterm = signal => signal === os.constants.signals.SIGTERM
     35 		|| (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM');
     36 
     37 const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => {
     38 	if (forceKillAfterTimeout === true) {
     39 		return DEFAULT_FORCE_KILL_TIMEOUT;
     40 	}
     41 
     42 	if (!Number.isFinite(forceKillAfterTimeout) || forceKillAfterTimeout < 0) {
     43 		throw new TypeError(`Expected the \`forceKillAfterTimeout\` option to be a non-negative integer, got \`${forceKillAfterTimeout}\` (${typeof forceKillAfterTimeout})`);
     44 	}
     45 
     46 	return forceKillAfterTimeout;
     47 };
     48 
     49 // `childProcess.cancel()`
     50 export const spawnedCancel = (spawned, context) => {
     51 	const killResult = spawned.kill();
     52 
     53 	if (killResult) {
     54 		context.isCanceled = true;
     55 	}
     56 };
     57 
     58 const timeoutKill = (spawned, signal, reject) => {
     59 	spawned.kill(signal);
     60 	reject(Object.assign(new Error('Timed out'), {timedOut: true, signal}));
     61 };
     62 
     63 // `timeout` option handling
     64 export const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => {
     65 	if (timeout === 0 || timeout === undefined) {
     66 		return spawnedPromise;
     67 	}
     68 
     69 	let timeoutId;
     70 	const timeoutPromise = new Promise((resolve, reject) => {
     71 		timeoutId = setTimeout(() => {
     72 			timeoutKill(spawned, killSignal, reject);
     73 		}, timeout);
     74 	});
     75 
     76 	const safeSpawnedPromise = spawnedPromise.finally(() => {
     77 		clearTimeout(timeoutId);
     78 	});
     79 
     80 	return Promise.race([timeoutPromise, safeSpawnedPromise]);
     81 };
     82 
     83 export const validateTimeout = ({timeout}) => {
     84 	if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) {
     85 		throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`);
     86 	}
     87 };
     88 
     89 // `cleanup` option handling
     90 export const setExitHandler = async (spawned, {cleanup, detached}, timedPromise) => {
     91 	if (!cleanup || detached) {
     92 		return timedPromise;
     93 	}
     94 
     95 	const removeExitHandler = onExit(() => {
     96 		spawned.kill();
     97 	});
     98 
     99 	return timedPromise.finally(() => {
    100 		removeExitHandler();
    101 	});
    102 };