Skip to content

fix(T13299): global backup lock LOWs: clock at acquisition, abandon lost locks, sweep orphaned copies - #1965

Merged
kryptobaseddev merged 4 commits into
mainfrom
fix/T13299-backup-lock-lows
Oct 10, 2026
Merged

kryptobaseddev merged 4 commits into
mainfrom
fix/T13299-backup-lock-lows

Conversation

@kryptobaseddev

Copy link
Copy Markdown
Owner

Summary

Follow-up to #1948 (T13293). It fixes the three review-hotfix LOWs on the single-flight session-end global backup.

This branch includes #1948's commits until #1948 merges. Review only the top commit, d39da76.

  • LOW-1: the stale-window clock (lockedAt) now starts as soon as takeLock returns, not after the db-heavy admission, so the admission wait counts against the window.
  • LOW-2: a lock that was lost (compromised) or held past its stale window is abandoned, never released.
    • Skipping unlock() alone would not be enough. proper-lockfile also registers a signal-exit hook that rmdirSyncs every lock it still tracks, and its release removes the directory without checking ownership.
    • The new acquireAbandonableLock (store/lock.ts) passes proper-lockfile a guarded fs. Once abandon() is called, both rmdir and rmdirSync are no-ops for that lock, and the release clears the refresh timer and the tracking entry.
    • A directory that was still ours is already older than the stale window, so the next taker reclaims it.
  • LOW-3: sweepOrphanedGlobalCopies removes cleo.db.<id>.tmp copies and their -journal/-wal/-shm files once they are older than the stale window. It runs under the lock, before the due check. A live copy is younger, because VACUUM INTO keeps writing it.

Tests

  • lock-abandon.test.ts: release removes the directory. After a simulated takeover, abandon leaves the other holder's directory in place, a second abandon never throws, and the lock can be taken again.
  • backup-store-roundtrip.test.ts:
    • the compromised case abandons and does not release;
    • an admission that returns after the whole window discards the copy and abandons;
    • a normal backup releases and leaves no lock directory;
    • the sweep removes old .tmp/.tmp-journal files and keeps a live .tmp and an unrelated other.tmp.
  • Mutation-checked: each of the 4 mutants (clock after admission, always release, abandon not marking, no sweep) fails at least one test.
  • Targeted run: 22/22. tsc -b packages/core is clean, and so is biome.
  • Gates pass: 3 (strict), 32 (PR mode), 34, 38 (--base origin/main, with the new LOCK_TIMEOUT throw tagged none:local-only) and 40.

Task: T13299

🤖 Generated with Claude Code

kryptobaseddev and others added 4 commits October 6, 2026 11:30
team-lead follow-up on #1917: VACUUM INTO is synchronous, so proper-lockfile
cannot refresh the lock mtime during the copy; at stale 60_000 a copy over
60 s could be taken over as stale and a second copy start (the class #1941
fixed for exodus). Measured on SSD: 70 MB 0.29 s, 1 GB 2.4 s; slow/FUSE
disks 20-50x slower. GLOBAL_BACKUP_LOCK_STALE_MS = 10 min (>= 3x worst
case, EXODUS_LOCK_STALE_MS floor). onCompromised logs instead of throwing.

Test: a lock whose mtime is 90 s old (a holder blocked mid-copy) is not
stolen: no second backup. At stale 60_000 the test fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rded, not published

review-hotfix / team-lead on #1948: logging alone could still publish a
racing duplicate. The copy now lands as cleo.db.<id>.tmp; after it,
createGlobalBackup's discardIf (from autoGlobalBackup) discards it when
onCompromised fired or the copy outlasted GLOBAL_BACKUP_LOCK_STALE_MS (the
blocking copy can hide a compromise until it ends): tmp deleted, no rename,
no meta, no rotation, a warning; BackupResult.skipped says why.

Test: a lock reported compromised while held publishes nothing (dir
unchanged, no auto backup listed); without the discard check it fails.
Timing figures: Mac17,16 (Apple M5 Pro), internal APFS SSD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- LOW-1: the stale-window clock starts when the lock is taken, so the
  db-heavy admission wait counts against it.
- LOW-2: a lost or outlasted lock is abandoned, never released: the new
  acquireAbandonableLock routes proper-lockfile through a guarded fs, so
  neither its release nor its exit hook removes a directory another process
  may own now.
- LOW-3: cleo.db.<id>.tmp copies (and SQLite sidecars) a killed backup left
  behind are swept under the lock once older than the stale window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kryptobaseddev
kryptobaseddev merged commit 7c2e9f7 into main Oct 10, 2026
103 checks passed
@kryptobaseddev
kryptobaseddev deleted the fix/T13299-backup-lock-lows branch October 10, 2026 03:55
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.

1 participant