redis_lifespan_fixed.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. """Async lifespan helper to initialize both an asyncio Redis client and a synchronous Redis client
  2. and attach them to app.state.redis (async) and app.state.redis_sync (sync).
  3. This lets synchronous request handlers use the sync client while async handlers use the asyncio client.
  4. """
  5. import os
  6. from contextlib import asynccontextmanager
  7. import redis.asyncio as aioredis
  8. import redis as redis_sync
  9. from fastapi import FastAPI
  10. import logging
  11. logger = logging.getLogger("app.redis")
  12. @asynccontextmanager
  13. async def lifespan(app: FastAPI):
  14. redis_url = os.getenv("REDIS_URL")
  15. redis_host = os.getenv("REDIS_HOST", "127.0.0.1")
  16. redis_port = int(os.getenv("REDIS_PORT", "6379"))
  17. redis_db = int(os.getenv("REDIS_DB", "0"))
  18. redis_password = os.getenv("REDIS_PASSWORD", None)
  19. # Create both async and sync clients
  20. if redis_url:
  21. async_client = aioredis.from_url(redis_url, decode_responses=True)
  22. sync_client = redis_sync.from_url(redis_url, decode_responses=True)
  23. else:
  24. async_client = aioredis.Redis(host=redis_host, port=redis_port, db=redis_db, password=redis_password, decode_responses=True)
  25. sync_client = redis_sync.Redis(host=redis_host, port=redis_port, db=redis_db, password=redis_password, decode_responses=True)
  26. try:
  27. # Verify connectivity for both clients
  28. await async_client.ping()
  29. try:
  30. sync_client.ping()
  31. except Exception:
  32. # If sync ping fails, log but still keep async client if it's ok
  33. logger.warning("Sync Redis ping failed during startup", exc_info=True)
  34. app.state.redis = async_client
  35. app.state.redis_sync = sync_client
  36. logger.info("Redis clients initialized and attached to app.state.redis (async) and app.state.redis_sync (sync) (host=%s port=%s db=%s)", redis_host, redis_port, redis_db)
  37. except Exception as e:
  38. logger.error("Failed to initialize Redis clients: %s", e, exc_info=True)
  39. app.state.redis = None
  40. app.state.redis_sync = None
  41. try:
  42. yield
  43. finally:
  44. # Close async client
  45. try:
  46. if getattr(app.state, "redis", None) is not None:
  47. await app.state.redis.close()
  48. logger.info("Async Redis client closed on shutdown")
  49. except Exception:
  50. logger.error("Error closing async Redis client", exc_info=True)
  51. # Close sync client
  52. try:
  53. if getattr(app.state, "redis_sync", None) is not None:
  54. try:
  55. app.state.redis_sync.close()
  56. except Exception:
  57. # Older redis-py may not have close(); disconnect pool as fallback
  58. try:
  59. app.state.redis_sync.connection_pool.disconnect()
  60. except Exception:
  61. pass
  62. logger.info("Sync Redis client closed on shutdown")
  63. except Exception:
  64. logger.error("Error closing sync Redis client", exc_info=True)