Files
werkator/docs/prs/2026-10-03-PR#103-max-concurrent-per-repository.md
miandmhoennig 018db2e425
werkator build succeeded after 01:47
werkator/deploy build succeeded after 00:06
werkator/mutation build succeeded after 40:05
executor.maxConcurrentPerRepository caps one repository's builds (v1.34.0) (#103)
For vm4006: one hsadmin-ng backend build next to a frontend build, never two backend builds.

- New instance-level key `executor.maxConcurrentPerRepository` (unset = only the global cap).
- A build takes its repository slot before a global one, so a waiting build never blocks another repository.
- Sized once like `maxConcurrent`; a change needs a restart.

PR-doc: `docs/prs/2026-10-03-PR#103-max-concurrent-per-repository.md`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #103
2026-10-03 21:01:01 +02:00

60 lines
3.1 KiB
Markdown

> **WARNING:** This document describes only the change applied in this PR.
> It may already be outdated once the next PR is merged.
> Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.
## The Problem
vm4006 builds hsadmin-ng (backend) and hsadmin-ng.frontend.
The machine can take one backend build next to a frontend build, but not two backend builds.
Werkator knew two limits: `executor.maxConcurrent` over all repositories, and one build per repository and branch.
With `maxConcurrent: 2`, two backend branches could build at the same time; with `maxConcurrent: 1`, the frontend waited behind every backend build.
## Non-Goals
- No limit per registry entry: one value for all repositories covers the case; an exception per entry can follow when someone needs it.
- No build-definition key: what a machine can take is the host's decision, so ADR 0010 does not apply.
## The Scenarios
### Feature: A cap per repository below the global one
#### Scenario#103.01: A second build of the same repository waits, another repository's runs
So that a heavy repository never builds twice at once, and a light one is not held up by it.
- **Given** `maxConcurrent: 2` and `maxConcurrentPerRepository: 1`
- **And** a running build of one repository
- **When** a second build of that repository and then a build of another repository are queued
- **Then** the other repository's build takes the second slot, although it was queued last
- **And** the second build of the first repository starts once its repository's slot is free
##### Verified by
- [BuildExecutorIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorIntegrationTest.kt) — Scenario#103.01
- Without the key nothing changes: [BuildExecutorIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorIntegrationTest.kt) — "builds of different branches run concurrently"
#### Scenario#103.02: Unset by default, read like the rest of the section
So that every existing instance keeps its behaviour.
- **Given** a configuration without and with `executor.maxConcurrentPerRepository`
- **When** it is loaded
- **Then** it is unset, or the value given
##### Verified by
- [ConfigLoaderUnitTest](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderUnitTest.kt) — Scenario#103.02
## The Solution
- `ExecutorConfig.maxConcurrentPerRepository`, instance-level like the whole `executor` section, so no branch can raise it.
- `BuildExecutor` sizes it once with the global semaphore and keeps one semaphore per `RepoContext`.
- A build takes its repository's slot first and the global one second: a build waiting for its repository never sits on a global slot that another repository's build could use.
A build cancelled while it waits for its repository's slot gives it back without waiting for a global one.
- Like `maxConcurrent`, a change takes effect with the next restart.
## Additional Changes
- `docs/configuration.md`, `docs/deployment.md`, `docs/llms.md`, the `init` template, `AGENTS.md` and the architecture skill describe the key.
- Version 1.34.0 with its release note.