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:
- Letting the subprocess end on its own.
- Performing cleanup in termination signal handlers.
- Sending a message to the subprocess so it aborts its operations and cleans up.
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;
}
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;
}
If the current process exits, the subprocess is automatically terminated unless either:
- The
cleanup
option isfalse
. - The subprocess is run in the background using the
detached
option. - The current process was terminated abruptly, for example, with
SIGKILL
as opposed toSIGTERM
or a successful exit.
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.
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
is like SIGTERM
except it forcefully terminates the subprocess, i.e. it does not allow it to handle the signal.
subprocess.kill('SIGKILL');
SIGQUIT
is like SIGTERM
except it creates a core dump.
subprocess.kill('SIGQUIT');
Other signals can be passed as argument. However, most other signals do not fully work on Windows.
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
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;
}
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:
- Calling
subprocess.kill()
with no arguments. - The
cancelSignal
,timeout
,maxBuffer
orcleanup
option.
This does not work when the subprocess is terminated by either:
- Calling
subprocess.kill()
with a specific signal. - Calling
process.kill(subprocess.pid)
. - Sending a termination signal from another process.
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();
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);
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;
}