Skip to content

store: write stamp so a packed set built before a later write is never published or loaded - #89

Merged
thorwhalen merged 2 commits into
masterfrom
packed-cache-write-stamp
Sep 22, 2026
Merged

thorwhalen merged 2 commits into
masterfrom
packed-cache-write-stamp

Conversation

@thorwhalen

Copy link
Copy Markdown
Member

Closes #86

The pick

The issue listed three options. This takes the stamp option, with an explicit write-stamp token in place of the meta/ directory mtime:

  • Every put_record/delete_record replaces matrix/write-stamp with a fresh random token (atomic replace, after the record's files).
  • matrix() reads the token before listing records and writes it into sig.json. If the token changed during the build, it does not publish.
  • _load_packed treats a sig whose stamp differs from the current token as a miss.

Why a token and not mtime: a token comparison has no clock resolution, so a write in the same tick as the reader's stamp cannot slip through. No quiet period (a new magic number) is needed. A reader right after ir build still publishes at once, where a quiet period would have made it wait. Like mtime, it needs no cooperation from the CLI commands (unlike the write-session marker, which also needs stale-marker expiry). "Invalidate on every write" alone does not work, as the issue says.

Cost: one tiny extra file write per record write, with no fsync. In isolation this made put_record about 60-80% slower on Linux (3000 writes: about 1.1 s to 1.9 s). Real builds are dominated by embedding time.

Compatibility: sigs from an older ir carry no stamp. They stay valid until the first stamped write, then get rebuilt once. An older-ir writer doesn't stamp, so mixing versions keeps today's #86 behaviour, and nothing gets worse. CorpusStore.memory() (what raglab uses) is untouched.

Tests

  • The xfail(strict=True) repro test_reader_publishing_mid_build_does_not_hide_later_writes now passes, and the marker is removed.
  • New: a set built across a write is not published; a set published before a write (the writer's single clear already spent) is not loaded; delete_record invalidates too; an unchanged corpus keeps serving its packed set (no rebuild); an unreadable/empty stamp never matches; a failed stamp write removes the stamp.
  • The subprocess concurrency test now also asserts that a fresh matrix() sees all 40 records.
  • Adjusted: test_legacy_flat_packed_layout_still_loads removes the stamp its own put_record created (a legacy corpus has none). test_republishing_sweeps_older_generations expects the stamp file to survive a clear. A test's _save_packed stub accepts the new keyword.
  • Full suite locally: 583 passed, 5 skipped. Dependent raglab (under a/) against this branch: 49 passed.

Review

An independent refute-review found nothing blocking. Its fixes are applied in the second commit: an unreadable/empty stamp maps to a unique non-matching value, a failed stamp write removes the stamp, and the dir is created once per store. Known residual gaps: a writer killed between its meta write and its stamp write, and a power loss losing the un-fsynced stamp rename. Either can leave a set published during that one write loadable until the next write. Master has the same gap on every write.

🤖 Generated with Claude Code

thorwhalen and others added 2 commits September 22, 2026 15:55
…ver published or loaded

A reader that rebuilt the packed matrix while another process was writing
could publish a set missing the writer's later records; the writer clears
the cache only once per session, so every fresh process then served that
partial set (#86).

Every put_record/delete_record now replaces matrix/write-stamp with a fresh
random token, after the record files. matrix() reads the token before
listing records, stamps it into sig.json, skips publishing if it changed
during the build, and _load_packed treats a sig whose stamp differs from
the current one as a miss. A token, not the meta/ directory mtime, so a
write in the same clock tick cannot slip through and no quiet period is
needed. Sigs from an older ir carry no stamp and stay valid until the
first stamped write.

Closes #86

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… through

- An empty/unreadable stamp (e.g. mid in-place rewrite on Windows) reads as a
  fresh unique value, so it matches no sig and a build does not publish.
- A failed stamp write removes the stamp instead of leaving the old token a
  concurrent build may already hold.
- The packed dir is created once per store, not on every record write.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thorwhalen
thorwhalen merged commit d9026d4 into master Sep 22, 2026
12 checks passed
@thorwhalen
thorwhalen deleted the packed-cache-write-stamp branch September 22, 2026 16:06
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.

Packed matrix published by a reader during a build hides the writer's later records

1 participant