4 Commits

Author SHA1 Message Date
iamtoruk 272da806e4 fix(menubar): no-output watchdog, lock signal cleanup, dead-pid takeover, serve orphan reaping
The 0.9.20 menubar never completed a fetch on a large corpus: the cache
version bump forced a full rehydration, DataClient's fixed 45s kill ended
it mid-transaction, cache-refresh-lock had no signal cleanup so the dead
holder's lock survived, and DEFAULT_WAIT_MS (30s) < DEFAULT_STALE_MS (90s)
meant no waiter could ever recover it.

A. Swift port of #1096's watchdog. CLIWatchdog holds the constants and the
   pure verdict; spawns and the resident serve child set CODEBURN_PROGRESS=1;
   the window restarts on any stdout/stderr byte; 45s silence, 10min cold
   floor until the first payload, 15min ceiling, SIGTERM then SIGKILL after
   5s. ServeConnection's fixed 60s warm cap becomes the same silence window,
   re-armed by each progress frame, and a spent death budget is now a
   5-minute cooldown instead of disabling the resident for the app run.

B. cache-refresh-lock arms SIGINT/SIGTERM cleanup the way session-cache does
   for hydrating.lock, so a SIGTERMed holder unlinks its own lock.

C. Staleness also opens on a dead holder pid, and the waiter budget derives
   from staleMs so it can never expire before the gate it waits for. A live
   holder - fresh heartbeat, pid answers signal 0 - is never taken from.
   parser.ts heartbeats through the lock wait, the one silent stretch left.

D. The app closes its end of a retired child's stdin (dropping the handle
   left the pipe alive inside the Process), reaps every serve child
   synchronously at quit, and records pid+argv so a crash-orphaned child is
   reaped next launch. serve's final exit no longer runs through a
   monkeypatched process.exit, and its post-drain cleanup is bounded.

Closes #1117
2026-08-23 05:35:17 -07:00
Aditya Vikram Singh a3beafba50 fix(cache): accept completed-by-other as the stale-lock loser
The process suite required the loser of a stale-lock contest to
be timed-out. On a slow runner the winner's unlink-guard /
create-successor gap is missing+missing, which the lock honestly
reports as completed-by-other (#904).

Exactly one owner still publishes. The loser may be timed-out,
completed-by-other, or unavailable — never parsed. Do not delay
the clean-release path by treating missing+missing as wait-out.
2026-08-21 21:59:54 +05:30
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
Aditya Vikram Singh e27fe36bd5 cache: strict cross-process gate for the warm session-cache refresh transaction (#743)
* 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>
2026-07-20 09:06:35 -07:00