Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
76a38f7
ffi: clear SA_ONSTACK on SIGPWR after dlopen
robobun May 21, 2026
efe6944
[autofix.ci] apply automated fixes
autofix-ci[bot] May 21, 2026
44ba51c
comment why Bun__repairJscGcSignalAfterDlopen exists
robobun May 21, 2026
05c79cc
drop the watchdog setTimeout — await-using + test timeout cover it
robobun May 21, 2026
0ef5a49
[autofix.ci] apply automated fixes
autofix-ci[bot] May 21, 2026
ce6f4c1
ship the real fix from WebKit#235, drop the band-aid
robobun May 21, 2026
c0c0e6c
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun May 23, 2026
be86d91
ci: retrigger — WebKit #235 preview autobuild is now published
robobun May 23, 2026
14c08e5
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun May 23, 2026
ee62500
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun May 25, 2026
a42d8e1
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun May 26, 2026
5700893
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun Jun 2, 2026
008c293
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun Jun 16, 2026
000610a
build: point WEBKIT_VERSION at rebased oven-sh/WebKit#235 preview (66…
robobun Jun 16, 2026
1f4deae
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun Jun 17, 2026
152f661
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun Jun 28, 2026
0968c00
build: align WEBKIT_VERSION to WebKit#235 697e8a79 (comment fix)
robobun Jun 28, 2026
7a95440
test(31158): filter benign ASAN stderr banner before asserting
robobun Jun 28, 2026
e8111d1
ci: retrigger (WebKit#235 preview tarball autobuild-preview-pr-235-69…
robobun Jun 28, 2026
8a95f9b
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun Jul 1, 2026
63e2247
Merge remote-tracking branch 'origin/main' into farm/ebd23fe3/fix-311…
robobun Jul 14, 2026
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
15 changes: 13 additions & 2 deletions scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "0d85951a31d3d04684e3e598589de065378c2c59";
// Preview autobuild of oven-sh/WebKit#235 rebased on current main — the
// ucontext-SP fix for JSC's signalHandlerSuspendResume (so the GC
// thread-suspend signal works under SA_ONSTACK, e.g. Go cgo's initsig,
// instead of spinning forever). Swap back to the next merged hash once
// #235 lands.
export const WEBKIT_VERSION = "autobuild-preview-pr-235-f9079851";

Check warning on line 11 in scripts/build/deps/webkit.ts

View check run for this annotation

Claude / Claude Code Review

sync-webkit-source.ts breaks with preview-tag WEBKIT_VERSION

nit: while `WEBKIT_VERSION` is a preview tag, `bun sync-webkit-source` (the workflow `.vscode/launch.json:9` points devs at to align `vendor/WebKit` for debugging) will fail — it does `git checkout main && git pull && git checkout ${WEBKIT_VERSION}`, and `git pull` on `main` won't auto-fetch a tag that points at a PR-branch commit, so the final checkout dies with `pathspec did not match`. Since this value is explicitly temporary it's probably fine, but worth either a note in the comment or havin
Comment thread
robobun marked this conversation as resolved.
Outdated
Comment thread
robobun marked this conversation as resolved.
Outdated

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down Expand Up @@ -83,7 +88,13 @@
* doesn't reuse a wrong-ABI extraction.
*/
function prebuiltDestDir(cfg: Config): string {
const version16 = cfg.webkitVersion.slice(0, 16);
// A raw 40-char git SHA is unique in its first 16 chars; a preview tag
// like `autobuild-preview-pr-235-a355e3e0` is NOT (every preview tag
// starts with `autobuild-previe` → same dir for every PR). Prefer the
// trailing hash when we recognise a preview-tag shape, so worktrees
// sharing `$BUN_INSTALL/build-cache` across branches don't collide.
const v = cfg.webkitVersion;
const version16 = v.startsWith("autobuild-") ? v.slice(-16) : v.slice(0, 16);
// Cross-compiled targets share a host (and cache dir) with native builds,
// so include os+arch in the key — otherwise a FreeBSD/arm64 extraction
// collides with a Linux/x64 one at the same WebKit version.
Expand Down
94 changes: 94 additions & 0 deletions test/regression/issue/31158.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Regression test for https://github.com/oven-sh/bun/issues/31158.
//
// When a shared library (e.g. Go's cgo runtime) force-sets `SA_ONSTACK` on
// JavaScriptCore's thread-suspend signal (SIGPWR on Linux in Bun's WebKit
// fork), every delivery of that signal on a thread that has an alternate
// signal stack (ASAN runtime, Bun's crash handler, libbacktrace) runs the
// handler on the alt stack. WTF's stack-check in `signalHandlerSuspendResume`
// used `currentStackPointer()` — the handler's own SP — so the sanity
// check failed every time, `Thread::suspend()` retried forever, and the
// event loop stopped advancing on the next WASM compile/install.
//
// Fixed upstream (oven-sh/WebKit#235) by reading the interrupted thread's
// SP from the ucontext instead of the handler's own SP — that SP is stable
// whether the handler runs on the normal stack or the alt stack, so
// `SA_ONSTACK` no longer matters.
//
// Triggers the exact shape of the bug without needing Go installed in CI:
// force `SA_ONSTACK` onto SIGPWR via `bun:ffi` (same thing Go's cgo
// `initsig` does to every handler at load time), then compile+call a WASM
// function (which triggers `resetInstructionCacheOnAllThreads`, the code
// path that actually suspends the JS thread). Without the fix,
// `setTimeout` after the WASM call never fires and the child hangs.
import { expect, test } from "bun:test";
import { bunEnv, bunExe, isGlibc } from "harness";

// The bug is only reachable on Linux: SIGPWR is only used as the suspend/
// resume signal on Linux (see `vendor/WebKit/Source/WTF/wtf/posix/
// ThreadingPOSIX.cpp` — USE(BUN_JSC_ADDITIONS) branch). The reproduction
// dlopens `libc.so.6` directly so it needs glibc; musl names its libc
// differently and static-musl builds of Bun don't even have a dynamic
// loader in the room.
test.skipIf(!isGlibc)(
"event loop survives SA_ONSTACK on SIGPWR + WASM (oven-sh/bun#31158)",
async () => {
const script = /* ts */ `
import { dlopen, FFIType, ptr } from "bun:ffi";

// 1. Open libc so we can flip SA_ONSTACK on SIGPWR the same way Go's
// cgo 'initsig' does on every inherited handler.
const libc = dlopen("libc.so.6", {
sigaction: { args: [FFIType.i32, FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
});

// x86_64 glibc layout: sa_handler (8), sa_mask (128), sa_flags (4), sa_restorer (8).
// We only touch sa_flags so this layout holds on aarch64 too (same offsets).
const SIGACTION_SIZE = 152;
const FLAGS_OFFSET = 8 + 128;
const SIGPWR = 30;
const SA_ONSTACK = 0x08000000;

const buf = new ArrayBuffer(SIGACTION_SIZE);
const view = new DataView(buf);

libc.symbols.sigaction(SIGPWR, null, ptr(buf));
view.setInt32(FLAGS_OFFSET, view.getInt32(FLAGS_OFFSET, true) | SA_ONSTACK, true);
libc.symbols.sigaction(SIGPWR, ptr(buf), null);

// 2. Exercise the WASM path that calls resetInstructionCacheOnAllThreads.
// The bytes below are a minimal WASM module with one exported function
// that loops 10_000 times and returns — enough to trigger compilation.
// SA_ONSTACK is still set at this point; the WTF fix makes the
// handler tolerate it instead of spinning.
const bytes = new Uint8Array([
0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 127, 3, 2, 1, 0, 7, 5,
1, 1, 102, 0, 0, 10, 34, 1, 32, 1, 1, 127, 65, 0, 33, 0, 2, 64, 3, 64,
32, 0, 65, 1, 106, 33, 0, 32, 0, 65, 144, 206, 0, 72, 13, 0, 11, 11, 32,
0, 11,
]);
const inst = new WebAssembly.Instance(new WebAssembly.Module(bytes));
(inst.exports.f as () => number)();

// 3. A setTimeout that must fire — the bug makes this hang forever.
await new Promise(r => setTimeout(r, 10));
console.log("EVENT_LOOP_ALIVE");
`;

// `await using` scopes the child to this block — if the test-runner
// timeout fires because the bug reproduced, `Bun.spawn`'s disposer
// kills the child on the way out, no harness-side watchdog needed.
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", script],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

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

expect(stderr).toBe("");
Comment thread
robobun marked this conversation as resolved.
Outdated
expect(stdout).toContain("EVENT_LOOP_ALIVE");
expect(exitCode).toBe(0);
},
15_000,
);
Loading