Skip to content

Stop a completed run from reporting that it was interrupted - #32

Merged
tamnd merged 2 commits into
mainfrom
m14-drain-notice
Aug 2, 2026
Merged

Stop a completed run from reporting that it was interrupted#32
tamnd merged 2 commits into
mainfrom
m14-drain-notice

Conversation

@tamnd

@tamnd tamnd commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Found while running M14 (#18). Every pass ends by printing this, whether or not anybody signalled it:

campaign: 400 done, 0 failed, 0 skipped of 400 queued, 940 statements, 642 entailed, 96 in review, 2225619 tokens, 1h17m50s
draining, finishing the provisions already in flight, signal again to abort

Nobody signalled that run. It read all 400 provisions and then announced that it was being cut short.

The cause is the shape the signal handling was written in. signal.NotifyContext with a deferred stop and a goroutine waiting on ctx.Done means the normal return cancels the context too, the goroutine cannot tell the two apart, and the line goes out on the way past. The effect is that a log cannot distinguish a completed pass from an abandoned one, which is the only thing that line was ever for, and on passes that take hours the log is usually all anybody has.

The two ways out are separate channels now and the goroutine is told which one happened. The notice also goes to a writer the caller names rather than to os.Stderr directly: a goroutine reading a package level variable is a thing a test cannot get underneath, and the race detector says so rather than letting the test look like it passes.

Both the breadth front end and the older norm run had it, from the same copied block, so both are fixed and they share the helper now.

Two tests. The one that matters runs everywhere, because it is the case an ordinary passing run exercises. The one that sends a real SIGTERM is built out on Windows rather than skipped, since there is no signal there to send.

gofmt clean, go vet clean, golangci-lint 0 issues, go test -race ./... green, and GOOS=windows go build and go vet both clean.

tamnd added 2 commits August 2, 2026 14:39
Every pass ended by printing that it was draining after a signal, whether
or not anybody signalled it. The deferred stop cancels the context, the
goroutine waiting on ctx.Done wakes, and the line goes out. A person
reading the log afterwards cannot tell a run that read every document
from one somebody abandoned, which is the only thing the line was there
to say.

The two ways out are separate channels now, and the goroutine is told
which one happened. The notice goes to a writer the caller names because
a goroutine reading os.Stderr is a thing a test cannot get underneath
without racing it, and the race detector says so.

Both the breadth passes and the norm run had this. It showed up on a real
tax campaign that finished all 400 provisions and signed off as if it had
been cut short.
…s is a coin flip

The normal return closes done and then cancels the context, so a
goroutine that had not reached the select by then found both ready and
chose between them at random. That came back heads on Linux and macOS
and tails on Windows, which is how CI caught it.

The signal branch now asks whether the run had already returned. It can
trust the answer: the cancel it observed can only have come from the stop
function, and the close happened before it.

The test runs the case two hundred times rather than once, since one
iteration of a coin flip is what passed on two platforms out of three.
@tamnd
tamnd merged commit ed688e1 into main Aug 2, 2026
5 checks passed
@tamnd
tamnd deleted the m14-drain-notice branch August 2, 2026 07:47
@tamnd tamnd mentioned this pull request Aug 3, 2026
4 tasks
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