cmd: bound per-runner check concurrency#2569
Open
bendrucker wants to merge 1 commit into
Open
Conversation
Per-runner check parallelism had no limit. Each ruleset's Check runs once per runner (root module plus one per module call), and the module-call checks were dispatched as one goroutine each with no bound. The only control was the boolean --no-parallel-runners; --max-workers did not apply. Configs with many module calls ran that many Check RPCs at once, which overwhelmed plugins that do per-runner network work (see terraform-linters/tflint-ruleset-aws#1113). Bound the per-runner fan-out with a semaphore sized by runnerWorkers(): the --max-workers value, the CPU count by default, or 1 under --no-parallel-runners. Extract the --max-workers resolution that recursive inspection used into a shared Options.maxWorkers() so one concurrency model covers both paths. Claude-Session: https://claude.ai/code/session_017eSDHWdGfds2STgoPE2saT
Member
|
In my experience, if a The reason I didn't set an upper limit on the parallel-runner concurrency is because it depends on the number of module calls. My intuition told me there wouldn't be that many module calls, but it seems I was wrong. How about starting with a sufficiently large fixed upper limit, like 10 or 20? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Per-runner check parallelism had no concurrency limit. tflint runs each ruleset's
Checkonce per runner (the root module plus one runner per module call), and dispatched the module-call checks as one goroutine each with no bound. The only control was the boolean--no-parallel-runners, which turns the fan-out fully off.--max-workersdid not apply. A configuration with many module calls therefore ran that many pluginCheckRPCs at once, and the only way to reduce the load was to serialize every runner.This bounds the per-runner fan-out by the
--max-workersvalue (default: number of CPUs), so parallelism has a ceiling instead of an on/off switch.Motivation
Checkis plugin code, and a plugin may do network or other rate-limited work per runner. When it does, unbounded fan-out becomes a load problem rather than a speedup.This surfaced in terraform-linters/tflint-ruleset-aws#1113. With
deep_checkand ~60provider "aws"instances, each runner opened many concurrent STS connections, and the runner count multiplied that into a connection storm that exhausted DNS and sockets.--max-workershad no effect. Only--no-parallel-runnersavoided the errors, at a large speed cost. The plugin side now caches clients across runners tflint-ruleset-aws#1114, but the core fan-out stayed unbounded for any plugin that does per-runner work.Two existing reports look like the same unbounded concurrency from other angles:
circular reference founderrors under parallel runners, gone with--no-parallel-runners.timeout waiting for connection infounder heavy load, with--no-parallel-runnerstried as a workaround.Change
The per-runner loop in
inspectModulenow acquires from a semaphore sized by a worker count before launching each check. The worker count comes fromrunnerWorkers():--max-workerswhen set, otherwise the CPU count, or 1 under--no-parallel-runners. The oldNoParallelRunnersbranch collapses into the N=1 case of the same path.The
--max-workersresolution that recursive inspection already used moves to a sharedOptions.maxWorkers(), so one definition now governs concurrency on both paths. The flag description now says it bounds both directory and per-runner workers.--no-parallel-runnersis unchanged in behavior: it still forces fully serial checks.Flag
This reuses
--max-workersrather than adding a flag, so a single, familiar control governs concurrency everywhere. If per-directory and per-runner concurrency turn out to need independent tuning, a dedicated flag (for example--max-runner-workers) could be layered on later without changing this default.One interaction to note: under
--recursive,--max-workersalready bounds the directory subprocess pool, and it is not currently propagated to those subprocesses. So each subprocess resolves its own per-runner bound, and the effective ceiling is roughly--max-workersdirectories times--max-workersrunners. That is bounded and far below today's unbounded fan-out, but we could also propagate the value to workers.Testing
Test_checkRunners_boundsConcurrencyblocks the checks mid-flight and samples the peak number running at once, asserting it never exceeds the worker count across serial, bounded, and worker-heavy cases. An unbounded implementation fails it.Test_checkRunners_returnsFirstErrorconfirms the first error surfaces and every runner is still drained.Test_Options_runnerWorkerscovers the worker-count resolution, including--no-parallel-runnersoverriding--max-workers.Origin
Per-runner parallelism landed in #1944 and the recursive
--max-workerspool in #2021. This reconciles the two so one concurrency model covers both paths.