codeburn/tests/fixtures/cache-refresh-corrupt-owner.ts
Aditya Vikram Singh d5144593f3 fix(cache): recover a corrupt session-refresh lock instead of freezing ingestion
observe() classified a stable unparseable session-refresh.lock body as the
terminal 'unavailable'. parseAllSessions routes that to a read-only parse, so a
zero-byte or truncated lock froze warm-cache ingestion permanently across every
later run while each command still exited successfully.

A corrupt body is now a recoverable observation carrying a real mtime, and is
recovered only through the UNMODIFIED staleness gate — tryTakeover and the age
check are byte-identical to main. sameObservation gains an explicit null/non-null
boundary and a sha1 of the raw bytes, because two corrupt bodies have no tokens
to compare and mtime granularity is coarse on some filesystems.

The heartbeat deliberately does NOT rewrite a body it cannot prove is its own.
An owner that cannot prove ownership ends its ownership: mtime stops advancing,
the publication fence refuses, and a successor recovers the lock one staleMs
later. Losing that parse is the price of never having two owners.
2026-07-30 13:06:25 +05:30

22 lines
1.1 KiB
TypeScript

import { writeFile } from 'fs/promises'
import { join } from 'path'
import { acquireCacheRefreshLock } from '../../src/cache-refresh-lock.js'
// A plain owner in its own process. It records its outcome, its token, and the
// result of the publication fence into the barrier directory so the parent can
// assert what the owner itself believed while a contender was racing it.
const [cacheDir, barrierDir, holdMs, heartbeatMs = '200'] = process.argv.slice(2)
if (!cacheDir || !barrierDir || !holdMs) throw new Error('missing owner argument')
const refresh = await acquireCacheRefreshLock({ cacheDir, heartbeatMs: Number(heartbeatMs) })
if (refresh.outcome !== 'acquired') {
await writeFile(join(barrierDir, `owner.${refresh.outcome}`), '')
process.exit(0)
}
await writeFile(join(barrierDir, 'owner.acquired'), refresh.handle.token)
await new Promise(resolve => { setTimeout(resolve, Number(holdMs)) })
// The publication fence, exactly as parser.ts uses it before saving.
await writeFile(join(barrierDir, `owner.verify.${await refresh.handle.verifyStillOwner()}`), '')
await refresh.handle.release()
await writeFile(join(barrierDir, 'owner.done'), '')