fix(custom_httpx): resolve aiohttp transport type without importing in finalizer paths

The function-level import of LiteLLMAiohttpTransport in
_dispose_wrapped_aiohttp_session() and _aiohttp_session_bound_elsewhere()
always raises ImportError when AsyncHTTPHandler.__del__ runs during
interpreter shutdown (sys.meta_path is already None). The bare
except Exception: pass in __del__ swallows it, so the disposal silently
never happens and the wrapped aiohttp ClientSession/TCPConnector fire
"Unclosed client session"/"Unclosed connector" warnings at GC - exactly
what the disposal was written to prevent.

Hoisting the import to module scope is not viable (circular import via
aiohttp_transport -> secret_managers.main -> caching -> gcs_bucket_base ->
http_handler during litellm/__init__). Instead, both methods already hold
the live transport instance, so resolve the type from the runtime class
name and skip the import entirely.
This commit is contained in:
Isaac 2026-10-24 13:24:38 +08:00
parent ecdc0f9ab9
commit 76a17d937e

View file

@ -1135,12 +1135,15 @@ class AsyncHTTPHandler:
def _aiohttp_session_bound_elsewhere(self, loop: asyncio.AbstractEventLoop) -> bool:
"""True when the wrapped aiohttp session is bound to a loop other than
``loop`` — awaiting ``aclose()`` here would touch that loop's internals."""
from litellm.llms.custom_httpx.aiohttp_transport import (
LiteLLMAiohttpTransport,
)
# 2026-10-24 patch 0006: class-name check instead of isinstance -- the
# original function-level import always raises ImportError when the
# handler is GC'd during interpreter shutdown (sys.meta_path is already
# None), which silently aborted the disposal inside __del__'s bare
# except and let aiohttp fire "Unclosed client session"/"Unclosed
# connector" warnings at GC. The instance is in hand, so its runtime
# class carries all the information isinstance needed.
transport: Final = getattr(self._client, "_transport", None)
if not isinstance(transport, LiteLLMAiohttpTransport):
if type(transport).__name__ != "LiteLLMAiohttpTransport":
return False
session: Final = transport.client
if not isinstance(session, ClientSession) or session.closed:
@ -1158,12 +1161,15 @@ class AsyncHTTPHandler:
"Unclosed client session" / "Unclosed connector" warnings fire at
garbage collection.
"""
from litellm.llms.custom_httpx.aiohttp_transport import (
LiteLLMAiohttpTransport,
)
# 2026-10-24 patch 0006: class-name check instead of the function-level
# import -- that import always raises ImportError when this runs during
# interpreter shutdown (sys.meta_path is already None), silently
# aborting the disposal inside __del__'s bare except and leaving the
# wrapped aiohttp session to fire "Unclosed client session" /
# "Unclosed connector" warnings at GC. The instance is in hand, so its
# runtime class carries all the information isinstance needed.
transport: Final = getattr(self._client, "_transport", None)
if not isinstance(transport, LiteLLMAiohttpTransport):
if type(transport).__name__ != "LiteLLMAiohttpTransport":
return
# A shared session (e.g. the proxy's) is never this handler's to close.
if not getattr(transport, "_owns_session", False):