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 maxWorkers inside 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions