Skip to content

improved redis singleton #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 47 additions & 21 deletions src/backend/cache/redis_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import asyncio
from redis import asyncio as aioredis
from dotenv import load_dotenv

Expand All @@ -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.")
"""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
4 changes: 1 addition & 3 deletions src/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down