Skip to content

advance takes the board lock before discovering a no-op, so an idempotent confirm replay can fail on contention #736

Description

@pbean

Follow-up residual from PR #726 (the #286 + #469 ledger/board cross-process serialization
program). Filed rather than fixed there because the remedy contradicts an invariant that PR
deliberately pinned with a test.

The defect

sprintstatus.advance acquires the board lock before _advance_locked discovers there is
nothing to write. An idempotent bmad-loop confirm replay against a story already at or
beyond target — previously a successful no-op — can therefore fail when the lock is
unavailable, because it acquires before learning it had no work to do.

Reported by codex on PR #726 and verified correct against the code:
#726 (comment)

This is the third instance of one class

The general shape: a lock taken for an operation that will not write turns a previously
successful no-op into a failure.
PR #726 fixed the other two instances on exactly that
reasoning:

  • archive_closed over a missing ledger (now skips the lock)
  • empty mark_done_many / mark_open_many batches (now skip the lock)

Consistency argues for fixing this site too. A fix should sweep the class rather than patch
this one report — a reviewer found the same shape at two sites across consecutive rounds, so
a per-report patch is likely to leave another instance standing.

Why it was not fixed in #726

The suggested remedy — probe for the already-at-target and absent-story cases before
acquiring — puts a board read above the lock. story_status goes through load, and
test_advance_holds_the_lock_across_every_read_and_the_write asserts
events[0] == "lock-enter" with events[1:-1] == ["load", "load", "write"].

That assertion is not incidental. It encodes #726's decision that all three reads move
under the hold, which is what closes the intra-call TOCTOU between the never-regress
decision and the bytes that decision is applied to. A pre-lock probe reopens a read outside
the lock and reddens that test by construction.

The actual trade-off

Either:

  1. Keep every read under the hold and accept that a no-op advance can fail when the lock
    is unavailable (status quo), or
  2. Admit an advisory pre-lock probe as an early-out, with the authoritative never-regress
    decision still made under the lock, and relax the pinned ordering to "the reads that
    decide the published bytes are inside the lock".

Option 2 is defensible — the probe would be advisory only, and the published bytes would
still be decided under the hold — but it changes a reviewed decision and requires rewriting
the test that protects it. That is a design call, not a repair, which is why it is here
rather than in #726.

Related: #726, #286, #469, #735.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions