Skip to content

fix(tracker): bound per-session bookkeeping dicts with LRU eviction (#18)#32

Open
santosh2345 wants to merge 1 commit into
AltioraLabs:mainfrom
santosh2345:fix/unbounded-session-dicts
Open

fix(tracker): bound per-session bookkeeping dicts with LRU eviction (#18)#32
santosh2345 wants to merge 1 commit into
AltioraLabs:mainfrom
santosh2345:fix/unbounded-session-dicts

Conversation

@santosh2345

Copy link
Copy Markdown
Contributor

Closes #18.

_session_turn_counters, _session_turn_states, and _session_providers were plain dicts with no eviction policy. A server handling thousands of unique sessions grows them forever — a slow memory leak. _sweep_stale_locks() only cleaned _session_locks; the other three were never bounded.

Changes

  • Convert the three dicts to collections.OrderedDict.
  • Add config.max_tracked_sessions (default 10000; 0 disables eviction / restores unbounded behaviour).
  • Add _touch_session(), called right after the per-turn counter increment. It marks the session most-recently-used and evicts LRU sessions beyond the limit, dropping the evicted id from all three dicts and the _session_locks registry in lockstep. The counter dict is the LRU authority because it is written on every tracked turn.

Why touch on the counter increment

It's the single per-turn chokepoint every tracked call passes through (under the per-session lock), and its key set is a superset of the state/provider dicts — so LRU order there is authoritative and eviction stays consistent. Explicit end_session() cleanup still pops all three as before.

Tests

  • Eviction beyond the limit (all three dicts shrink in lockstep).
  • Recency refresh (re-touching a session protects it from eviction).
  • max_tracked_sessions=0 disables eviction.
  • Config default.

Verification

pytest tests/test_tracker_advanced.py tests/test_config.py tests/test_new_features.py tests/test_production_hardening.py → 118 passed. Full suite: 390 passed (2 pre-existing failures are unrelated missing optional deps — flask, litellm).

_session_turn_counters, _session_turn_states and _session_providers were
plain dicts with no eviction, so a server handling many unique sessions
leaked memory over long uptimes — _sweep_stale_locks() only cleaned
_session_locks. Closes AltioraLabs#18.

- Convert the three dicts to collections.OrderedDict.
- Add config.max_tracked_sessions (default 10000; 0 disables eviction).
- Add _touch_session(), called on each per-turn counter increment: it
  marks the session most-recently-used and evicts LRU sessions beyond the
  limit, dropping them from all three dicts and the lock registry in
  lockstep. The counter dict is the LRU authority since it is written
  every tracked turn.
- Tests: eviction beyond limit, recency refresh, and disable-when-zero,
  plus the config default.
@santosh2345
santosh2345 requested a review from abhay-2108 as a code owner July 16, 2026 06:44

@abhay-2108 abhay-2108 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work addressing the memory leak issue (#18). Using OrderedDict for LRU tracking and evicting state/locks in sync keeps the server footprint lightweight and safe.

One design detail to note:

Direct Tracking Path (track_async / track_sync): If a user calls track_async() or track_sync() directly (bypassing the @tracker.wrap decorator), the bookkeeping dicts _session_turn_states and _session_providers are still written to inside _track_background(), but _touch_session() is never triggered.
Suggestion: For complete safety under all usage patterns, we could also call _touch_session(session_id) inside _track_background() when these fields are written.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: _session_turn_counters and related dicts grow unbounded in long-running servers

2 participants