Parallel (and therefore every expanded matrix, since expand_*_matrix returns a Parallel) launches all children at once via an unbounded asyncio.TaskGroup (src/camas/core/execution.py:execute). There's no way to bound how many leaf subprocesses run concurrently.
Motivation
A modest matrix multiplies quickly: a 5-version × 2-leaf test matrix spawns 10 uv run pytest processes simultaneously, each potentially syncing a venv and running a full suite. On a laptop or small CI runner this oversubscribes CPU/RAM/disk and can end up slower (or OOM) than a bounded run.
Suggestion
An opt-in global cap — --jobs N (and/or CAMAS_JOBS) — as an asyncio.Semaphore(N) acquired around run_cmd. The tree shape stays unchanged (still logically parallel, just throttled at the subprocess boundary); default remains unbounded.
Found while evaluating camas as a poe replacement for a 5-Python-version test matrix.
Parallel(and therefore every expanded matrix, sinceexpand_*_matrixreturns aParallel) launches all children at once via an unboundedasyncio.TaskGroup(src/camas/core/execution.py:execute). There's no way to bound how many leaf subprocesses run concurrently.Motivation
A modest matrix multiplies quickly: a 5-version × 2-leaf test matrix spawns 10
uv run pytestprocesses simultaneously, each potentially syncing a venv and running a full suite. On a laptop or small CI runner this oversubscribes CPU/RAM/disk and can end up slower (or OOM) than a bounded run.Suggestion
An opt-in global cap —
--jobs N(and/orCAMAS_JOBS) — as anasyncio.Semaphore(N)acquired aroundrun_cmd. The tree shape stays unchanged (still logically parallel, just throttled at the subprocess boundary); default remains unbounded.Found while evaluating camas as a poe replacement for a 5-Python-version test matrix.