fix(desktop): release reconnect-orphaned warm transcripts once their authoritative state settles - #95338
fix(desktop): release reconnect-orphaned warm transcripts once their authoritative state settles#95338BrunoBza wants to merge 1 commit into
Conversation
…authoritative state settles A gateway connection that dies mid-turn leaves cached session snapshots whose busy/awaitingResponse flags can never settle: the respawned backend re-mints runtime ids, so no terminal publish ever reaches the orphaned snapshot again. #isWarmSettled treated those frozen flags as live work, so every orphan pinned its full warm transcript until app restart — roughly 5MB per reconnect cycle, which turned the restart loop in NousResearch#95189 into renderer OOM. SessionStateCache now accepts an optional isAuthoritativelyActive probe. When wired, in-flight flags only block eviction while the authoritative $sessionStates record still claims work for the same runtime id; without the probe the legacy always-block behavior is preserved byte-for-byte. Eviction remains gated on needsInput, pending drafts, and active references, so a genuinely running turn (which re-asserts busy on every publish) is never a casualty. The useSessionStateCache hook wires the probe to the store it already imports. Reconnect reconciliation (reconcileBusyStatesOnReconnect) settles the authoritative record, and the next prune drains the orphaned cache entry through the normal LRU path, ownership included.
|
This lands exactly right, and for full transparency: it's the deterministic criterion laid out in #95276 (comment) — trust frozen in-flight flags only while the authoritative What I checked against source, all clean:
One non-blocking observation for the record: there's a small theoretical window during a transient blip on a live socket — reconciliation clears the turn's Residual gap (fine as follow-up): snapshots whose scope outlived the socket and were never reconciled — e.g. preserved Nice work — this is the renderer half of #95189 done properly. |
What
Adds an optional liveness probe to
SessionStateCacheso warm transcripts orphaned by a mid-turn connection death stop being pinned forever by frozenbusy/awaitingResponseflags.Partially addresses #95189 (the renderer-side retention guardrail; see scope note below).
The leak
When a gateway connection dies mid-turn, reconnect reconciliation (
reconcileBusyStatesOnReconnect) correctly settles the authoritative$sessionStatesrecord. But the per-session transcript cache keeps its own snapshot under the old runtime id - and the respawned backend re-mints runtime ids, so no terminal publish ever reaches that snapshot again. Its cachedbusy: trueis frozen forever.#isWarmSettledtreated those frozen flags as live work, so the orphaned cache entry was never evictable: every reconnect cycle stranded another full warm transcript (~5MB) until app restart. Under the ~2-minute restart loop reported in #95189 that churn compounds into the renderer OOM the reporter measured (~5GB/day growth).Fix
SessionStateCachenow accepts an optionalisAuthoritativelyActive(runtimeId)callback. When wired:needsInput, pending drafts/in-flight messages, and active references, so a genuinely running turn (which re-asserts busy on every publish) is never a casualty.Without the probe the behavior is byte-for-byte identical to today - existing constructions keep the always-block semantics.
The only production wiring is
useSessionStateCache, which reads the same$sessionStatesstore it already imports.Regression coverage (behavior-contract)
All new tests verified RED on pristine
origin/main(71d804d) before the fix and GREEN after:busy/awaitingResponsetranscripts are released once the authoritative record settles (probe wired);tsc -p . --noEmit: no diagnostics in touched files (only the pre-existing@tabler/icons-reactdeclaration errors on unrelated files, reproduced on pristine main). ESLint and Prettier clean.Scope note
This covers the guardrail half of #95189 ("mark stalled sessions evictable"). The unclean-exit restart loop itself is WSL2-environmental and needs lifecycle-sentinel forensics (boot-id instrumentation) - the direction already sketched by @Finn763 in #95237 (comment), which explicitly reserved "renderer-side retention guard ... worth its own issue+PR pair". This PR is that pair's renderer half; it does not touch the pool-eviction code reviewed there.
Bruno Bza (@BrunoBza)