The worker pool and the default workspaceConcurrency took the CPU count
from os.availableParallelism(), which counts the CPUs in the process's
affinity mask. In a container with a cgroup CPU quota smaller than that
mask, such as a Kubernetes pod with a 4-CPU limit on a 233-CPU node,
pnpm 11 created 232 workers per install, and four concurrent installs
exhausted a 16 GiB memory limit while the workers were still loading.
Node's libuv does try to apply the quota, but its cgroup v1 parser
looks for ":cpu," and keeps "cpuacct:" in the path it builds, so it
never finds the quota file there, and both it and Rust's standard
library give up when the cgroup path from /proc/self/cgroup is not
mounted under /sys/fs/cgroup, as happens with a host cgroup namespace
and a bind-mounted controller.
A new package, `@pnpm/os.available-parallelism`, reads the quota itself:
the smallest limit/period walking from the process's cgroup up to the
hierarchy root, for cgroup v2 (cpu.max) and v1 (cpu.cfs_quota_us and
cpu.cfs_period_us), rounded down and floored at 1 like Rust's
std::thread::available_parallelism, which pnpm 12 already uses. The
cgroup directory is located through /proc/self/mountinfo, which says
which cgroup each mount shows, so a container that sees only its own
cgroup bind-mounted at /sys/fs/cgroup is read correctly; the standard
mount points are the fallback. The worker pool and the config reader's
workspace-concurrency default both use it, so the two stay in step.
pnpm 12 is unaffected: every Rust pool is sized with
std::thread::available_parallelism, which honours the quota.
Closespnpm/pnpm#17382