Skip to content

fix(runtime): per-job cgroup pids.max instead of a host-wide shared RLIMIT_NPROC - #218

Merged
luthermonson merged 1 commit into
mainfrom
fix/per-job-pids-limit
Oct 23, 2026
Merged

luthermonson merged 1 commit into
mainfrom
fix/per-job-pids-limit

Conversation

@luthermonson

Copy link
Copy Markdown
Contributor

Problem

ePHPm hit two rustc internal compiler errors on 2026-10-22, both on ducks with 4 concurrent jobs:

  • run 39785206572 (E2E PHP 8.5, keen_rosalind): write.rs:1276, failed to spawn helper thread: Os { code: 11, ... "Resource temporarily unavailable" }
  • run 39863141372 (pdo_mysql E2E, kind_pascal): write.rs:1915/2157, failed to spawn work thread: ... code: 11

EAGAIN from thread creation means a task-count limit, not memory. ducks has no [runtime.rlimits], so every runner container gets RLIMIT_NPROC=1024. The kernel counts that per UID across the host, and every runner container runs as uid 1001 with no user namespace. So all concurrent jobs share one 1,024-task budget. uid 1001 held 558 threads at an idle moment with 4 jobs, and parallel rustc/LLVM codegen spikes push it over. There were no OOM kills and no kernel messages (a hit RLIMIT_NPROC fails clone() silently). kernel.threads-max (385k) was nowhere near.

Fix

  • [runtime] pids_limit sets the cgroup pids.max on the runner container and every dind sibling of its job. The default is 32,768 per container; -1 disables it. It's counted per container, so one job's fork bomb can't starve another, and a busy neighbour can't fail a build.
  • The default RLIMIT_NPROC goes from 1024 to 1<<20. As a per-job bound it never worked; it only let jobs fail each other. An explicit [runtime.rlimits] nproc is still honoured.
  • Linux only. Windows and macOS are unchanged, with no-op stubs.

Memory: Linux job containers have no per-container memory limit today. That's unchanged here, and it's worth a separate decision.

Verification

  • go vet passes on Windows and GOOS=linux.
  • go test ./pkg/config passes.
  • The Linux test binaries pass under WSL Ubuntu: runtime Rlimit|Pids (including the new TestPidsLimitOpts) and the full pkg/dind suite.
  • New TestRuntimeConfig_ResolvedPidsLimit; the NPROC default tests are updated.

…T_NPROC

RLIMIT_NPROC is counted per UID across the whole host, and every runner
container runs as the same UID with no user namespace, so concurrent jobs
on a node drew from one shared 1024-task budget. On ducks (2026-10-22,
4 concurrent jobs) two ephpm builds failed with rustc panics:

  failed to spawn work thread: Os { code: 11, kind: WouldBlock,
  message: "Resource temporarily unavailable" }

(runs 39785206572 and 39863141372; uid 1001 held 558 threads at an idle
moment). No OOM kills, no kernel messages, and kernel.threads-max was not
close -- a hit RLIMIT_NPROC fails clone() silently.

- [runtime] pids_limit sets the cgroup pids.max on the runner container
  and on every dind sibling of its job. Default 32768 per container; -1
  disables. A cgroup limit counts per container, so a fork bomb in one job
  cannot starve another and a busy neighbour cannot fail a build.
- The default RLIMIT_NPROC rises from 1024 to 1<<20: as a per-job limit it
  never worked, it only made jobs fail each other. An explicit
  [runtime.rlimits] nproc is still honoured.
@luthermonson
luthermonson merged commit babea76 into main Oct 23, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant