Register the fork handlers once per process, not on every mi_heap_new - #19
Merged
Merged
Conversation
The upstream/dev3 merge (27d1636) moved the pthread_atfork call from mi_process_init_once() into mi_process_init(), after the do-once block. mi_process_init() is reached from _mi_thread_init_with_heap() and so from every mi_heap_new(), which meant each heap registered another (prepare, parent, child) triple. macOS caps a process's atfork table at one page (~680 entries on arm64); once a heap-heavy workload filled it, any other pthread_atfork in the process failed with ENOMEM. In Bun that surfaced as BoringSSL's init_pthread_fork_detection() aborting on the first RAND_bytes after ~700 transpiled modules (astro build, vite build). glibc's list is unbounded so Linux only leaked entries. Move the call back into mi_process_init_once(). test-fork-user-heap gains case_d: after 2048 heap create/delete cycles a fresh pthread_atfork must still succeed (fails with 12/ENOMEM on macOS before this change).
dylan-conway
added a commit
to oven-sh/bun
that referenced
this pull request
Aug 14, 2026
…in astro/vite builds) Bumps oven-sh/mimalloc to f201f52a (oven-sh/mimalloc#19). The dev3 sync in #37367 left mimalloc's pthread_atfork call outside the once-guard in mi_process_init(), which every mi_heap_new() reaches, so each transpile job's heap registered another handler triple. macOS caps the atfork table at one page (~680 entries); once full, BoringSSL's own pthread_atfork got ENOMEM and init_pthread_fork_detection() aborted on the first RAND_bytes. That is the SIGABRT in astro-post.test.js and vite-build.test.ts on the darwin lanes. Linux was unaffected (glibc's list is unbounded).
dylan-conway
added a commit
to oven-sh/bun
that referenced
this pull request
Aug 14, 2026
…in astro/vite builds) Bumps oven-sh/mimalloc to f201f52a (oven-sh/mimalloc#19). The dev3 sync in #37367 left mimalloc's pthread_atfork call outside the once-guard in mi_process_init(), which every mi_heap_new() reaches, so each transpile job's heap registered another handler triple. macOS caps the atfork table at one page (~680 entries); once full, BoringSSL's own pthread_atfork got ENOMEM and init_pthread_fork_detection() aborted on the first RAND_bytes. That is the SIGABRT in astro-post.test.js and vite-build.test.ts on the darwin lanes. Linux was unaffected (glibc's list is unbounded).
dylan-conway
added a commit
to oven-sh/bun
that referenced
this pull request
Aug 14, 2026
…in astro/vite builds) Bumps oven-sh/mimalloc to 6e891cbe, the bun-dev3-v2 merge of oven-sh/mimalloc#19. The dev3 sync in mi_process_init(), which every mi_heap_new() reaches, so each transpile job's heap registered another handler triple. macOS caps the atfork table at one page (~680 entries); once full, BoringSSL's own pthread_atfork got ENOMEM and init_pthread_fork_detection() aborted on the first RAND_bytes. That is the SIGABRT in astro-post.test.js and vite-build.test.ts on the darwin lanes. Linux was unaffected (glibc's list is unbounded).
dylan-conway
added a commit
to oven-sh/bun
that referenced
this pull request
Aug 14, 2026
…in astro/vite builds) Bumps oven-sh/mimalloc to 6e891cbe, the bun-dev3-v2 merge of oven-sh/mimalloc#19. The dev3 sync in bun#37367 left mimalloc's pthread_atfork call outside the once-guard in mi_process_init(), which every mi_heap_new() reaches, so each transpile job's heap registered another handler triple. macOS caps the atfork table at one page (~680 entries); once full, BoringSSL's own pthread_atfork got ENOMEM and init_pthread_fork_detection() aborted on the first RAND_bytes. That is the SIGABRT in astro-post.test.js and vite-build.test.ts on the darwin lanes. Linux was unaffected (glibc's list is unbounded).
Jarred-Sumner
pushed a commit
to oven-sh/bun
that referenced
this pull request
Aug 14, 2026
…in astro/vite builds); build: --local-deps (#38291) ### What was crashing `test/js/third_party/astro/astro-post.test.js` and `test/integration/vite-build/vite-build.test.ts` SIGABRT on the darwin lanes since #37367 ([build 95095](https://buildkite.com/bun/bun/builds/95095)). Stack: ``` abort init_pthread_fork_detection() ← BoringSSL aborts when pthread_atfork() != 0 pthread_once RAND_bytes bun_runtime::node::crypto::random::__jsc_host_random_bytes ``` The mimalloc dev3 sync left `pthread_atfork(...)` in `mi_process_init()` *after* its `mi_atomic_do_once` block instead of inside `mi_process_init_once()`. `mi_process_init()` is reached from every `mi_heap_new()`, so each transpile job / arena reset registered another handler triple (~1050 registrations by the time astro's build reaches its first `randomBytes`). macOS caps a process's atfork table at one page (~680 entries on arm64); once full, every other `pthread_atfork` in the process gets `ENOMEM`, and BoringSSL aborts on that. glibc's list is unbounded, so Linux only leaked entries. Minimal repro: 700 trivial `.ts` imports followed by `crypto.randomBytes(4)` → `abort()`; 600 is fine. ### Fix Bump `oven-sh/mimalloc` to `6e891cbe` — the `bun-dev3-v2` merge of oven-sh/mimalloc#19 — which moves the call back under the once-guard and adds a regression case to the fork's `test-fork-user-heap` (fails with `ENOMEM` on macOS before, passes after). `process.versions.mimalloc` expectation updated. Verified locally with `build:release` on macOS arm64, same build config both ways: | mimalloc source | 700-import + `randomBytes` | `astro-post.test.js` | |---|---|---| | `be7eb3f` (current pin) | abort | abort | | `6e891cbe` (tree-identical to the tested `f201f52a`) | ok | 4/4 pass ×2 | ### Also: `--local-deps=name=path` Added while chasing this so a vendored dep can be built from a local checkout instead of the pinned tarball: ```sh bun bd --local-deps=mimalloc=~/code/mimalloc test foo.test.ts ``` Works for any `github-archive` dep (`name=path[,name=path]`); no fetch / `.ref` / patches, banner shows `local:<name>`, unknown or disabled names fail at configure, and an out-of-repo checkout's objects map onto `obj/vendor/<name>/`. Editing the checkout rebuilds incrementally (for mimalloc: just `static.c.o` + link). Local/in-tree `direct` deps also stop stamping their source *directory* as a PCH dependency (depfiles already track edits). Default-config `build.ninja` is byte-identical apart from the pin. Docs in `scripts/build/deps/README.md`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The upstream/dev3 merge (27d1636) moved the
pthread_atfork(...)call frommi_process_init_once()intomi_process_init(), after themi_atomic_do_onceblock.mi_process_init()is reached from_mi_thread_init_with_heap()— i.e. from everymi_heap_new()— so each heap registered another (prepare, parent, child) triple.macOS caps a process's atfork handler table at one VM page (~680 entries on arm64). Once a heap-heavy workload fills it, every later
pthread_atforkin the process fails withENOMEM. In Bun this showed up as BoringSSL'sinit_pthread_fork_detection()callingabort()on the firstRAND_bytesafter ~700 transpiled modules (each transpile job creates a heap) —astro buildandvite buildboth hit it: https://buildkite.com/bun/bun/builds/95095. glibc's list is heap-allocated and unbounded, so Linux only leaked entries (and ran N nested no-op handlers per fork).The fix moves the call back into
mi_process_init_once(), where it was before the merge.test-fork-user-heapgainscase_d: after 2048mi_heap_new/mi_heap_deletecycles, a freshpthread_atforkmust still succeed. On macOS it returns 12 (ENOMEM) before this change and 0 after; on Linux it passes either way (documented in the test). Full ctest passes locally in Release and Debug (Debug also exercisescase_b).Verified in Bun with a release build: unpatched →
astro-post.test.jsand a 700-import +randomBytesrepro both abort; patched → both pass.