mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix: classify asyncio timeouts on Python 3.10
This commit is contained in:
parent
6675e1fd9c
commit
d71737e5a1
2 changed files with 24 additions and 21 deletions
|
|
@ -288,15 +288,14 @@ def _is_redis_health_failure(exc: BaseException) -> bool:
|
|||
def _redis_timeout_error_types() -> tuple[type, ...]:
|
||||
"""Health failures that are timeouts rather than unambiguous connectivity errors.
|
||||
|
||||
``builtins.TimeoutError`` covers ``asyncio.TimeoutError`` and ``socket.timeout``
|
||||
(aliases since py3.11 / py3.10). ``redis.exceptions.TimeoutError`` does not subclass
|
||||
either, so it is listed explicitly.
|
||||
``asyncio.TimeoutError`` is separate from ``builtins.TimeoutError`` on Python 3.10.
|
||||
``redis.exceptions.TimeoutError`` does not subclass either, so all three are listed.
|
||||
"""
|
||||
try:
|
||||
from redis.exceptions import TimeoutError as RedisTimeoutError
|
||||
except ImportError:
|
||||
return (TimeoutError,)
|
||||
return (RedisTimeoutError, TimeoutError)
|
||||
return (asyncio.TimeoutError, TimeoutError)
|
||||
return (RedisTimeoutError, asyncio.TimeoutError, TimeoutError)
|
||||
|
||||
|
||||
def _is_redis_timeout_failure(exc: BaseException) -> bool:
|
||||
|
|
|
|||
|
|
@ -817,36 +817,40 @@ async def test_only_connectivity_failures_open_the_breaker(error, opens_breaker)
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_loop_stall_timeout_burst_keeps_breaker_closed():
|
||||
"""One blocking stall of the worker event loop must not trip the breaker.
|
||||
async def test_concurrent_timeout_burst_keeps_breaker_closed():
|
||||
"""One concurrent timeout burst must not trip the breaker.
|
||||
|
||||
Every operation already waiting on the loop times out together when the loop resumes,
|
||||
so a purely consecutive threshold is satisfied instantly even though the Redis on the
|
||||
other end (here an in-process fake that answers immediately) is healthy.
|
||||
Operations already waiting on a stalled loop can time out together when it resumes,
|
||||
so a purely consecutive threshold can be satisfied instantly without establishing a
|
||||
persistent Redis outage.
|
||||
"""
|
||||
import time as time_mod
|
||||
|
||||
from litellm.caching.redis_cache import RedisCircuitBreaker, _run_under_circuit_breaker
|
||||
|
||||
breaker = RedisCircuitBreaker(failure_threshold=3, recovery_timeout=60, timeout_min_duration=5.0)
|
||||
release = asyncio.Event()
|
||||
|
||||
async def healthy_redis_call_with_client_timeout():
|
||||
return await asyncio.wait_for(asyncio.sleep(0.001, result="ok"), timeout=0.05)
|
||||
async def timing_out_call():
|
||||
await release.wait()
|
||||
raise asyncio.TimeoutError
|
||||
|
||||
async def stall_the_loop():
|
||||
async def release_burst():
|
||||
await asyncio.sleep(0)
|
||||
time_mod.sleep(0.2)
|
||||
release.set()
|
||||
|
||||
results = await asyncio.gather(
|
||||
*(_run_under_circuit_breaker(breaker, "op", healthy_redis_call_with_client_timeout) for _ in range(8)),
|
||||
stall_the_loop(),
|
||||
*(_run_under_circuit_breaker(breaker, "op", timing_out_call) for _ in range(8)),
|
||||
release_burst(),
|
||||
return_exceptions=True,
|
||||
)
|
||||
timeouts = [r for r in results if isinstance(r, asyncio.TimeoutError)]
|
||||
assert len(timeouts) >= breaker.failure_threshold, "the stall must time out a full burst"
|
||||
assert len(timeouts) >= breaker.failure_threshold, "the concurrent calls must time out as one burst"
|
||||
|
||||
assert breaker.is_open() is False, "a healthy Redis behind one loop stall must stay in the pool"
|
||||
assert await _run_under_circuit_breaker(breaker, "op", healthy_redis_call_with_client_timeout) == "ok"
|
||||
assert breaker.is_open() is False, "one timeout burst must not remove Redis from the pool"
|
||||
|
||||
async def healthy_call():
|
||||
return "ok"
|
||||
|
||||
assert await _run_under_circuit_breaker(breaker, "op", healthy_call) == "ok"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue