Repository navigation
Conversation
Regression test for Worker churn at RLIMIT_NPROC (the same EAGAIN path a cgroup pids.max budget hits). Before oven-sh/WebKit#311 the child process dies with SIGABRT from RELEASE_ASSERT in WTF::Thread::create or PAS_ASSERT in bmalloc's pas_scavenger; with it the child exits 0 after churning Workers that individually fail. Linux-only and skipped under uid 0 (the kernel ignores RLIMIT_NPROC for root). Requires the WebKit bump once #311 lands.
Collaborator
Author
Collaborator
Author
|
Closing: superseded by #41587 and oven-sh/WebKit#569. That WebKit change covers the same aborts and also the synchronous GC that spun in waitForCollector with no collector thread, and it keeps the VMTraps queue lazy instead of creating a thread at startup. The new PR carries a test for both the GC and the Worker case and is verified against the preview tarballs. |
Bewinxed
added a commit
to Bewinxed/cawco
that referenced
this pull request
Oct 8, 2026
…er runs out of room itself A Bun process that cannot create a thread aborts: WTF::Thread::create asserts on a refused pthread_create (oven-sh/WebKit#311 and oven-sh/bun#34705 closed unmerged; 1.4.2 is the latest release). Stage 2's joiner keeper died that way at its container's 307-task limit, in JITWorklist::enqueue, and took every child it held. Reproduced on a scratch keeper under `prlimit --nproc=120`: 150 spawns all acked applied (81 alive), then the first connection after a 40 s idle aborted it. - Before each spawn the keeper reads its room under the tightest limit: the user's process limit (/proc/self/limits against the user's tasks in /proc/*/status) and cgroup v2 pids.max along its cgroup chain. A child is started only with 64 tasks to come up (a Claude Code CLI reached 17 threads starting) above the 64 the keeper keeps for its own threads and its `ps`; otherwise the spawn is refused with `process-limit: <limit>`. - Spawns run one at a time, so a burst is weighed child by child. - A started child gets RLIMIT_NPROC 64 below the keeper's (prlimit(2) via bun:ffi): it and all it starts can never take the keeper's room. - The ack waits for the child's `spawn` or `error`: a fork the kernel refuses (EAGAIN) is a process-limit refusal, not an applied spawn that dies unexplained. - The agent says it as "<machine> can't start another process right now (its process limit is reached), so <session> didn't start." Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
What
new Worker()at a kernel thread limit (cgrouppids.max,RLIMIT_NPROC) currently aborts the whole process instead of failing that one Worker. Node at the same limit emits a per-Worker error and fully recovers.The load-bearing fix is in oven-sh/WebKit#311; this PR adds the bun-side regression test and will bump
WEBKIT_VERSIONonce that lands.Repro
worker-churn.mjs
Stock 1.4.0 and current main:
SIGABRT(134), silent in release. With debug assertions:Cause
Bun's own Worker OS thread (
std::thread::Builder::spawninweb_worker.rs) already turns spawn failure into a JSTypeError. The abort comes from the JSC/WTF/bmalloc helper threads that the Worker churn triggers:AutomaticThread::start(GC markerParallelHelperPool, JIT/Wasm worklists, per-VM Heap collector) callsThread::createwhichRELEASE_ASSERTs onpthread_createEAGAIN.pas_scavengerdoes a rawpthread_create+PAS_ASSERT(!result).VMTraps' process-global signal-senderWorkQueueis created lazily on the firstfireTrap, which the firstworker.terminate()reaches after Workers have consumed every pid slot.Fix (oven-sh/WebKit#311)
WTF::Thread::tryCreatereturningRefPtr<Thread>.AutomaticThread::startuses it and backs off to the no-underlying-thread state so the next notify retries.Heap::finishRelinquishingConnre-takes the conn when the collector thread has no underlying thread, so the mutator drives GC instead ofwaitForCollectorparking forever.pas_scavengerdrops back tono_threadon failure instead of asserting.VMTraps::initializeSignalscreates the signal-sender queue eagerly.Verification
Patched the five objects into the prebuilt debug-asan
libWTF.a/libbmalloc.a/libJavaScriptCore.aand relinked; sweptulimit -u13 through 28 on the repro. Every value now exits 0 where the unpatched build aborts. All existingtest/js/web/workersandtest/js/node/worker_threadstests pass against the patched build.The new test (
test/js/web/workers/worker-thread-limit.test.ts) runs the repro underulimit -uand asserts exit 0. Linux-only, skipped under uid 0. It will stay red untilWEBKIT_VERSIONis bumped to include oven-sh/WebKit#311.Status
Draft until oven-sh/WebKit#311 merges and autobuilds; then this PR bumps
scripts/build/deps/webkit.tsand goes green.no test proof · iteration 0 · Platform-specific test-only change; deferring to CI.