fix(#4808): widen /api/sessions cache TTL while streaming to stop per-poll rebuilds

The frontend polls /api/sessions every 5s while a turn streams
(static/sessions.js _streamingPollMs=5000), but the cache TTL was 2.5s
(_SESSIONS_CACHE_TTL_SECONDS) — so every streaming poll landed in a fresh window
and forced a full all_sessions() rebuild on the hot path under the global store
LOCK, pinning CPU to 100% and starving token rendering on large stores
(recurrence of #4672). The #4680 freeze-marker only de-dupes rebuilds WITHIN one
TTL window; with polls spaced wider than the TTL it never helped.

Fix: while _session_list_cache_streaming_freeze_marker() indicates an active
stream, use _SESSIONS_CACHE_STREAMING_TTL_SECONDS=10.0 (> the 5s poll) so the
sidebar holds steady across streaming polls. Live runtime state (active stream,
sort order, pending flags) is overlaid on every response regardless of cache, and
structural/settings changes still evict immediately via the source stamp — only a
streaming session's own persisted title/message_count can lag up to 10s, which the
#4672 design already deems acceptable. Idle path byte-for-byte unchanged.

Diagnosis credit: investigation reproduced 6 streaming polls -> 6 rebuilds before,
1 after. Co-authored-by: batkuip <reporter>
This commit is contained in:
nesquena-hermes committed 2026-06-24 08:06:45 +00:00
1 parent 27b6cd436e
commit 8eed95a25e
2 files changed
+88 -1

No files matched your search

+16 -1
View File
@@ -1572,6 +1572,16 @@ def _clear_live_models_cache() -> None:
# ── /api/sessions response cache (hot-sidebar response) ──────────────────────
_SESSIONS_CACHE_TTL_SECONDS = 2.5
# #4808: while a turn is actively streaming the frontend polls /api/sessions on a
# fixed cadence (static/sessions.js `_streamingPollMs` = 5000ms). With the idle TTL
# of 2.5s, every streaming poll lands in a fresh window and forces a full
# all_sessions() rebuild on the hot path under the global store LOCK — pinning CPU
# and starving token rendering on large stores (recurrence of #4672). Hold the
# sidebar cache steady for longer than one poll interval while streaming; live
# runtime state (active stream, sort order, pending flags) is overlaid on every
# response regardless of cache (_session_list_cache_overlay_runtime_rows), and
# structural/settings changes still evict immediately via the source stamp.
_SESSIONS_CACHE_STREAMING_TTL_SECONDS = 10.0
_SESSIONS_CACHE_MAX_ENTRIES = 64
_SESSIONS_CACHE_WAIT_SECONDS = 0.25
_SESSIONS_CACHE_STALE_WAIT_SECONDS = 0.10
@@ -1630,7 +1640,12 @@ def _session_list_cache_get(
if stamp != current_stamp:
_SESSIONS_CACHE.pop(key, None)
return None, False
fresh = (now - ts) < _SESSIONS_CACHE_TTL_SECONDS
# #4808: widen the freshness window while a turn is streaming so the fixed
# 5s streaming poll cadence doesn't force a full rebuild on every poll.
ttl = _SESSIONS_CACHE_TTL_SECONDS
if _session_list_cache_streaming_freeze_marker() is not None:
ttl = _SESSIONS_CACHE_STREAMING_TTL_SECONDS
fresh = (now - ts) < ttl
if fresh:
_SESSIONS_CACHE.move_to_end(key)
return copy.deepcopy(payload), True