mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Removed Hack that is no longer required (#10195)
This commit is contained in:
parent
116ba199d1
commit
3302c31c60
@ -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()
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user