Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/api/child_process.md
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,10 @@ re-raise the handled signal.

See waitpid(2).

When `code` is `null` due to signal termination, you can use
[`util.convertProcessSignalToExitCode()`][] to convert the signal to a POSIX
exit code.

### Event: `'message'`

<!-- YAML
Expand Down Expand Up @@ -1671,6 +1675,11 @@ within the child process to close the IPC channel as well.
The `subprocess.exitCode` property indicates the exit code of the child process.
If the child process is still running, the field will be `null`.

When the child process is terminated by a signal, `subprocess.exitCode` will be
`null` and [`subprocess.signalCode`][] will be set. To get the corresponding
POSIX exit code, use
[`util.convertProcessSignalToExitCode(subprocess.signalCode)`][`util.convertProcessSignalToExitCode()`].

### `subprocess.kill([signal])`

<!-- YAML
Expand Down Expand Up @@ -2107,6 +2116,10 @@ connection to the child.
The `subprocess.signalCode` property indicates the signal received by
the child process if any, else `null`.

When the child process is terminated by a signal, [`subprocess.exitCode`][] will be `null`.
To get the corresponding POSIX exit code, use
[`util.convertProcessSignalToExitCode(subprocess.signalCode)`][`util.convertProcessSignalToExitCode()`].

### `subprocess.spawnargs`

* Type: {Array}
Expand Down Expand Up @@ -2387,12 +2400,15 @@ or [`child_process.fork()`][].
[`stdio`]: #optionsstdio
[`subprocess.connected`]: #subprocessconnected
[`subprocess.disconnect()`]: #subprocessdisconnect
[`subprocess.exitCode`]: #subprocessexitcode
[`subprocess.kill()`]: #subprocesskillsignal
[`subprocess.send()`]: #subprocesssendmessage-sendhandle-options-callback
[`subprocess.signalCode`]: #subprocesssignalcode
[`subprocess.stderr`]: #subprocessstderr
[`subprocess.stdin`]: #subprocessstdin
[`subprocess.stdio`]: #subprocessstdio
[`subprocess.stdout`]: #subprocessstdout
[`util.convertProcessSignalToExitCode()`]: util.md#utilconvertprocesssignaltoexitcodesignalcode
[`util.promisify()`]: util.md#utilpromisifyoriginal
[synchronous counterparts]: #synchronous-process-creation
[v8.serdes]: v8.md#serialization-api
32 changes: 32 additions & 0 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,38 @@ callbackFunction((err, ret) => {
});
```

## `util.convertProcessSignalToExitCode(signalCode)`

<!-- YAML
added: REPLACEME
-->

* `signalCode` {string} A signal name (e.g., `'SIGTERM'`, `'SIGKILL'`).
* Returns: {number|null} The exit code, or `null` if the signal is invalid.

The `util.convertProcessSignalToExitCode()` method converts a signal name to its
corresponding POSIX exit code. Following the POSIX standard, the exit code
for a process terminated by a signal is calculated as `128 + signal number`.

```mjs
import { convertProcessSignalToExitCode } from 'node:util';

console.log(convertProcessSignalToExitCode('SIGTERM')); // 143 (128 + 15)
console.log(convertProcessSignalToExitCode('SIGKILL')); // 137 (128 + 9)
console.log(convertProcessSignalToExitCode('INVALID')); // null
```

```cjs
const { convertProcessSignalToExitCode } = require('node:util');

