Repository navigation
Conversation
The guard that stops JS listeners from overriding the signal JSC uses for thread
suspend/resume is `#if OS(LINUX)`. JSC uses SIGPWR on Linux but SIGUSR1 on FreeBSD
(see wtf/posix/ThreadingPOSIX.cpp). So on FreeBSD, `process.on('SIGUSR1', ...)` made Bun
install its own SIGUSR1 handler and clobber JSC's. The suspend signal was then delivered
to the wrong handler, `Thread::suspend()` never got its acknowledgement, and tearing down
a VM (e.g. a Web Worker) deadlocked in `VMTraps::SignalSender` with a GC helper blocked
on the process-global suspend lock it held.
Treat `g_wtfConfig.sigThreadSuspendResume` as unhandleable on FreeBSD too, as Linux does.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. WalkthroughSignal listener installation now skips the thread suspend/resume signal on FreeBSD. Darwin continues to skip only ChangesPlatform signal listener filtering
Suggested reviewers: Merge Risk:
|
|
@dylan-conway hi Dylan. Any chance to get your review? Thank you. |
|
@Jarred-Sumner any chance? |
What does this PR do?
Bun protects the signal JSC uses to suspend/resume threads from being overridden by a JS listener (
process.on(...)) only under#if OS(LINUX):JSC picks that signal in
wtf/posix/ThreadingPOSIX.cpp: SIGPWR on Linux, SIGUSR1 everywhere else (including FreeBSD). So on FreeBSD aprocess.on('SIGUSR1', ...)listener makes Bun install its own SIGUSR1 handler and clobber JSC's. The GC/VMTraps suspend signal is then delivered to Bun's handler,globalSemaphoreForSuspendResumeis never posted, and anything that suspends a thread never resumes — tearing down a VM (e.g. a Web Worker,WebWorker__teardownJSCVM→destroyVM) deadlocks.This is easy to hit: any JS dependency that handles
SIGUSR1, or justprocess.on('SIGUSR1', () => {}), breaks GC suspension for the life of the process.How did you verify your code works?
On FreeBSD/aarch64 (a compiled
bun --compileapp that registers a SIGUSR1 listener), before the change the process deadlocked on exit 5/5. With this change the same deterministic test isexit=OK15/15, and the JS turns it exercises still pass.Instrumentation used to confirm the mechanism:
write(2)log inThread::signalHandlerSuspendResume(to a dedicated file fd) showed the handler never executes, while the suspender's side (Thread::suspend) does run.ktrace -t sshowedPSIG SIGUSR1 caught ... code=SI_LWPdelivered to the owner thread but to a non-JSC handler; the thread's mask is clear and itssigfastblockword is 0.VMTraps::SignalSender::work()→Thread::suspend()→sem_wait, with a GC helper blocked inMachineThreads::tryCopyOtherThreadStacks→ the process-global suspend lock the sender holds.oven-sh/WebKit#693(thesignalHandlerSuspendResumerequest/generation rework) does not fix this, which is consistent: the problem is that JSC's handler is not installed at all.Risk
Low. It only makes FreeBSD behave like Linux for
sigThreadSuspendResume: Bun will refuse to install a JS listener for it, the same way it already does on Linux. macOS is unaffected (it uses Mach thread suspension, not signals), so theSIGUSR1guard is#if OS(FREEBSD)only.