mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-24 04:14:55 +00:00
Move GET /api/events, the remote-device command stream and artifact downloads from Flask to Starlette routes mounted ahead of the Flask catch-all. On Flask each held an a2wsgi threadpool slot for as long as its response stayed open, and because uvicorn drops writes after a client disconnects, a closed tab never released it. - asgi_auth: one JWT/OIDC gate for Starlette routes; the chat reconnect reader uses it too - ClosingStreamingResponse closes the body iterator and releases the SSE slot or file handle even when the client leaves before the first frame - AsyncTopic liveness probe replaces the sync client's socket_timeout guard against half-open pub/sub sockets - ASYNC_REDIS_MAX_CONNECTIONS sizes the async Redis pool; every open stream holds a connection and redis-py defaults to 100
51 lines
1.9 KiB
Python
51 lines
1.9 KiB
Python
"""Lazy async Redis client for the native-async (event-loop) routes.
|
|
|
|
Async twin of :func:`docsgpt.cache.get_redis_instance`. The Starlette-mounted
|
|
routes — the chat reconnect reader, ``/api/events`` and the device command
|
|
stream — wait on Redis from the event loop, so they need a ``redis.asyncio``
|
|
client rather than the sync one used by the producer side. The app runs a
|
|
single ASGI worker / event loop, so a module-level singleton is sufficient
|
|
and avoids reconnecting per request.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Optional
|
|
|
|
import redis.asyncio as aioredis
|
|
|
|
from docsgpt.core.settings import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_async_redis: Optional[aioredis.Redis] = None
|
|
_creation_failed = False
|
|
|
|
|
|
async def get_async_redis_instance() -> Optional[aioredis.Redis]:
|
|
"""Return a process-wide async Redis client, or ``None`` if unavailable.
|
|
|
|
``from_url`` builds the client without opening a socket (connection is
|
|
lazy), so a transient broker outage surfaces later on the first command
|
|
rather than here. Mirrors the sync client's ``socket_connect_timeout``
|
|
and ``health_check_interval`` so a half-open TCP can't wedge the tail
|
|
loop past its keepalive cadence. Every open stream holds a pooled
|
|
connection, so the pool is sized by ``ASYNC_REDIS_MAX_CONNECTIONS``
|
|
rather than redis-py's default of 100.
|
|
"""
|
|
global _async_redis, _creation_failed
|
|
if _async_redis is None and not _creation_failed:
|
|
try:
|
|
_async_redis = aioredis.Redis.from_url(
|
|
settings.CACHE_REDIS_URL,
|
|
socket_connect_timeout=2,
|
|
health_check_interval=10,
|
|
max_connections=int(settings.ASYNC_REDIS_MAX_CONNECTIONS),
|
|
)
|
|
except ValueError as e:
|
|
logger.error("Invalid Redis URL for async client: %s", e)
|
|
_creation_failed = True
|
|
_async_redis = None
|
|
return _async_redis
|