From ad78a8f8d0a4e6200a98d5ef0ddae252b1e5c154 Mon Sep 17 00:00:00 2001 From: mateo Date: Thu, 10 Sep 2026 21:35:49 +0000 Subject: [PATCH] refactor(caching): walk exception causes iteratively for the breaker timeout check Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/caching/redis_cache.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/litellm/caching/redis_cache.py b/litellm/caching/redis_cache.py index de4dcb5aa27..742c7144784 100644 --- a/litellm/caching/redis_cache.py +++ b/litellm/caching/redis_cache.py @@ -15,7 +15,7 @@ import hashlib import inspect import json import time -from collections.abc import Awaitable, Callable, Sequence +from collections.abc import Awaitable, Callable, Iterator, Sequence from contextvars import ContextVar from dataclasses import dataclass from datetime import timedelta @@ -334,11 +334,22 @@ def _redis_timeout_error_types() -> tuple[type, ...]: return (RedisTimeoutError, TimeoutError) +_MAX_EXCEPTION_CAUSE_DEPTH: Final = 20 + + +def _exception_cause_chain(exc: BaseException) -> Iterator[BaseException]: + current = exc # rebind-ok: advances one link per iteration of the bounded walk + for _ in range(_MAX_EXCEPTION_CAUSE_DEPTH): + yield current + if current.__cause__ is None: + return + current = current.__cause__ + + def _is_redis_timeout_failure(exc: BaseException) -> bool: - """Follows __cause__: a blocking pool wait raises ConnectionError from asyncio.TimeoutError.""" - if isinstance(exc, _redis_timeout_error_types()): - return True - return exc.__cause__ is not None and _is_redis_timeout_failure(exc.__cause__) + """Walks __cause__: a blocking pool wait raises ConnectionError from asyncio.TimeoutError.""" + timeout_types: Final = _redis_timeout_error_types() + return any(isinstance(cause, timeout_types) for cause in _exception_cause_chain(exc)) class _BreakerMetrics: