Replace the npm wrapper with the native binary using execve (#57513)

## Why

Replacing the Node process lets the native binary receive signals and report its termination directly.

## What changed

- Use `process.execve` on non-Windows platforms when available, preserving CLI arguments and the prepared environment.
- Install a `SIGXFSZ` handler before replacement so the native binary gets the default signal disposition instead of inheriting Node's ignored signal.
- Retain asynchronous spawning for Windows and older Node versions.

## Testing

Extend the exit-status test to check that `execve` leaves no child process and that `SIGHUP` and `SIGXFSZ` terminate it with the original signal, while retaining coverage for the spawn fallback.

GitOrigin-RevId: 7437f15af419fce5b10c1c00425c7d38160991fc
This commit is contained in:
Michael Bolin
2026-10-27 16:55:48 +00:00
committed by copyberry
parent 5a361b5596
commit 27ace641fe
2 changed files with 27 additions and 21 deletions
+16 -15
View File
@@ -81,25 +81,26 @@ test(
}
}
assert.ok(prompted, "migration must reach its confirmation prompt");
if (signal === "SIGHUP") {
// The wrapper must forward the signal and retain signal termination.
const processes = execFileSync("ps", ["-axo", "pid=,ppid="], {
encoding: "utf8",
});
const children = processes
.trim()
.split("\n")
.map((line) => line.trim().split(/\s+/).map(Number))
.filter(([, parent]) => parent === child.pid);
const usesExecve = typeof process.execve === "function";
assert.equal(
children.length,
usesExecve ? 0 : 1,
"execve must replace the wrapper; spawn must create one native child",
);
if (usesExecve || signal === "SIGHUP") {
// Signal the native process directly, or let the wrapper forward it.
child.kill(signal);
assert.deepEqual(await exited, [null, signal]);
} else {
// A native child can die from a signal that Node ignores by default.
const processes = execFileSync("ps", ["-axo", "pid=,ppid="], {
encoding: "utf8",
});
const children = processes
.trim()
.split("\n")
.map((line) => line.trim().split(/\s+/).map(Number))
.filter(([, parent]) => parent === child.pid);
assert.equal(
children.length,
1,
"wrapper must have one native child",
);
process.kill(children[0][0], signal);
assert.deepEqual(await exited, [
128 + constants.signals[signal],