Removed Hack that is no longer required (#10195)

This commit is contained in:
Tim O'Farrell 2025-08-10 12:13:19 -06:00 committed by GitHub
parent 116ba199d1
commit 3302c31c60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 0 additions and 147 deletions

View File

@ -1,78 +0,0 @@
"""
LiteLLM currently have an issue where HttpHandlers are being created but not
closed. We have submitted a PR to them, (https://github.com/BerriAI/litellm/pull/8711)
and their dev team say they are in the process of a refactor that will fix this, but
in the meantime, we need to manage the lifecycle of the httpx.Client manually.
We can't simply pass in our own client object, because all the different implementations use
different types of client object.
So we monkey patch the httpx.Client class to track newly created instances and close these
when the operations complete. (Since some paths create a single shared client and reuse these,
we actually need to create a proxy object that allows these clients to be reusable.)
Hopefully, this will be fixed soon and we can remove this abomination.
"""
import contextlib
from typing import Callable
import httpx
@contextlib.contextmanager
def ensure_httpx_close():
wrapped_class = httpx.Client
proxys = []
class ClientProxy:
"""
Sometimes LiteLLM opens a new httpx client for each connection, and does not close them.
Sometimes it does close them. Sometimes, it reuses a client between connections. For cases
where a client is reused, we need to be able to reuse the client even after closing it.
"""
client_constructor: Callable
args: tuple
kwargs: dict
client: httpx.Client
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
self.client = wrapped_class(*self.args, **self.kwargs)
proxys.append(self)
def __getattr__(self, name):
# Invoke a method on the proxied client - create one if required
if self.client is None:
self.client = wrapped_class(*self.args, **self.kwargs)
return getattr(self.client, name)
def close(self):
# Close the client if it is open
if self.client:
self.client.close()
self.client = None
def __iter__(self, *args, **kwargs):
# We have to override this as debuggers invoke it causing the client to reopen
if self.client:
return self.client.iter(*args, **kwargs)
return object.__getattribute__(self, 'iter')(*args, **kwargs)
@property
def is_closed(self):
# Check if closed
if self.client is None:
return True
return self.client.is_closed
httpx.Client = ClientProxy
try:
yield
finally:
httpx.Client = wrapped_class
while proxys:
proxy = proxys.pop()
proxy.close()

View File

@ -1,69 +0,0 @@
import httpx
from openhands.utils.ensure_httpx_close import ensure_httpx_close
def test_ensure_httpx_close_basic():
"""Test basic functionality of ensure_httpx_close."""
ctx = ensure_httpx_close()
with ctx:
# Create a client - should be tracked
client = httpx.Client()
# After context exit, client should be closed
assert client.is_closed
def test_ensure_httpx_close_multiple_clients():
"""Test ensure_httpx_close with multiple clients."""
ctx = ensure_httpx_close()
with ctx:
client1 = httpx.Client()
client2 = httpx.Client()
assert client1.is_closed
assert client2.is_closed
def test_ensure_httpx_close_nested():
"""Test nested usage of ensure_httpx_close."""
with ensure_httpx_close():
client1 = httpx.Client()
with ensure_httpx_close():
client2 = httpx.Client()
assert not client2.is_closed
# After inner context, client2 should be closed
assert client2.is_closed
# client1 should still be open since outer context is still active
assert not client1.is_closed
# After outer context, both clients should be closed
assert client1.is_closed
assert client2.is_closed
def test_ensure_httpx_close_exception():
"""Test ensure_httpx_close when an exception occurs."""
client = None
try:
with ensure_httpx_close():
client = httpx.Client()
raise ValueError('Test exception')
except ValueError:
pass
# Client should be closed even if an exception occurred
assert client is not None
assert client.is_closed
def test_ensure_httpx_close_restore_client():
"""Test that the original client is restored after context exit."""
original_client = httpx.Client
with ensure_httpx_close():
assert httpx.Client != original_client
# Original __init__ should be restored
assert httpx.Client == original_client