Skip to content

guard: make the stopped run hole certain instead of raced for - #90

Merged
donislawdev merged 1 commit into
mainfrom
guard/stopped-run-hole
Sep 8, 2026
Merged

guard: make the stopped run hole certain instead of raced for#90
donislawdev merged 1 commit into
mainfrom
guard/stopped-run-hole

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

TestARunStoppedPartWayNamesEveryFileThatFinished was flaky, roughly one run in four inside a full suite. It needs a run where one writer is still owed bytes while its neighbours have already been renamed into place, and it bought that state with size - a first file sixteen times the size of the rest.

The recorded diagnosis was wrong

The observation said the flakiness depended on how fast the machine writes. Measured with a new probe, fifty runs per condition:

condition failures
idle machine 0 in 50, with 165 ms of margin never used
heavy disk writing beside it 0 in 25
CPU starved, 24 busy loops on 16 threads 14 in 25

The disk is not involved. Size buys margin in work, and which writer finishes first is decided by which one gets a processor. Under starvation the time to the first finished file goes from 11 ms to 5.8 s - three orders of magnitude - at which point sixteen times the work means nothing. A full suite runs packages beside each other and builds binaries in sub processes, so a starved machine is the normal condition there rather than an exotic one.

The fix removes the race rather than widening it

The file at index zero now carries a generator that writes nothing and returns only once the run is cancelled, so it cannot finish no matter who gets a processor.

Descriptor is a value and every planned file carries its own copy, so this is installed after planning, on one PlannedFile. The registry is untouched, the other thirty one files write real bytes through the real generator, and every step of the engine below Plan is the one that ships.

The guard also asserts it has at least two writers, because with one the held open file would wait for a cancellation nobody can send. A deadline in the generator turns a hypothetical deadlock into a sentence naming the guard that caused it, rather than a hung package.

Verified

  • 25 of 25 green under the same starvation that failed the old version 14 times in 25
  • the mutation that restores the prefix behaviour still reddens it, so the hole is genuinely reached
  • -race clean over 5 runs, staticcheck v0.8.1 clean, gofmt and vet clean
  • all seven neighbours in safety_test.go green, all fifteen code shape ceilings green, document consistency guards green, staleness.py reports every pattern occurring exactly once
  • the guard now writes 3.5 MiB where it used to write 94 MiB

The five guards in progress_test.go were measured under the same starvation as well - 50 of 50 green, because none of them cancels a run or assumes who finishes first.

Not run: the full suite and preflight, deliberately - this goes to CI.

🤖 Generated with Claude Code

TestARunStoppedPartWayNamesEveryFileThatFinished needs a run where one
writer is still owed bytes while its neighbours have been renamed into
place. It bought that state with size - a first file sixteen times the
size of the rest - and that made it flaky, roughly one run in four
inside a full suite.

Size buys margin in work, and which writer finishes first is decided by
which one gets a processor. Measured with tools/probes/stoprace, fifty
runs per condition:

  idle machine                  0 failures in 50, 165 ms of unused margin
  heavy disk writing beside it  0 failures in 25
  CPU starved, 24 busy loops    14 failures in 25

So the disk was never involved, which is what the observation first
recorded. Under starvation the time to the first finished file goes from
11 ms to 5.8 s, and sixteen times the work stops meaning anything. A
full suite runs packages beside each other and builds binaries in sub
processes, so a starved machine is the normal condition there.

The file at index zero now carries a generator that writes nothing and
returns only once the run is cancelled, so it cannot finish whoever gets
a processor. Descriptor is a value and every planned file carries its
own copy, so this is installed after planning on one PlannedFile - the
registry is untouched, the other thirty one files write real bytes, and
every step of the engine below Plan is the one that ships.

The guard also asserts it has at least two writers, because with one the
held open file would wait for a cancellation nobody can send.

Measured after the change: 25 of 25 green under the same starvation, the
mutation that restores the prefix behaviour still reddens it, and the
guard writes 3.5 MiB where it used to write 94 MiB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit 1f6f004 into main Sep 8, 2026
18 checks passed
@donislawdev
donislawdev deleted the guard/stopped-run-hole branch September 8, 2026 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant