diff --git a/.changeset/python-volume-proxy-transport.md b/.changeset/python-volume-proxy-transport.md new file mode 100644 index 0000000000..34a49d3fe7 --- /dev/null +++ b/.changeset/python-volume-proxy-transport.md @@ -0,0 +1,11 @@ +--- +"@e2b/python-sdk": patch +--- + +fix(python-sdk): stop leaking per-call proxy connection pools in volume content clients + +The volume content clients passed both `proxy` and the shared cached +`transport` to httpx, so with a proxy configured every volume operation mounted +a fresh, never-closed proxy transport. The proxy is already part of the cached +transport, so the client-level `proxy` argument is now dropped. Volume +transports also gained connect-level retries, matching the other transports. diff --git a/packages/python-sdk/e2b/volume/client_async/__init__.py b/packages/python-sdk/e2b/volume/client_async/__init__.py index 0df74903c0..db4fe8411e 100644 --- a/packages/python-sdk/e2b/volume/client_async/__init__.py +++ b/packages/python-sdk/e2b/volume/client_async/__init__.py @@ -7,7 +7,7 @@ from httpx import Limits from httpx._types import ProxyTypes -from e2b.api import make_async_logging_event_hooks +from e2b.api import connection_retries, make_async_logging_event_hooks from e2b.api.metadata import default_headers from e2b.exceptions import AuthenticationException from e2b.volume.client.client import AuthenticatedClient as AsyncVolumeApiClient @@ -47,7 +47,8 @@ def get_api_client(config: VolumeConnectionConfig, **kwargs) -> AsyncVolumeApiCl httpx.Timeout(request_timeout) if request_timeout is not None else None ), httpx_args={ - "proxy": config.proxy, + # The proxy lives in the cached transport; passing `proxy` here too + # would mount a fresh, never-closed proxy transport per client. "transport": get_transport(config), "event_hooks": make_async_logging_event_hooks(config.logger), }, @@ -82,6 +83,7 @@ def get_transport(config: VolumeConnectionConfig) -> AsyncTransportWithLogger: transport = AsyncTransportWithLogger( limits=limits, proxy=config.proxy, + retries=connection_retries, ) loop_instances[key] = transport diff --git a/packages/python-sdk/e2b/volume/client_sync/__init__.py b/packages/python-sdk/e2b/volume/client_sync/__init__.py index 4ceceda321..d5dff28391 100644 --- a/packages/python-sdk/e2b/volume/client_sync/__init__.py +++ b/packages/python-sdk/e2b/volume/client_sync/__init__.py @@ -6,7 +6,7 @@ from httpx import Limits from httpx._types import ProxyTypes -from e2b.api import make_logging_event_hooks +from e2b.api import connection_retries, make_logging_event_hooks from e2b.api.metadata import default_headers from e2b.exceptions import AuthenticationException from e2b.volume.client.client import AuthenticatedClient as VolumeApiClient @@ -46,7 +46,8 @@ def get_api_client(config: VolumeConnectionConfig, **kwargs) -> VolumeApiClient: httpx.Timeout(request_timeout) if request_timeout is not None else None ), httpx_args={ - "proxy": config.proxy, + # The proxy lives in the cached transport; passing `proxy` here too + # would mount a fresh, never-closed proxy transport per client. "transport": get_transport(config), "event_hooks": make_logging_event_hooks(config.logger), }, @@ -74,6 +75,7 @@ def get_transport(config: VolumeConnectionConfig) -> TransportWithLogger: transport = TransportWithLogger( limits=limits, proxy=config.proxy, + retries=connection_retries, ) instances[key] = transport TransportWithLogger._thread_local.instances = instances