Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion src/jsc/bindings/c-bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -933,10 +933,12 @@
M(SIGIO);

#if OS(LINUX)
// SIGPWR is intentionally excluded: JSC uses it for GC thread suspend/resume
// (see WTF/wtf/posix/ThreadingPOSIX.cpp). Replacing its handler can terminate
// the process when the GC next signals a thread.
#define FOR_EACH_LINUX_ONLY_SIGNAL(M) \
M(SIGPOLL); \
M(SIGPWR); \
M(SIGSTKFLT);

Check notice on line 941 in src/jsc/bindings/c-bindings.cpp

View check run for this annotation

Claude / Claude Code Review

SIGPOLL/SIGIOT alias entries clobber saved handlers (pre-existing)

Pre-existing, but since you're cleaning up this list: `SIGPOLL` is an alias for `SIGIO` on Linux (both signal 29), and `SIGIOT` is an alias for `SIGABRT` (both signal 6). Because both the alias and the original appear in `FOR_EACH_SIGNAL`, the second `sigaction()` overwrites `previous_actions[29]`/`[6]` with the forwarding lambda you just installed, so `Bun__unregisterSignalsForForwarding` restores the lambda instead of the original handler. Worth dropping `M(SIGPOLL)` here and `M(SIGIOT)` from
Comment thread
claude[bot] marked this conversation as resolved.
Comment thread
claude[bot] marked this conversation as resolved.

#endif

Expand Down
58 changes: 58 additions & 0 deletions test/js/bun/spawn/spawnSync-sigpwr-gc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir, isLinux } from "harness";
import { join } from "path";
import { symlinkSync } from "fs";

// Regression: spawnSync's signal-forwarding list included SIGPWR on Linux.
// JSC uses SIGPWR to suspend/resume threads for GC. Bun.openInEditor spawns a
// detached thread per call that runs spawnSync; concurrent calls race on the
// process-wide previous-handler table and can leave SIGPWR at SIG_DFL. The
// next GC suspend then terminates the process with signal 30.
test.skipIf(!isLinux)(
"spawnSync signal forwarding does not clobber JSC's SIGPWR handler",
async () => {
const sleepBin = Bun.which("sleep");
expect(sleepBin).toBeTruthy();

using dir = tempDir("spawnSync-sigpwr", {
"run.js": `
for (let i = 0; i < 64; i++) {
try { Bun.openInEditor("0.2"); } catch {}
}
let junk = [];
for (let i = 0; i < 2000; i++) {
junk.push({ a: new Uint8Array(4096).fill(i), b: { c: i } });
}
for (let i = 0; i < 30; i++) Bun.gc(true);
console.log("ok");
`,
});
// Make `code` (first in the editor preference list) resolve to `sleep`, so
// each background spawnSync holds its signal-forwarding window open long
// enough for threads to overlap.
symlinkSync(sleepBin!, join(String(dir), "code"));

await using proc = Bun.spawn({
cmd: [bunExe(), join(String(dir), "run.js")],
env: {
...bunEnv,
PATH: String(dir),
EDITOR: "",
VISUAL: "",
},
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([
proc.stdout.text(),
proc.stderr.text(),
proc.exited,
]);

expect(proc.signalCode).toBeNull();
expect(stderr).not.toContain("AddressSanitizer");
expect(stdout.trim()).toBe("ok");
expect(exitCode).toBe(0);
},
);
Loading