From df36845839378b3c8bff0cffd69aac5aa4bb2e1d Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Thu, 26 Feb 2026 17:39:01 -0800 Subject: [PATCH] fix: remove cache eviction close that kills in-use httpx clients --- litellm/caching/llm_caching_handler.py | 19 ---------- .../caching/test_redis_connection_pool.py | 36 ------------------- 2 files changed, 55 deletions(-) diff --git a/litellm/caching/llm_caching_handler.py b/litellm/caching/llm_caching_handler.py index 5dc16a224c..16eb824f4c 100644 --- a/litellm/caching/llm_caching_handler.py +++ b/litellm/caching/llm_caching_handler.py @@ -8,25 +8,6 @@ from .in_memory_cache import InMemoryCache class LLMClientCache(InMemoryCache): - def _remove_key(self, key: str) -> None: - """Close async clients before evicting them to prevent connection pool leaks.""" - value = self.cache_dict.get(key) - super()._remove_key(key) - if value is not None: - close_fn = getattr(value, "aclose", None) or getattr( - value, "close", None - ) - if close_fn and asyncio.iscoroutinefunction(close_fn): - try: - asyncio.get_running_loop().create_task(close_fn()) - except RuntimeError: - pass - elif close_fn and callable(close_fn): - try: - close_fn() - except Exception: - pass - def update_cache_key_with_event_loop(self, key): """ Add the event loop to the cache key, to prevent event loop closed errors. diff --git a/tests/test_litellm/caching/test_redis_connection_pool.py b/tests/test_litellm/caching/test_redis_connection_pool.py index b8922846e8..f6e429ceff 100644 --- a/tests/test_litellm/caching/test_redis_connection_pool.py +++ b/tests/test_litellm/caching/test_redis_connection_pool.py @@ -4,14 +4,12 @@ Regression tests for Redis connection pool leak fixes (RC1-RC5). Tests are pure unit tests — no Redis server required. """ -import asyncio from unittest.mock import AsyncMock, MagicMock, patch import pytest import redis.asyncio as async_redis from litellm._redis import get_redis_async_client, get_redis_connection_pool -from litellm.caching.llm_caching_handler import LLMClientCache def test_url_config_uses_passed_pool(): @@ -131,37 +129,3 @@ async def test_disconnect_idempotent(): await cache.disconnect() # should not raise -@pytest.mark.asyncio -async def test_eviction_calls_aclose(): - """When an async client is evicted from LLMClientCache, its aclose() - should be scheduled via create_task.""" - cache = LLMClientCache(max_size_in_memory=2, default_ttl=600) - - client = AsyncMock() - client.aclose = AsyncMock() - - cache.set_cache(key="client-0", value=client) - cache.set_cache(key="filler", value="x") - # Third insert triggers eviction of client-0 - cache.set_cache(key="trigger", value="y") - - # Let the scheduled task run - await asyncio.sleep(0.05) - - assert client.aclose.await_count > 0 - - -@pytest.mark.asyncio -async def test_eviction_non_closeable_safe(): - """Evicting plain values (strings, dicts, ints) should not crash.""" - cache = LLMClientCache(max_size_in_memory=2, default_ttl=600) - - cache.set_cache(key="str-val", value="hello") - cache.set_cache(key="dict-val", value={"foo": "bar"}) - # This evicts "str-val" — should not raise - cache.set_cache(key="int-val", value=42) - - await asyncio.sleep(0.05) - - # If we got here without exception, the test passes - assert cache.get_cache(key="int-val") == 42