Skip to content

v1.12.11 - a run whose lock was taken over stops before its next write

Choose a tag to compare

@github-actions github-actions released this 17 Oct 22:07
· 50 commits to main since this release
v1.12.11
b49cc77

A run whose lock was taken over stops before its next write

A wash, RE-TIER or ULTRA run whose lock went stale and was taken over by another run now stops before its next write instead of writing on beside the new holder. Nothing is removed from the plugin and no command or config key changes; a refusal gains additive fields (robbed, committed and lockLost, plus lockLeft when a lock had to be left behind, which also rides a finished wash, RE-TIER or ULTRA result and a thrown error). The installed plugin moved in four files (git diff --stat 904e783 HEAD -- plugin/: 4 files, 262 insertions, 45 deletions): the plugin/ twins of scripts/lib/apply.mjs, scripts/lib/retier.mjs and scripts/lib/estate-archive.mjs (git diff --stat 904e783 HEAD -- plugin/scripts/lib/: 3 files, 261 insertions, 44 deletions), and the version in plugin.json.

Fixed

  • A run whose lock was taken over after it went stale no longer writes beside the new holder. A lock older than LOCK_STALE_MS (30 minutes) is presumed dead and another run may take it over; a run that had only stalled (a suspended machine, a long pause) then woke and kept writing, and its rollback could overwrite what the new holder had written since. Now the wash transaction (applyPlan's fence), RE-TIER (runRetier's stolen) and the ULTRA estate run (archiveSession's new lockState option) each re-read their lock immediately before every write step: one bounded read per step, none inside a byte, line or content loop. A run robbed of its lock returns a refusal (with robbed and lockLost: 'stolen') that starts lock stolen: another CoalWash run took over the <project|global> lock and says what it left on disk. It does not roll back, because a restore would overwrite the new holder's work. RE-TIER keeps every archive copy it wrote, and an ULTRA failure after a theft removes no partial copy, since the new holder may be archiving to the same paths. Residuals: a theft landing between the lock read and the write it guards still goes through (a filesystem cannot check a token); the new holder overwrites the robbed run's single journal, after which that run's record lives in its refusal and its snapshot (kept for the newest 3). — test: scripts/lib/apply.test.mjs, scripts/lib/retier.test.mjs and scripts/lib/estate-archive.test.mjs, the tests named fire 119: ... (for example fire 119: every fence of a committing transaction stops a robbed run on the spot (15 fences, one theft each) and fire 119: an archive failure after a theft removes nothing: the partial copies stay (the new holder may own copies at those paths))
  • A lock file another program holds open for a moment is no longer reported as a takeover by another CoalWash run. An indexer, antivirus or sync client can hold the lock file open for milliseconds, so a read of it fails. A failed read is now retried (LOCK_READ_RETRIES 8, LOCK_READ_WAIT_MS 125: about one second); a read that succeeds is final, and a missing or foreign token is a theft as before. A lock still unreadable after that stops the run with its own refusal, lock unreadable: ... could not read its own <project|global> lock file ..., with lockLost: 'unreadable' (and robbed, which RE-TIER and ULTRA use to keep their copies). It says what landed; the journal on disk is still that run's, so the next gauge's dangling-transaction recovery rolls the store back from the snapshot, which undoes the wash even when every step had landed. The release retries the same way, and a release that cannot read or remove the lock never deletes a lock it cannot prove is its own: it leaves it in place and says so in lockLeft (the note is appended to a refusal's error, listed in flagged on a finished wash, printed as LOCK LEFT: in the RE-TIER and ULTRA reports, and carried on a thrown error as e.lockLeft). A later run defers on that lock until it goes stale (30 minutes), or the user removes it once no CoalWash run is active. A lock file that cannot be read back right after it is created defers with that reason instead of "lost a race". — test: scripts/lib/apply.test.mjs, scripts/lib/retier.test.mjs and scripts/lib/estate-archive.test.mjs, the tests named fire 122: ... and fire 123: ... (for example fire 122: a lock file unreadable for a moment mid-run (EBUSY for 3 reads) is retried: the run finishes, releases its lock and reports no theft and fire 123: an unexpected error inside the transaction still names a lock the release had to leave behind)