Skip to content

Commit 333598c

Browse files
committed
fix(gateway): cap cached session sources with LRU eviction
Follow-up on top of Zyproth's session-source cache: swap the unbounded dict for an OrderedDict with a 512-entry LRU cap so long-running gateways can't accumulate stale entries for dead sessions forever. - self._session_sources is now an OrderedDict - _cache_session_source() move_to_end + popitem(last=False) above cap - _get_cached_session_source() move_to_end on hit (LRU read bump) - restart_test_helpers.py wires OrderedDict + _session_sources_max
1 parent 176b935 commit 333598c

2 files changed

Lines changed: 27 additions & 4 deletions

File tree

gateway/run.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,7 +1086,13 @@ def __init__(self, config: Optional[GatewayConfig] = None):
10861086
self._pending_native_image_paths_by_session: Dict[str, List[str]] = {}
10871087
self._busy_ack_ts: Dict[str, float] = {} # last busy-ack timestamp per session (debounce)
10881088
self._session_run_generation: Dict[str, int] = {}
1089-
self._session_sources: Dict[str, "SessionSource"] = {}
1089+
# LRU cache of live SessionSources keyed by session_key. Used by
1090+
# fallback routing paths (shutdown notifications, synthetic
1091+
# background-process events) when the persisted origin is missing
1092+
# and _parse_session_key can't recover thread_id. Capped so it
1093+
# cannot grow unbounded over a long-running gateway lifetime.
1094+
self._session_sources: "OrderedDict[str, SessionSource]" = OrderedDict()
1095+
self._session_sources_max = 512
10901096

10911097
# Cache AIAgent instances per session to preserve prompt caching.
10921098
# Without this, a new AIAgent is created per message, rebuilding the
@@ -6015,20 +6021,35 @@ def _cache_session_source(self, session_key: str, source) -> None:
60156021
return
60166022
cached_sources = getattr(self, "_session_sources", None)
60176023
if cached_sources is None:
6018-
cached_sources = {}
6024+
cached_sources = OrderedDict()
60196025
self._session_sources = cached_sources
60206026
try:
60216027
cached_sources[session_key] = dataclasses.replace(source)
60226028
except Exception:
60236029
logger.debug("Failed to cache live session source for %s", session_key, exc_info=True)
6030+
return
6031+
# LRU: mark as most-recently-used and trim to max size.
6032+
try:
6033+
cached_sources.move_to_end(session_key)
6034+
max_size = getattr(self, "_session_sources_max", 512)
6035+
while len(cached_sources) > max_size:
6036+
cached_sources.popitem(last=False)
6037+
except Exception:
6038+
pass
60246039

60256040
def _get_cached_session_source(self, session_key: str):
60266041
if not session_key:
60276042
return None
60286043
cached_sources = getattr(self, "_session_sources", None)
60296044
if not cached_sources:
60306045
return None
6031-
return cached_sources.get(session_key)
6046+
source = cached_sources.get(session_key)
6047+
if source is not None:
6048+
try:
6049+
cached_sources.move_to_end(session_key)
6050+
except Exception:
6051+
pass
6052+
return source
60326053

60336054
async def _handle_message_with_agent(self, event, source, _quick_key: str, run_generation: int):
60346055
"""Inner handler that runs under the _running_agents sentinel guard."""

tests/gateway/restart_test_helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
from collections import OrderedDict
23
from unittest.mock import AsyncMock, MagicMock
34

45
from gateway.config import GatewayConfig, Platform, PlatformConfig
@@ -74,7 +75,8 @@ def make_restart_runner(
7475
runner._update_prompt_pending = {}
7576
runner._voice_mode = {}
7677
runner._session_model_overrides = {}
77-
runner._session_sources = {}
78+
runner._session_sources = OrderedDict()
79+
runner._session_sources_max = 512
7880
runner._shutdown_all_gateway_honcho = lambda: None
7981
runner._update_runtime_status = MagicMock()
8082
runner._queue_or_replace_pending_event = GatewayRunner._queue_or_replace_pending_event.__get__(

0 commit comments

Comments
 (0)