Repository navigation
fix(queen): a beat thread is ended only once it has loaded, so a node stopped at its start no longer crashes Bun 1.3.x (Closes #1813) - #1837
Conversation
… stopped at its start no longer crashes Bun 1.3.x (Closes #1813) Bun 1.3.6 and 1.3.11 segfault (0xFFFFFFFFFFFFFFF8, SavedSourceMap.putValue on a transpiler pool thread) when a Worker is terminated while its module graph is still being transpiled. The pglive edges test stops a PgLink within milliseconds of its start, so createPgLink's halt terminated the beat thread mid-load and the whole pglive suite died after its tests had passed. The beat thread now says it has loaded once its module body runs, and halt ends it only after that word, or after the error or exit that ends a failed load. A new edges test watches every beat thread's terminate and fails on the old code 6 of 6 times. Bun 1.4.3 survives the same churn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary by t27-beesThe change fixes a Bun 1.3.x crash caused by terminating a worker thread while its modules were still being transpiled. The beat thread now signals Worth a look
Read: 3 files, +70 -3, the whole diff. Head |
✅ Tests passed — 3312/3381
|
20-run results (local: Postgres 16, macOS arm64)Every run used the command
At about 1 in 20 runs, the file loop alone cannot separate the two trees. The same input with a higher dose does. Each run below is 200 cycles of a real
Every crash had the same stack as both CI failures: On the t27-bees review points:
🤖 Generated with Claude Code |
…so a stop at its start no longer crashes Bun 1.3.x (Closes Trinity-S-AI-DNA#1865) threadWork terminated its worker at once on stop, even while the thread was still transpiling its entry or its work module. Bun 1.3.6 (the CI and production pin) then crashes the whole process (SavedSourceMap.putValue, Segmentation fault at 0xFFFFFFFFFFFFFFF8), the crash Trinity-S-AI-DNA#1813 hit on the PgLink beat thread. The thread entry now posts { loaded: true } once its work module's import has settled. Every terminate goes through one end() that waits for that word, or for the error or close event that ends a failed load, and then terminates once. Same pattern as Trinity-S-AI-DNA#1837. No rule changed: when to stop stays with kill_effect and escalation. New test: 200 cycles of start, a random 0-30 ms wait, then stop, on the work's own stop and through the runtime with turn stop on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…loses Trinity-S-AI-DNA#1810) The flaky pglive case "mail to a node that died is expired, not kept for ever" saw 0 rows where it expected 5. The racing pair: the poll's read of node 2's mailbox that was on its way when b.link.stop() was called, and the sends to node 2 right after the stop. stop() did not wait for that drain, so the read could reach the store after the sends, hand the mail to the stopped node's actor and acknowledge (delete) it. - stop() now waits for the drain that was running. - A read that answers after the stop hands nothing on and acknowledges nothing; a drain checks the stop before each read. New live case holds that read until the sends are in, so the race runs on every run. It fails on the old code (all 5 handled on the stopped node) and with only the post-read check removed; passes with the fix. No .t27 change: no netlink card decision changes; stop() ordering is the I/O shell, as in Trinity-S-AI-DNA#1837. Refs Trinity-S-AI-DNA#1862
Closes #1813
Cause
Not a leaked pool or socket: a Bun 1.3.x crash that our own stop path triggered.
tests/pglive/queen-actors-pg-edges-live.test.ts, "a node whose UNLISTEN fails at stop". Theirbun.reportlink is identical. It decodes toSavedSourceMap.putValue<-js_printer.printAst<-RuntimeTranspilerStore.TranspilerJob.runFromWorkerThread, on Bun's transpiler thread pool, withworkers_spawned(23) workers_terminated(13).PgLinkand stops it at once.createPgLinkspawns the beat thread (queen-actors-pg-beat.ts), andhalt()calledterminate()while the thread's module graph was still being transpiled. The transpile job then wrote into the source map table of the worker that was gone.pg_stat_activity, becauseterminate()closes the beat thread's socket.Fix
queen-actors-pg-beat.tsposts{ loaded: true }once its module body runs, which means every one of its modules is loaded. It does not post this on the main thread, where the beat live test importsnodeBeat.createPgLink,halt()now ends the beat thread throughendBeat(). That function waits for theloadedword, or for theerrororcloseevent that ends a failed load, and only then callsterminate(). Everyhaltshares this one end, sofence()followed bystop()terminates the thread once.error).Test
New edges test: "a node stopped as soon as it starts ends its beat thread only once the thread has loaded". It wraps
Worker, records whether eachterminate()came after the thread said it loaded, and runs 20 start/stop cycles.Results (local, Postgres 16, this Mac)
The 20-run results for the edges file and the whole pglive suite follow in a comment.
Not done
packageManagerand production.🤖 Generated with Claude Code