Files
werkator/docs/prs/2026-10-04-PR#109-command-process.md
miandmhoennig 2061d76d38
werkator build succeeded after 02:01
werkator/deploy build succeeded after 00:07
Build command process and build logs moved out of BuildExecutor, tested with real short processes (v1.34.6) (#109)
- `CommandProcess`: one build command while it runs (log pumps, processor-time sampling, deadline via `awaitExit`, `destroyTree`).
- `BuildLogs`: the three log files of a build, opened and closed together; `runBuildCommands` uses one `use`.
- `CommandProcessIntegrationTest`: real processes, ~4 s, kept by the nightly mutation build.
- runBuildCommands 20.78 -> 6.84; every BuildExecutor method below 15 (max settle 12.19). Gate stays 46 (NginxProxyManager.cleanupStaleContainers 45.04).

PR-doc: `docs/prs/2026-10-04-PR#109-command-process.md`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #109
2026-10-04 16:56:45 +02:00

91 lines
4.3 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
After PR#108 the rest of `BuildExecutor` above the limit was its process handling: copying a command's output into the logs, sampling its processor time, keeping its deadline, ending its process tree.
`runBuildCommands` stood at 20.78; it opened the three log files in three nested `use` blocks, and every `use` is several branches.
This part needs real processes, and the only test with real processes, `BuildExecutorIntegrationTest`, takes minutes and stays out of the nightly mutation build.
## Non-Goals
- No change in behaviour: the same logs, the same deadline, the same termination.
- `BuildExecutorIntegrationTest` stays out of the nightly run.
## The Scenarios
### Feature: A build command's process
#### Scenario#109.01: A command's output goes to its log and to the live log, and its exit code is the answer
So that the page that follows a build shows both streams, and each log only its own.
##### Verified by
- [CommandProcessIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/build/CommandProcessIntegrationTest.kt) — Scenario#109.01
#### Scenario#109.02: A command past its deadline is told, its whole tree is ended, and it reports a failure
So that a hanging build cannot outlive its timeout through a child process.
- **Given** a command that starts a child and waits for it, and a deadline of 300 ms
- **When** the command is awaited
- **Then** the timeout is reported once, the command and its child are gone, and the exit code is not 0
- **And** a command within its deadline ends with its own exit code
##### Verified by
- [CommandProcessIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/build/CommandProcessIntegrationTest.kt) — Scenario#109.02
#### Scenario#109.03: Ending a process ends its whole tree
So that a cancellation leaves no child running.
##### Verified by
- [CommandProcessIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/build/CommandProcessIntegrationTest.kt) — Scenario#109.03
#### Scenario#109.04: The processor time of a command is measured where it is the build's own, and only there
So that a Docker build records no processor time rather than the client's second.
##### Verified by
- [CommandProcessIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/build/CommandProcessIntegrationTest.kt) — Scenario#109.04
#### Scenario#109.05: A log name that is a path is refused before anything is opened
So that a build's output never lands outside its staging directory.
##### Verified by
- [CommandProcessIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/build/CommandProcessIntegrationTest.kt) — Scenario#109.05
## The Solution
- `CommandProcess` is one command while it runs: the pumps into the logs, the processor-time sampling, `awaitExit` with the deadline, `finish`, and `destroyTree` for the whole process tree.
- `BuildLogs` opens the three logs of a build and closes them together; `runBuildCommands` uses it in one `use`.
- `BuildExecutor` keeps the order of the commands; `startCommand` and `becomeBuildProcess` make a started process the one a cancellation ends, preparation processes included; `deadlineOf` takes the build's one deadline.
- `CommandProcessIntegrationTest` runs real processes of a second or two, about four seconds in all, so the nightly run keeps it.
Measured with PIT as the nightly build runs it:
| Method | Before | After |
| ---------------------------------------- | ------ | ----- |
| `BuildExecutor.runBuildCommands` | 20.78 | 6.84 |
| `destroyTree` (was `destroyProcessTree`) | 12 | 4.94 |
| `awaitExit` (was `waitForBounded`) | 9.49 | 4.03 |
| the processor-time sampling | 12 | 4.13 |
Every method of `BuildExecutor` is below 15 now; the highest is `settle` at 12.19.
The gate `maxMutationCrap` stays at 46: the highest score of Werkator is `NginxProxyManager.cleanupStaleContainers` at 45.04.
## Additional Changes
- Version 1.34.6 with its release note.
## Follow-up PRs
- `NginxProxyManager.cleanupStaleContainers` (45.04), `UiFormats.cpuTime` (43.81) and `MetricsConfiguration.monitoredDisks` (42), the highest scores left.