console.log(convertProcessSignalToExitCode('SIGTERM')); // 143 (128 + 15)
console.log(convertProcessSignalToExitCode('SIGKILL')); // 137 (128 + 9)
console.log(convertProcessSignalToExitCode('INVALID')); // null
```

This is particularly useful when working with processes to determine
the exit code based on the signal that terminated the process.

## `util.debuglog(section[, callback])`

<!-- YAML
Expand Down
14 changes: 14 additions & 0 deletions lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const {
ObjectGetOwnPropertyDescriptor,
ObjectGetOwnPropertyDescriptors,
ObjectGetPrototypeOf,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ObjectSetPrototypeOf,
ObjectValues,
Expand Down Expand Up @@ -106,6 +107,7 @@ function isError(e) {
const codesWarned = new SafeSet();

let validateString;
let validateOneOf;

function getDeprecationWarningEmitter(
code, msg, deprecated, useEmitSync,
Expand Down Expand Up @@ -393,6 +395,17 @@ function convertToValidSignal(signal) {
throw new ERR_UNKNOWN_SIGNAL(signal);
}

function convertProcessSignalToExitCode(signalCode) {
// Lazy-load to avoid a circular dependency.
if (validateOneOf === undefined)
({ validateOneOf } = require('internal/validators'));

validateOneOf(signalCode, 'signalCode', ObjectKeys(signals));

// POSIX standard: exit code for signal termination is 128 + signal number.
return 128 + signals[signalCode];
}

function getConstructorOf(obj) {
while (obj) {
const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
Expand Down Expand Up @@ -956,6 +969,7 @@ module.exports = {
assignFunctionName,
cachedResult,
constructSharedArrayBuffer,
convertProcessSignalToExitCode,
convertToValidSignal,
createClassWrapper,
decorateErrorStack,
Expand Down
2 changes: 2 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const { getOptionValue } = require('internal/options');
const binding = internalBinding('util');

const {
convertProcessSignalToExitCode,
deprecate: internalDeprecate,
getLazy,
getSystemErrorMap,
Expand Down Expand Up @@ -472,6 +473,7 @@ module.exports = {
'The `util._extend` API is deprecated. Please use Object.assign() instead.',
'DEP0060'),
callbackify,
convertProcessSignalToExitCode,
debug: debuglog,
debuglog,
deprecate,
Expand Down
67 changes: 67 additions & 0 deletions test/parallel/test-util-convert-signal-to-exit-code.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { mustCall, mustNotCall, isWindows } from '../common/index.mjs';
import assert from 'assert';
import { convertProcessSignalToExitCode } from 'util';
import { spawn } from 'child_process';

{
// SIGTERM = 15, so 128 + 15 = 143
assert.strictEqual(convertProcessSignalToExitCode('SIGTERM'), 143);

// SIGKILL = 9, so 128 + 9 = 137
assert.strictEqual(convertProcessSignalToExitCode('SIGKILL'), 137);

// SIGINT = 2, so 128 + 2 = 130
assert.strictEqual(convertProcessSignalToExitCode('SIGINT'), 130);

// SIGHUP = 1, so 128 + 1 = 129
assert.strictEqual(convertProcessSignalToExitCode('SIGHUP'), 129);

// SIGABRT = 6, so 128 + 6 = 134
assert.strictEqual(convertProcessSignalToExitCode('SIGABRT'), 134);
}

{
[
'INVALID',
'',
'SIG',
undefined,
null,
123,
true,
false,
{},
[],
Symbol('test'),
() => {},
].forEach((value) => {
assert.throws(
() => convertProcessSignalToExitCode(value),
{
code: 'ERR_INVALID_ARG_VALUE',
name: 'TypeError',
}
);
});
}

{
const cat = spawn(isWindows ? 'cmd' : 'cat');
cat.stdout.on('end', mustCall());
cat.stderr.on('data', mustNotCall());
cat.stderr.on('end', mustCall());

cat.on('exit', mustCall((code, signal) => {
assert.strictEqual(code, null);
assert.strictEqual(signal, 'SIGTERM');
assert.strictEqual(cat.signalCode, 'SIGTERM');

const exitCode = convertProcessSignalToExitCode(signal);
assert.strictEqual(exitCode, 143);
}));

assert.strictEqual(cat.signalCode, null);
assert.strictEqual(cat.killed, false);
cat[Symbol.dispose]();
assert.strictEqual(cat.killed, true);
}
Loading