fix(redis): 按事件循环隔离 Redis 连接,防止子线程 asyncio.run 导致连接泄漏

RedisClientManager 新增 _redis_by_loop 字典,按 event loop id 维护独立连接,
避免 usage consumer 在 asyncio.to_thread + asyncio.run 场景下复用主循环连接。

同步重构 consumer_streams 的写库路径:_apply_record_event 统一走
record_usage_batch,_apply_streaming_event 改为 to_thread 执行同步 DB 操作,
移除冗余的 session 传递和手动 rollback 逻辑。
This commit is contained in:
fawney19
2026-03-18 17:56:48 +08:00
parent 696ec65175
commit 203cd5a9d5
4 changed files with 195 additions and 165 deletions

View File

@@ -1,3 +1,7 @@
import asyncio
import pytest
from src.clients import redis_client as redis_client_module
from src.clients.redis_client import RedisClientManager, RedisState
@@ -39,3 +43,49 @@ def test_reset_redis_circuit_breaker_returns_false_when_uninitialized() -> None:
finally:
redis_client_module._redis_manager = old_global
redis_client_module._usage_queue_redis_manager = old_usage
@pytest.mark.asyncio
async def test_get_redis_client_isolated_per_event_loop(monkeypatch: pytest.MonkeyPatch) -> None:
old_global = redis_client_module._redis_manager
try:
redis_client_module._redis_manager = RedisClientManager(client_name="global")
class DummyRedis:
def __init__(self, name: str) -> None:
self.name = name
self.closed = False
async def ping(self) -> bool:
return True
async def close(self) -> None:
self.closed = True
created_clients: list[DummyRedis] = []
async def fake_from_url(*args: object, **kwargs: object) -> DummyRedis:
client = DummyRedis(f"client-{len(created_clients)}")
created_clients.append(client)
return client
monkeypatch.setattr(redis_client_module.aioredis, "from_url", fake_from_url)
main_client = await redis_client_module.get_redis_client()
def _get_client_from_thread() -> object:
return asyncio.run(redis_client_module.get_redis_client())
thread_client = await asyncio.to_thread(_get_client_from_thread)
assert main_client is created_clients[0]
assert thread_client is created_clients[1]
assert thread_client is not main_client
assert redis_client_module.get_redis_client_sync() is main_client
await redis_client_module.close_redis_client()
assert created_clients[0].closed is True
assert created_clients[1].closed is True
finally:
redis_client_module._redis_manager = old_global