mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-05 22:30:58 +00:00
* cache: strict cross-process gate for the warm session-cache refresh transaction (#645) * lock: serialize the fence against the owner's own heartbeat verifyStillOwner and the heartbeat tick both take the takeover guard; without in-process serialization the fence could observe its own heartbeat's guard file, read it as displacement, and abort a legitimate publication. Fail-safe, but it discarded the parse the lock exists to protect (~6% of verifies at a 1ms heartbeat in the repro). Owner-side guard operations now run through one serializer; cross-process guard semantics are unchanged. Regression test at heartbeatMs 1, mutation- verified against the unserialized code. --------- Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com> Co-authored-by: reviewer <review@local>
43 lines
1.8 KiB
TypeScript
43 lines
1.8 KiB
TypeScript
import { existsSync } from 'fs'
|
|
import { mkdir, readFile, writeFile } from 'fs/promises'
|
|
import { join } from 'path'
|
|
|
|
import { acquireCacheRefreshLock } from '../../src/cache-refresh-lock.js'
|
|
import { loadCache, saveCache } from '../../src/session-cache.js'
|
|
|
|
const [cacheDir, barrierDir, id, sourcePath, bypass = 'false'] = process.argv.slice(2)
|
|
if (!cacheDir || !barrierDir || !id || !sourcePath) throw new Error('missing worker argument')
|
|
|
|
async function waitFor(name: string): Promise<void> {
|
|
const path = join(barrierDir!, name)
|
|
while (!existsSync(path)) await new Promise(resolve => { setTimeout(resolve, 5) })
|
|
}
|
|
|
|
process.env['CODEBURN_CACHE_DIR'] = cacheDir
|
|
await mkdir(barrierDir, { recursive: true })
|
|
|
|
const refresh = bypass === 'true' ? null : await acquireCacheRefreshLock({ cacheDir, waitMs: 2_000, pollMs: 5 })
|
|
if (refresh && refresh.outcome !== 'acquired') {
|
|
await writeFile(join(barrierDir, `${id}.${refresh.outcome}`), '')
|
|
process.exit(0)
|
|
}
|
|
|
|
try {
|
|
const cache = await loadCache()
|
|
// Parsing is deliberately tiny; the files and barrier make the transaction
|
|
// interleaving deterministic rather than relying on parser runtime variance.
|
|
const parsed = JSON.parse(await readFile(sourcePath, 'utf-8')) as { output: number }
|
|
cache.providers['regression'] ??= { parseVersion: 'test', envFingerprint: 'test', files: {} }
|
|
cache.providers['regression'].files[sourcePath] = {
|
|
fingerprint: { dev: 1, ino: parsed.output, mtimeMs: parsed.output, sizeBytes: parsed.output },
|
|
mcpInventory: [],
|
|
turns: [],
|
|
}
|
|
;(cache as { _dirty?: boolean })._dirty = true
|
|
await writeFile(join(barrierDir, `${id}.parsed`), '')
|
|
await waitFor(`${id}.save`)
|
|
const published = await saveCache(cache, refresh?.handle.verifyStillOwner)
|
|
await writeFile(join(barrierDir, `${id}.${published ? 'published' : 'fenced'}`), '')
|
|
} finally {
|
|
await refresh?.handle.release()
|
|
}
|