diff --git a/README.md b/README.md index 76c725e..9b27b21 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,7 @@ block is the canonical description; this side owns the names. |---|---|---| | `.stage-` | `cache-lib.sh`, `hardlink_clone_into()` | stranded; only the arbiter reclaims it | | `.publish-new-` | `publish-snapshot.sh` | stranded; see `daniel/gitdan#30` | -| `.publish-old--` | `publish-snapshot.sh` | swept by the next publish of that key | +| `.publish-old--` | `publish-snapshot.sh` | swept by the next publish of that key, if there is one | | `.evicting--` | `prune-cache.sh`, `evict_dir()` | swept at the start of the next prune pass | | `.reading--` | `cache-lib.sh`, `reader_lock_acquire()` | not garbage — see below | @@ -269,9 +269,11 @@ as `.stage-` does, so it is the one that needed catching. Bringing both under the arbiter's enumeration is tracked as `daniel/gitdan#30`. The two lists are meant to be the same length — the five names above, and the -prefixes `ci-cache-reclaim.sh` enumerates. A mismatch means one side gained a -shape without telling the other, which is the drift the rule exists to catch -and the cheapest thing to check. +prefix constants `ci-cache-reclaim.sh` *declares*. Not the subset it enumerates +as reclaim candidates: that one is smaller, because `.reading-` is read and +never swept. A mismatch means one side gained a shape without telling the +other, which is the drift the rule exists to catch and the cheapest thing to +check. **The staleness constants are part of the same contract, and that half has a direction.** `CACHE_READ_STALE_SECONDS` (`cache-lib.sh`) and diff --git a/scripts/cache-lib.sh b/scripts/cache-lib.sh index bf50169..2f94aea 100755 --- a/scripts/cache-lib.sh +++ b/scripts/cache-lib.sh @@ -32,7 +32,9 @@ # its clone and the swap. Strands the same way. # .publish-old-- publish-snapshot.sh: the rotated-away generation, # kept while a reader still holds it and swept by -# the next publish of the same key. +# the next publish of the same key — if there ever +# is one. A merged or renamed branch never +# publishes again, and its last one stays. # .evicting-- prune-cache.sh, evict_dir(): a cache renamed # aside so the decision to unlink it can be retaken # after the rename. Swept at the start of every @@ -63,9 +65,11 @@ # precisely the shape only the arbiter can reach. Bringing both in is tracked # as daniel/gitdan#30. # -# The two lists are meant to be the same length. A shape here without a prefix -# there is one side having changed without telling the other, and it is -# cheapest to notice by counting. +# The two lists are meant to be the same length: the five names above, and the +# prefix constants that script DECLARES — not the subset it enumerates as +# reclaim candidates, which is smaller because `.reading-` is read and never +# swept. A shape here without a constant there is one side having changed +# without telling the other, and it is cheapest to notice by counting. # # The two staleness constants the arbiter mirrors are part of the same # contract, and that half has a direction to it — see CACHE_READ_STALE_SECONDS diff --git a/scripts/publish-snapshot.sh b/scripts/publish-snapshot.sh index 0d8d2d5..bb9c647 100755 --- a/scripts/publish-snapshot.sh +++ b/scripts/publish-snapshot.sh @@ -12,9 +12,11 @@ # The swap is two renames, not one, because POSIX rename() can only replace an # EMPTY directory and a snapshot from a prior publish is not one: # -# 1. stage the new snapshot at .stage- (copy time is off every -# consumer's hot path — nothing reads a staging path); -# 2. rename the current snapshot aside to .publish-old-, if present; +# 1. stage the new snapshot at .publish-new-, which hardlink_clone_into +# builds at its own .stage- and renames there (copy time is off +# every consumer's hot path — nothing reads either path); +# 2. rename the current snapshot aside to .publish-old--, if +# present; # 3. rename the staged snapshot into place. # # Step 3 is a single atomic rename onto a path now guaranteed absent, so it @@ -60,8 +62,10 @@ DST=$(snapshot_dir_for "$ROOT" "$OWN_KEY") # per job per run, so a publisher killed between staging its snapshot and the # swap below strands a full hardlink clone that no later run of this script # will ever match. `.publish-old-` is milder — the sweep further down reclaims -# it on the next publish of the same key. Bringing both under the arbiter's -# enumeration is tracked as daniel/gitdan#30. +# it on the next publish of the same key, if there ever is one; a key whose +# branch is merged, deleted or renamed is never published again, and its +# rotated generation stays until something outside this repo takes it. +# Bringing both under the arbiter's enumeration is tracked as daniel/gitdan#30. OLD="${ROOT}/.publish-old-${OWN_KEY}-${TAG}" SNAP_NAME=$(basename "$DST") GRACE="${CACHE_READ_GRACE_SECONDS}" @@ -97,6 +101,8 @@ start=$(date +%s) # the snapshot it just published — the same aliasing hazard the consume side # closes, pointing the other way. The staging path is not the final name, so # `hardlink_clone_into`'s rename lands on DST only after OLD is out of the way. +# `.publish-new-` is a contract name — see the note at OLD above before +# changing it. TMP_DST="${ROOT}/.publish-new-${TAG}" rm -rf "$TMP_DST" hardlink_clone_into "$SRC" "$TMP_DST" "$TAG" || {