- `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
91 lines
4.3 KiB
Markdown
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.
|