fix: close aiohttp sessions on proxy shutdown to prevent fd leaks

The proxy was not explicitly closing aiohttp.ClientSession instances
during shutdown, causing "Unclosed client session" warnings during GC
and potential fd leaks in long-running deployments.

Changes:
- Add close_litellm_async_clients() call to proxy shutdown path so the
  global base_llm_aiohttp_handler and cached async clients are cleaned
  up properly (not just via atexit).
- Add async_close() to presidio guardrail so its cached aiohttp sessions
  are closed during proxy shutdown.
- Iterate litellm.callbacks on shutdown and call async_close() on any
  callback that implements it, providing a general cleanup hook for
  guardrails and other callbacks that hold resources.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaeyeon Kim 2026-03-19 16:15:34 +09:00
parent d7c419bfee
commit 12e4ffa39c
2 changed files with 25 additions and 0 deletions

View file

@ -244,6 +244,10 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail):
await session.close()
self._loop_sessions.clear()
async def async_close(self) -> None:
"""Close resources held by this guardrail (called on proxy shutdown)."""
await self._close_http_session()
def __del__(self):
"""Cleanup: we try to close, but doing async cleanup in __del__ is risky."""
pass

View file

@ -963,6 +963,27 @@ async def proxy_startup_event(app: FastAPI): # noqa: PLR0915
except Exception as e:
verbose_proxy_logger.error(f"Error closing shared aiohttp session: {e}")
# Shutdown event - close cached async HTTP clients (base_llm_aiohttp_handler, etc.)
try:
from litellm.llms.custom_httpx.async_client_cleanup import (
close_litellm_async_clients,
)
await close_litellm_async_clients()
verbose_proxy_logger.info("Closed cached async HTTP clients")
except Exception as e:
verbose_proxy_logger.error(f"Error closing cached async HTTP clients: {e}")
# Shutdown event - close guardrail/callback resources (e.g. presidio aiohttp sessions)
for callback in litellm.callbacks:
if hasattr(callback, "async_close") and callable(callback.async_close):
try:
await callback.async_close()
except Exception as e:
verbose_proxy_logger.error(
f"Error closing callback {type(callback).__name__}: {e}"
)
# Shutdown event - stop RDS IAM token refresh background task
if (
prisma_client is not None