Skip to content

cache: strict cross-process gate for the warm session-cache refresh transaction#743

Draft
avs-io wants to merge 1 commit into
getagentseal:mainfrom
avs-io:fix/645-warm-refresh-lock
Draft

cache: strict cross-process gate for the warm session-cache refresh transaction#743
avs-io wants to merge 1 commit into
getagentseal:mainfrom
avs-io:fix/645-warm-refresh-lock

Conversation

@avs-io

@avs-io avs-io commented Jul 18, 2026

Copy link
Copy Markdown
Member

Draft on purpose: per the commitment in #645, this stays a draft until you give the go-ahead — it's here so the code is reviewable whenever bandwidth exists, not to jump the queue.

Implements the revised design (third comment on #645): first slice, warm session-cache transaction only.

What changed

  • hydrating.lock is untouched — still cold-only, advisory, fail-open.
  • New src/cache-refresh-lock.ts: a strict cross-process gate (session-refresh.lock) around the warm read→reconcile→parse→save transaction. Four explicit outcomes: acquired, completed-by-other (reload the now-fresh cache), timed-out / unavailable (serve the prior complete snapshot read-only — a correctness lock never fails open into mutation). Exclusive-create + random ownership token, 10s heartbeat, 90s staleness with a takeover-guard sidecar that never steals a heartbeating owner, in-process single-flight ahead of the filesystem lock, monotonic-clock wait deadlines, bounded Windows EPERM/EBUSY retries.
  • Publication fence: saveCache re-verifies ownership under the takeover guard immediately before the payload rename and aborts publication if displaced — a suspended/clock-jumped owner can no longer overwrite its successor's work.
  • Waiter budget is 30s, comfortably under the menubar's 45s subprocess timeout; a timed-out waiter serves the previous complete snapshot and never invalidates the holder.
  • Read-only serve path threads through parseAllSessions so timeout/stale serving performs no cache mutation at all.

How tested

  • Red-before: tests/cache-refresh-lock-process.test.ts is a real child-process lost-update regression — two warm processes parse disjoint changed files; with the lock wiring stashed it fails on stock code (later save drops the other's update), with the wiring it passes. Verified both directions locally.
  • Unit suite with injected clock/poll: every acquisition outcome, single-flight, heartbeat-prevents-takeover, exactly-one-takeover-winner, fence aborts a displaced save, timed-out waiter leaves the holder intact, token-scoped release.
  • Full suite: 2162 passed with dist built (the app/electron/cli.test.ts fresh-clone failures are app/electron/cli.test.ts fails on a fresh clone: dev-resolution tests require a built dist/cli.js #681, pre-existing).

Follow-ups per the design: daily-cache cross-process transaction (lock order daily → session), then #703's incremental parse runs inside this gate (draft PR opening alongside, stacked on this branch).

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