Skip to content

Two sessions of the loop took the same three tasks, and the obvious lock does not lock #3331

Description

@gHashTag

Two sessions of this loop ran concurrently and picked the same three tasks
from the same list of recommendations, opening PRs for all of them (#3314,
#3317). Nothing in the flow says who is working on what.

I found it the long way: a republish notice on the dashboard artifact. The
heading sets were identical — 738 = 738, zero either way — and the byte
counts matched, but the hashes did not. The only difference sat inside the
platform's injected runtime. Which meant my source file contained an injected
runtime — it was a downloaded copy of the published page — and that copy's
stamp read "pass 117 · all three", entries I had never written, sitting above
my own.

tri loop claim <name>

Git gives exactly one atomic operation over a shared remote: creating a ref
that does not exist. That is the whole mechanism.

tri loop claim pass-118            # 0 = it is yours, 1 = someone has it
tri loop claim pass-118 --who      # ask without taking
tri loop claim pass-118 --release  # give it back

Exit codes follow the repo's vocabulary: 0 taken · 1 refused, and the
line names the holder · 2 the attempt could not be made, which is not the
same as being refused.

The obvious version does not lock

Pushing origin/master to the claim tag succeeds for the second claimant
too
. Git treats re-pushing the same value to an existing tag as a no-op:

first push  exit 0
second push exit 0      <- both sessions now believe they hold it

Only a different value is rejected. Since two sessions of this loop sit on
the same origin/master almost by definition, the naive lock would have locked
nothing — and would have been worse than none, because it reports success.

So the claim is a commit no other claimant can produce: an empty tree with a
message naming this host, process and HEAD. Measured:

ALPHA claims: exit 0
BETA claims:  exit 1   already held -- loop claim `probe-118` by ... pid 76989

A structural test pins that the claim is built, not borrowed; mutating it
back to rev-parse origin/master fails the suite.

And a refused push with no holder is exit 2

If the push is refused but nobody holds the claim, that is a broken push, not a
lost race. Reporting it as HELD would send the next session away from work
nobody is doing — the same shape as a gate reporting could-not-run as clean.
Mutating that arm from exit(2) to exit(1) fails the suite.

That second test needed fixing before it could kill anything: the first version
sliced from the first match holder(name) to end of file and asserted
contains("exit(2)"), which four unrelated exit-2 sites satisfy. The mutant
survived it. Pinned to the arm.

Claimed pass-118 with it while writing this.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions