From 44f0594b032470f859fcb87a28aed8b660beab7a Mon Sep 17 00:00:00 2001 From: cyclotruc Date: Wed, 4 Jun 2025 04:43:04 +0000 Subject: [PATCH] improved redis singleton --- src/backend/cache/redis_client.py | 68 +++++++++++++++++++++---------- src/backend/main.py | 4 +- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/backend/cache/redis_client.py b/src/backend/cache/redis_client.py index 3dbce07..01eb25e 100644 --- a/src/backend/cache/redis_client.py +++ b/src/backend/cache/redis_client.py @@ -1,4 +1,5 @@ import os +import asyncio from redis import asyncio as aioredis from dotenv import load_dotenv @@ -15,30 +16,55 @@ class RedisClient: """Service for managing Redis connections with proper lifecycle management.""" _instance = None + _client = None + _lock = asyncio.Lock() @classmethod async def get_instance(cls) -> aioredis.Redis: - """Get or create a Redis client instance.""" - if cls._instance is None: - cls._instance = cls() - await cls._instance.initialize() - return cls._instance.client + """Get or create a Redis client instance with proper singleton behavior.""" + if cls._client is None: + async with cls._lock: + # Double-check pattern to prevent race conditions + if cls._client is None: + if cls._instance is None: + cls._instance = cls() + await cls._instance.initialize() + return cls._client - def __init__(self): - self.client = None async def initialize(self) -> None: - """Initialize the Redis client.""" - self.client = aioredis.from_url( - REDIS_URL, - password=REDIS_PASSWORD, - decode_responses=True, - health_check_interval=30 - ) - - async def close(self) -> None: - """Close the Redis client connection.""" - if self.client: - await self.client.close() - self.client = None - print("Redis client closed.") \ No newline at end of file + """Initialize the Redis client with connection pool limits.""" + if RedisClient._client is None: + try: + RedisClient._client = aioredis.from_url( + REDIS_URL, + password=REDIS_PASSWORD, + decode_responses=True, + health_check_interval=30, + max_connections=20, # Limit connection pool size + retry_on_timeout=True, + socket_keepalive=True, + socket_keepalive_options={} + ) + print(f"Redis client initialized with connection pool (max 20 connections)") + + # Test the connection + await RedisClient._client.ping() + + except Exception as e: + print(f"Failed to initialize Redis client: {e}") + RedisClient._client = None + raise + + @classmethod + async def close(cls) -> None: + """Close the Redis client connection and reset singleton state.""" + if cls._client: + try: + await cls._client.close() + print("Redis client closed.") + except Exception as e: + print(f"Error closing Redis client: {e}") + finally: + cls._client = None + cls._instance = None \ No newline at end of file diff --git a/src/backend/main.py b/src/backend/main.py index 9c4fabe..6354ecc 100644 --- a/src/backend/main.py +++ b/src/backend/main.py @@ -49,7 +49,6 @@ async def lifespan(app: FastAPI): # Initialize Redis client and verify connection redis = await RedisClient.get_instance() - await redis.ping() print("Redis connection established successfully") # Initialize the canvas worker @@ -58,9 +57,8 @@ async def lifespan(app: FastAPI): yield - # Shutdown await CanvasWorker.shutdown_instance() - await redis.close() + await RedisClient.close() await engine.dispose() app = FastAPI(lifespan=lifespan)