From 879ed17db1a7d23ad15572673b2eacf3404c2949 Mon Sep 17 00:00:00 2001 From: Tamil Adhavan S K <99407456+adhavan18@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:10:38 +0000 Subject: [PATCH 1/2] fix(caching): guard against None async_redis_conn_pool in RedisCache.disconnect Fixes #31206 When REDIS_CLUSTER_NODES is set, get_redis_connection_pool() returns None because the cluster mode path exits early. This causes RedisCache.__init__ to store self.async_redis_conn_pool = None. At shutdown, disconnect() then crashes with AttributeError: 'NoneType' has no attribute 'disconnect'. Add a None guard before calling .disconnect() on the connection pool. --- litellm/caching/redis_cache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/litellm/caching/redis_cache.py b/litellm/caching/redis_cache.py index dd1c152a4214..4263a41b47d4 100644 --- a/litellm/caching/redis_cache.py +++ b/litellm/caching/redis_cache.py @@ -1270,7 +1270,8 @@ def flushall(self): self.redis_client.flushall() async def disconnect(self): - await self.async_redis_conn_pool.disconnect(inuse_connections=True) + if self.async_redis_conn_pool is not None: + await self.async_redis_conn_pool.disconnect(inuse_connections=True) try: self.redis_client.close() except Exception as e: From fd0235295b870c21532b77ab7e05ed0a3ba8aae4 Mon Sep 17 00:00:00 2001 From: Tamil Adhavan S K <99407456+adhavan18@users.noreply.github.com> Date: Fri, 26 Jun 2026 04:06:21 +0000 Subject: [PATCH 2/2] test(caching): add regression tests for disconnect() when async_redis_conn_pool is None --- .../caching/test_redis_connection_pool.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_litellm/caching/test_redis_connection_pool.py b/tests/test_litellm/caching/test_redis_connection_pool.py index c824d3e7a0e0..bb288b1f4c9a 100644 --- a/tests/test_litellm/caching/test_redis_connection_pool.py +++ b/tests/test_litellm/caching/test_redis_connection_pool.py @@ -128,3 +128,29 @@ async def test_disconnect_idempotent(): await cache.disconnect() await cache.disconnect() # should not raise + + +@pytest.mark.asyncio +async def test_disconnect_when_async_pool_is_none(): + """Regression: disconnect() must not raise AttributeError when + async_redis_conn_pool is None (cluster-mode path sets it to None).""" + cache, mock_sync_client, _ = _make_redis_cache() + # Simulate cluster mode: connection pool is None after construction + cache.async_redis_conn_pool = None + + # Should complete without raising AttributeError + await cache.disconnect() + + # Sync client cleanup is still attempted + mock_sync_client.close.assert_called_once() + + +@pytest.mark.asyncio +async def test_disconnect_with_pool_calls_pool_disconnect(): + """Happy path: when async_redis_conn_pool is present, disconnect() + forwards the call with inuse_connections=True.""" + cache, mock_sync_client, mock_async_pool = _make_redis_cache() + await cache.disconnect() + + mock_async_pool.disconnect.assert_awaited_once_with(inuse_connections=True) + mock_sync_client.close.assert_called_once()