Skip to content

Latest commit

 

History

History
210 lines (149 loc) · 7.44 KB

termination.md

File metadata and controls

210 lines (149 loc) · 7.44 KB
execa logo

🏁 Termination

Alternatives

Terminating a subprocess ends it abruptly. This prevents rolling back the subprocess' operations and leaves them incomplete. When possible, graceful exits should be preferred, such as:

Canceling

The cancelSignal option can be used to cancel a subprocess. When abortController is aborted, a SIGTERM signal is sent to the subprocess.

import {execa} from 'execa';

const abortController = new AbortController();

setTimeout(() => {
	abortController.abort();
}, 5000);

try {
	await execa({cancelSignal: abortController.signal})`npm run build`;
} catch (error) {
	if (error.isCanceled) {
		console.error('Aborted by cancelSignal.');
	}

	throw error;
}

Timeout

If the subprocess lasts longer than the timeout option, a SIGTERM signal is sent to it.

try {
	await execa({timeout: 5000})`npm run build`;
} catch (error) {
	if (error.timedOut) {
		console.error('Timed out.');
	}

	throw error;
}

Current process exit

If the current process exits, the subprocess is automatically terminated unless either:

  • The cleanup option is false.
  • The subprocess is run in the background using the detached option.
  • The current process was terminated abruptly, for example, with SIGKILL as opposed to SIGTERM or a successful exit.

Signal termination

subprocess.kill() sends a signal to the subprocess. This is an inter-process message handled by the OS. Most (but not all) signals terminate the subprocess.

More info.

SIGTERM

SIGTERM is the default signal. It terminates the subprocess.

const subprocess = execa`npm run build`;
subprocess.kill();
// Is the same as:
subprocess.kill('SIGTERM');

The subprocess can handle that signal to run some cleanup logic.

process.on('SIGTERM', () => {
	cleanup();
	process.exit(1);
})

SIGKILL

SIGKILL is like SIGTERM except it forcefully terminates the subprocess, i.e. it does not allow it to handle the signal.

subprocess.kill('SIGKILL');

SIGQUIT

SIGQUIT is like SIGTERM except it creates a core dump.

subprocess.kill('SIGQUIT');

Other signals

Other signals can be passed as argument. However, most other signals do not fully work on Windows.

Default signal

The killSignal option sets the default signal used by subprocess.kill() and the following options: cancelSignal, timeout, maxBuffer and cleanup. It is SIGTERM by default.

const subprocess = execa({killSignal: 'SIGKILL'})`npm run build`;
subprocess.kill(); // Forceful termination

Signal name and description

When a subprocess was terminated by a signal, error.isTerminated is true.

Also, error.signal and error.signalDescription indicate the signal's name and human-friendly description. On Windows, those are only set if the current process terminated the subprocess, as opposed to another process.

try {
	await execa`npm run build`;
} catch (error) {
	if (error.isTerminated) {
		console.error(error.signal); // SIGFPE
		console.error(error.signalDescription); // 'Floating point arithmetic error'
	}

	throw error;
}

Forceful termination

If the subprocess is terminated but does not exit, SIGKILL is automatically sent to forcefully terminate it.

The grace period is set by the forceKillAfterDelay option, which is 5 seconds by default. This feature can be disabled with false.

This works when the subprocess is terminated by either:

This does not work when the subprocess is terminated by either:

Also, this does not work on Windows, because Windows doesn't support signals: SIGKILL and SIGTERM both terminate the subprocess immediately. Other packages (such as taskkill) can be used to achieve fail-safe termination on Windows.

// No forceful termination
const subprocess = execa({forceKillAfterDelay: false})`npm run build`;
subprocess.kill();

Inter-process termination

subprocess.kill() only works when the current process terminates the subprocess. To terminate the subprocess from a different process, its subprocess.pid can be used instead.

const subprocess = execa`npm run build`;
console.log('PID:', subprocess.pid); // PID: 6513
await subprocess;

For example, from a terminal:

$ kill -SIGTERM 6513

Or from a different Node.js process:

import process from 'node:process';

process.kill(subprocessPid);

Error message and stack trace

When terminating a subprocess, it is possible to include an error message and stack trace by using subprocess.kill(error). The error argument will be available at error.cause.

try {
	const subprocess = execa`npm run build`;
	setTimeout(() => {
		subprocess.kill(new Error('Timed out after 5 seconds.'));
	}, 5000);
	await subprocess;
} catch (error) {
	if (error.isTerminated) {
		console.error(error.cause); // new Error('Timed out after 5 seconds.')
		console.error(error.cause.stack); // Stack trace from `error.cause`
		console.error(error.originalMessage); // 'Timed out after 5 seconds.'
	}

	throw error;
}

Next: 🎹 Input
Previous: ❌ Errors
Top: Table of contents