Skip to content

Commit f5df871

Browse files
author
User
committed
feat(high): Fix memory leak in session management with SessionManager
Implemented comprehensive session lifecycle management: - SessionManager class with LRU eviction policy - Automatic cleanup of expired sessions - Connection pooling and reuse - Memory leak prevention Features: - LRU cache with configurable max_sessions limit - Automatic eviction of least recently used sessions - TTL-based session expiration (default: 1 hour) - Background cleanup task (default: 5 minutes interval) - Thread-safe session access with asyncio.Lock - Statistics tracking (cache hits, misses, evictions) Session Lifecycle: 1. get_session() - Get or create session (lazy initialization) 2. Session pooling - Reuse existing sessions 3. LRU eviction - Remove oldest when at capacity 4. TTL expiration - Cleanup inactive sessions 5. Graceful shutdown - Close all sessions on shutdown Memory Leak Prevention: - Proper connection cleanup via clear_session() - Automatic eviction prevents unbounded growth - Background task removes expired sessions - Shutdown closes all active sessions Integration: - Added to DIContainer with lazy initialization - Integrated into initialize() and shutdown() lifecycle - Configurable via ErniConfig.data_dir Configuration: - max_sessions: 100 (prevents memory exhaustion) - session_ttl: 3600 seconds (1 hour) - cleanup_interval: 300 seconds (5 minutes) - db_path: data/sessions.db (persistent storage) Benefits: - Prevents memory leaks from unclosed connections - Reduces database connection overhead - Improves performance with session reuse - Automatic cleanup of stale sessions - Thread-safe concurrent access Tests: - 13 comprehensive unit tests - Test LRU eviction, TTL expiration, cleanup - Test concurrent access, statistics - All tests passing (13/13) Before: - Sessions created but never cleaned up - Database connections left open - Memory usage grows unbounded - No session reuse After: - Automatic session lifecycle management - Proper connection cleanup - Bounded memory usage (max 100 sessions) - Session pooling and reuse - Background cleanup task Resolves: openai#11 (HIGH priority) Impact: Eliminates memory leaks and improves stability
1 parent 59e069e commit f5df871

File tree

4 files changed

+643
-0
lines changed

4 files changed

+643
-0
lines changed

examples/erni-foto-agency/erni_foto_agency/di_container.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from .performance.circuit_breaker import CircuitBreaker
2828
from .performance.cost_optimizer import CostBudget, CostOptimizer
2929
from .performance.rate_limiter import RateLimiter, get_rate_limiter
30+
from .session.session_manager import SessionManager
3031
from .utils.image_processor import ImageProcessor
3132

3233
logger = structlog.get_logger(__name__)
@@ -123,6 +124,7 @@ def __init__(self, config: ErniConfig | None = None):
123124
self._cost_optimizer: CostOptimizer | None = None
124125
self._rate_limiter_gpt4o: RateLimiter | None = None
125126
self._rate_limiter_gpt4o_mini: RateLimiter | None = None
127+
self._session_manager: SessionManager | None = None
126128

127129
# Agents (lazy-initialized)
128130
self._schema_extractor: SharePointSchemaExtractorAgent | None = None
@@ -274,6 +276,24 @@ def rate_limiter_gpt4o_mini(self, value: RateLimiter) -> None:
274276
"""Set rate limiter for GPT-4o-mini (for testing)"""
275277
self._rate_limiter_gpt4o_mini = value
276278

279+
@property
280+
def session_manager(self) -> SessionManager:
281+
"""Get session manager"""
282+
if self._session_manager is None:
283+
self._session_manager = SessionManager(
284+
db_path=self._config.data_dir / "sessions.db",
285+
max_sessions=100,
286+
session_ttl=3600, # 1 hour
287+
cleanup_interval=300 # 5 minutes
288+
)
289+
logger.debug("Session manager created")
290+
return self._session_manager
291+
292+
@session_manager.setter
293+
def session_manager(self, value: SessionManager) -> None:
294+
"""Set session manager (for testing)"""
295+
self._session_manager = value
296+
277297
# ========================================================================
278298
# AI Agents
279299
# ========================================================================
@@ -371,6 +391,9 @@ async def initialize(self) -> None:
371391
# Initialize metrics collector
372392
await self.metrics_collector.start_background_tasks()
373393

394+
# Start session manager
395+
await self.session_manager.start()
396+
374397
self._initialized = True
375398
logger.info("DI Container initialized successfully")
376399

@@ -387,6 +410,10 @@ async def shutdown(self) -> None:
387410

388411
logger.info("Shutting down DI Container components...")
389412

413+
# Stop session manager
414+
if self._session_manager:
415+
await self._session_manager.shutdown()
416+
390417
# Stop metrics collector (if it has stop method)
391418
if self._metrics_collector:
392419
if hasattr(self._metrics_collector, 'stop_background_tasks'):
@@ -419,6 +446,7 @@ def reset(self) -> None:
419446
self._cost_optimizer = None
420447
self._rate_limiter_gpt4o = None
421448
self._rate_limiter_gpt4o_mini = None
449+
self._session_manager = None
422450
self._schema_extractor = None
423451
self._vision_analyzer = None
424452
self._sharepoint_uploader = None
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Session management module"""
2+
3+
from .session_manager import SessionManager, SessionMetadata
4+
5+
__all__ = ["SessionManager", "SessionMetadata"]
6+

0 commit comments

Comments
 (0)