Expand description
LRU-bounded cache of per-palace ChatSessionStore handles (issue #4639).
Why: AppState::session_stores was a plain DashMap<String, Arc<ChatSessionStore>> with no remove, no TTL, and no cap — every palace
the daemon ever touched leaked one chat_sessions.redb file descriptor for
the process lifetime. A live daemon was measured holding 844 such handles
(all 844 pointing at files already unlinked from disk) against an 8 192 fd
ceiling, growing ~250-300/day. PalaceRegistry already solved exactly this
failure class for kg/usearch/recall via an LRU (issue #463); this module
applies the same shape to the one file type that registry never tracked.
What: SessionStoreCache — a parking_lot::Mutex<LruCache<..>> (opened
unbounded, trimmed manually to SessionStoreCache::capacity) that opens a
store on miss, promotes on hit, and evicts from the cold end once resident
entries exceed the cap. Dropping the last Arc closes the redb Database
and releases its fd; the next request reopens from disk transparently.
Two invariants make eviction safe, both enforced under the single cache mutex:
- Never evict a store a caller still holds. redb takes an exclusive
flock, andChatSessionStore::openhas no snapshot fallback, so a second open of a file that is still open in this same process fails hard withDatabase already open. Cannot acquire lock.(reproduced directly while diagnosing #4639). The chat streaming handler holds anArcacross a whole SSE response (chat::handler), so unconditional LRU eviction would turn a leak into an outage. Eviction therefore skips any entry whoseArc::strong_count() > 1and overshoots the cap rather than closing a store out from under its user. - Exactly one store per palace. The open runs while the cache mutex
is held, so two concurrent callers for the same id cannot both open the
file (the
DatabaseAlreadyOpenpathPalaceRegistryguards with per-id mutexes). The critical section is pure blocking I/O with no.await, soparking_lot::Mutexis safe here.
Test: tests::open_handles_are_bounded_by_cap,
session_store_fd_count_is_bounded_by_cap (in
tests/session_store_fd_bound.rs — needs its own process to measure real
fds; see that file’s header),
tests::evicted_store_reopens_with_data_intact,
tests::in_use_store_is_never_evicted,
tests::concurrent_callers_share_one_store,
tests::remove_drops_cached_handle.
Structs§
- Session
Store Cache - LRU-bounded, thread-safe cache of open per-palace chat-session stores.
Constants§
- DEFAULT_
MAX_ OPEN_ SESSION_ STORES - Default maximum number of
chat_sessions.redbhandles held open at once. - MAX_
OPEN_ SESSION_ STORES_ ENV - Environment variable overriding the resident chat-session-store cap.
Functions§
- max_
open_ session_ stores_ from_ env - Resolve the effective cap from the environment.