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

4.3 KiB

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

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

Scenario#109.03: Ending a process ends its whole tree

So that a cancellation leaves no child running.

Verified by

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

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

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.