""" GovBridge India โ€” Memory Guard Middleware (Sprint 18) Prevents progressive RAM bloat by: 1. Running gc.collect() after every request 2. Logging RSS memory usage for monitoring 3. Providing a /debug/memory endpoint for live inspection Target: Hugging Face Spaces (CPU Basic: 2 vCPU, 16GB RAM) """ import gc import os import psutil from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response, JSONResponse def get_memory_mb() -> float: """Get current process RSS in MB.""" process = psutil.Process(os.getpid()) return process.memory_info().rss / (1024 * 1024) class MemoryGuardMiddleware(BaseHTTPMiddleware): """ Post-request garbage collection middleware. CRITICAL DESIGN: Python's default GC thresholds (700, 10, 10) are tuned for short-lived scripts, not long-running ML servers. PyTorch tensors create circular references that the generational GC doesn't collect promptly. Forcing gc.collect() after every request ensures translation tensors are freed immediately. Performance impact: gc.collect() takes 1-5ms on this workload. Negligible vs. the 2-4s translation + inference latency. """ # Threshold in MB. If exceeded, log a warning. MEMORY_WARNING_THRESHOLD_MB = 12_000 # 12GB of 16GB async def dispatch(self, request: Request, call_next): response = await call_next(request) # Force garbage collection after every request collected = gc.collect() # Log memory state for monitoring (only on heavy endpoints) if request.url.path in ("/api/rag/query", "/webhook/whatsapp"): mem_mb = get_memory_mb() if mem_mb > self.MEMORY_WARNING_THRESHOLD_MB: print(f"๐Ÿšจ MEMORY WARNING: {mem_mb:.0f}MB RSS (threshold: {self.MEMORY_WARNING_THRESHOLD_MB}MB)") elif collected > 0: print(f"๐Ÿงน GC freed {collected} objects | RSS: {mem_mb:.0f}MB") return response