Files
PolyGateway/src/polygateway/backends/redis_cache.py
T
iomgaa e69ca4c82c fix: make every client close what it built and nothing else
A client used to close whatever transport, recorder or cache it happened
to hold, injected or not, so the first client to shut down killed the
backend its siblings were still using. That is why the explicit-sharing
path the architecture prescribes was unusable in practice and downstream
projects fell back to one private instance per client. The mirror image
of the same gap: the redis clients the factories build for the limiter
and the breaker were never closed at all, because nobody kept a
reference to them once they were handed to the retry middleware.

Ownership is now stated once, the way RedisLimiter already stated it:
whoever builds a resource closes it, injected ones are left alone. The
constructor is the full-injection path, so it owns nothing by default
and only the factories mark what they built. RedisCache gains the same
rule for its own client, and the three copies of the "probe for aclose,
fall back to close" dance collapse into a single helper so the next
correction cannot land in only one of them.
2026-08-24 08:34:06 -04:00

54 lines
1.9 KiB
Python

"""Redis 缓存后端: 笨 KV(CacheBackend 契约);redis 是 optional extra。
降级不在这里做——本类忠实上抛异常,静默降级是 CacheMW 的职责(算法一份)。
"""
from __future__ import annotations
from typing import Any
try:
import redis.asyncio as aioredis
except ImportError as _exc: # pragma: no cover - 依赖缺失路径
aioredis = None
_IMPORT_ERROR = _exc
else:
_IMPORT_ERROR = None
class RedisCache:
"""基于 redis.asyncio 的 KV;value 为 JSON 字符串,TTL 由调用方传入。"""
def __init__(self, client: Any) -> None:
if aioredis is None:
raise ImportError(
"Redis 缓存后端需要 redis 包: pip install 'polygateway[redis]'"
) from _IMPORT_ERROR
self._client = client
# 注入的客户端归注入方管理: 关掉它会弄死共享同一连接的其他组件
# (与 RedisLimiter/RedisGate 同一纪律)
self._owns_client = False
@classmethod
def from_url(cls, url: str) -> RedisCache:
"""自建并持有 Redis 客户端(aclose 时代关);共享后端请直接注入 client。"""
if aioredis is None:
raise ImportError(
"Redis 缓存后端需要 redis 包: pip install 'polygateway[redis]'"
) from _IMPORT_ERROR
cache = cls(aioredis.from_url(url, decode_responses=True))
cache._owns_client = True
return cache
async def get(self, key: str) -> str | None:
return await self._client.get(key)
async def set(self, key: str, value: str, ttl_s: int) -> None:
await self._client.set(key, value, ex=ttl_s)
async def aclose(self) -> None:
"""幂等释放自建客户端;注入的客户端归注入方管理。"""
if self._owns_client:
self._owns_client = False
await self._client.aclose()