mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-07 16:21:23 +00:00
6caaced8eb
Root cause of the silent-crash-after-hours cluster (#4765/#2233/#4633): the global in-memory SESSIONS LRU evicted with a blind popitem(last=False), which could drop an actively streaming or not-yet-persisted session (data loss) and was capped only via an env var. On long-running installs the effective result was unbounded RAM growth until segfault. - Add _session_is_evictable(): a session is evictable ONLY when it is not streaming (no active_stream_id), has no in-flight turn (no pending_user_message / pending_started_at), and its full state is proven on disk (sidecar message_count >= in-memory count; metadata-only stubs and zero-message shells are trivially safe). - Add _evict_sessions_over_cap(): replaces all 8 blind popitem loops across models.py, routes.py, streaming.py. Walks the LRU oldest-first and removes only provably-safe entries; never acquires LOCK/stream locks itself (caller holds LOCK) so no lock-ordering deadlock. May briefly exceed the cap rather than ever evict an active/unsaved session. - Make the cap configurable via config.yaml webui.sessions_cache_max (get_sessions_cache_max()); precedence config.yaml -> HERMES_WEBUI_SESSIONS_MAX (legacy) -> DEFAULT_SESSIONS_CACHE_MAX=300. No new HERMES_* env var. Invalid or <1 values fall back so a typo can never disable the bound. - Evicted sessions lazily reload from their JSON sidecar via the existing get_session() accessor; no call sites changed. _index.json sidebar behavior unchanged (the sidebar reads the index, not SESSIONS). - Add tests/test_issue4765_sessions_lru_eviction.py (8 tests): eviction past cap, active/streaming never evicted, unsaved/stale-tail never evicted, lazy reload with identical content, and no-data-loss under heavy churn. - README: document the config.yaml key + safety semantics. Fixes #4765.