ci: stop starving vitest workers to death #126
No reviewers
Labels
No labels
dependencies
major
security-pin
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
trails-cool/trails!126
Loading…
Reference in a new issue
No description provided.
Delete branch "ci-vitest-worker-starvation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Checksfails intermittently with every test passing:This is not the cold-import timeout
vitest.shared.tsalready handles, andtestTimeoutcannot touch it — the run fails because a worker never finished starting. That deadline is a hardcodedSTART_TIMEOUTconstant inside vitest's pool runner (60s), with no config option and no environment variable to raise it. Confirmed by reading the shipped bundle:So the only available lever is to stop starving the workers.
The starvation is multiplicative, and nothing in the repo made it visible
pnpm test→turbo testvitest runos.availableParallelism()— the host CPU count, because a cgroup cpu limit does not change it → up to (n-1) forkscpus: 3, mem_limit: 10gfor the dind service in the runner compose on the BRouter host)Ten packages × nine forks ≈ 90 node processes on three CPUs. A fork missing a 60s handshake under that load is unsurprising.
The fix
TURBO_CONCURRENCY=3on the CI step plusmaxForks: 2invitest.shared.tsbounds the product at ~6 workers instead of ~90. The fork cap applies only whenCIis set — locally the cores exist, and capping would just makepnpm testslow.Verified with
CI=1 TURBO_CONCURRENCY=3 pnpm test: 12 tasks successful, every suite green.Two things this is NOT
Checksfailed 71 of the last 300 runs (23%), and that population also contains genuine failures — openspec 1.12 rejecting five specs on #104, and theTS2724on #120. This removes one contributor; it does not account for 23%.Follow-up worth considering
The runner resource limits live in host-side compose outside this repo, where nothing here can see or review them. That is the same blind spot that made the playwright image version unfixable in-repo until it was declared in
ci.yml.`Checks` fails intermittently with every test passing: Error: [vitest-pool]: Failed to start forks worker for test files .../packages/i18n/src/client.test.ts Caused by: [vitest-pool-runner]: Timeout waiting for worker to respond Test Files 10 passed (10) Tests 89 passed (89) Errors 1 error This is NOT the cold-import timeout that vitest.shared.ts already handles, and testTimeout cannot touch it: the run fails because a worker never finished *starting*. That deadline is a hardcoded START_TIMEOUT constant inside vitest's pool runner — 60s, with no config option and no environment variable to raise it (confirmed by reading the shipped bundle). So the only lever is to stop starving the workers. The starvation is multiplicative, and nothing in the repo made it visible: pnpm test -> turbo test, default concurrency 10 11 packages -> each defines a `vitest run` task each vitest -> sizes its fork pool from os.availableParallelism(), which reports the HOST cpu count because a cgroup cpu limit does not change it, so up to (n-1) forks the job container -> is allowed 3 CPUs (`cpus: 3, mem_limit: 10g` for the dind service in /home/trails/forgejo-runners/ docker-compose.yml on the BRouter host) Ten packages by nine forks is ~90 node processes contending for three CPUs. A fork missing a 60s handshake under that load is unsurprising. TURBO_CONCURRENCY=3 on the CI step plus maxForks=2 in vitest.shared.ts bounds the product at ~6 workers instead of ~90. The fork cap is applied only when CI is set — locally the cores exist and capping would just make `pnpm test` slow. Two things this is NOT: - Not a proven fix for the whole failure rate. `Checks` failed 71 of the last 300 runs (23%), and that population also contains genuine failures: openspec 1.12 rejecting five specs on #104, and the TS2724 on #120. This removes one contributor. - Not the borgmatic I/O window. That was the first hypothesis, since the BRouter host's backup saturates I/O in the morning and main's failure was at 08:56 UTC. Bucketing all 300 runs by hour does not support it — failures sit near the 23% baseline across the day rather than clustering. Verified locally with `CI=1 TURBO_CONCURRENCY=3 pnpm test`: 12 tasks successful, every suite green. Follow-up worth considering: the runner resource limits live in host-side compose outside this repo, where nothing here can see or review them. That is the same blind spot that made the playwright image version unfixable in-repo until it was declared in ci.yml. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W9K6hHEZx5aqyWZKHNrxsD