Skip to content

vitest's own worker pool scales with host cores, uncapped, in 40/41 packages — compounds with turbo's outer --concurrency #11958

Description

@os-steve

Found while fixing #11938 (pnpm test OOM at turbo's default concurrency).

What's measured

Only packages/cli/vitest.config.ts mentions poolOptions/maxWorkers/
maxThreads anywhere in this repo's 41 vitest.config.ts files — and that
one mention is in a comment documenting a rejected lever, not live
config. Every other package runs vitest's own default pool sizing, which
scales with the host's own detected core count, independently of
turbo's --concurrency.

Live evidence, captured while measuring #11938's fix on a 4-CPU/~15GB
container: with turbo's outer --concurrency=4 (100% of this box), ps
showed 4 concurrent turbo test tasks running 10 total vitest worker
processes
(~2.5 workers/task average — some packages fork fewer, some
more). The two fan-outs multiply rather than one bounding the other.

Why this matters beyond #11938

#11938's fix bounds turbo's OUTER concurrency as a percentage of host
cores, which is the right fix for the reported OOM and is what CI's own
explicit --concurrency=4 already relies on. But because vitest's INNER
pool also scales with host cores, the compound cost on any given box is
roughly outer × inner, both of which grow with core count — so total
concurrent test-worker processes can grow faster than linearly with
core count. On a hypothetical much larger box (more cores than this repo's
CI/dev containers have today), that compounding could reproduce a
same-shape OOM at a higher core count than #11938's fix protects against,
even though the box has proportionally more memory — because the inner
lever isn't bounded by anything that tracks memory either.

packages/cli/vitest.config.ts's own header (from #10149) documents the
opposite direction of this same finding: pinning maxWorkersinside one
package's config was rejected there specifically because worker allocation
is a property of the shard (decided by whatever invokes turbo), not of
any one package's config — i.e., the fix belongs at the invocation layer,
not scattered across 41 files' test: blocks.

Suggested direction (not implemented here — out of scope for #11938,

which is scoped to "the root test-concurrency policy")

Bound vitest's own default pool sizing repo-wide from the invocation layer
(one place), rather than editing 41 vitest.config.ts files individually —
e.g. a shared vitest workspace/project config, or passing an explicit
--poolOptions.threads.maxThreads=N (or the forks pool's equivalent)
through whatever wraps vitest run in every package's own test script,
sized relative to turbo's own --concurrency so the product of the two
stays bounded rather than compounding. This needs its own measurement (peak
memory at various outer×inner combinations) and its own PR — a 41-file
surface with real behavior implications for every package's suite runtime,
not a one-line change.

Related: #11938 (the OOM this was found investigating), #9311 (closed —
same investigation lineage, a different mechanism: default vitest
timeouts, not memory).


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions