-
Notifications
You must be signed in to change notification settings - Fork 810
Description
Description
When an AsyncSandbox or Sandbox instance is created, an httpx.AsyncClient (self._envd_api) is instantiated with its own connection pool. When the sandbox is killed (via kill(), __aexit__, or __exit__), the httpx client is never closed. This leaks TCP connections and file descriptors.
Root Cause
Creation (sandbox_async/main.py, lines 96-102):
self._envd_api = httpx.AsyncClient(
base_url=self.connection_config.get_sandbox_url(
self.sandbox_id, self.sandbox_domain
),
transport=self._transport,
headers=self.connection_config.sandbox_headers,
)__aexit__ (lines 315-316):
async def __aexit__(self, exc_type, exc_value, traceback):
await self.kill() # only calls API to kill remote sandboxkill() (lines 346-358) delegates to SandboxApi._cls_kill(), which is a static API call — it never touches self._envd_api.
No cleanup anywhere: Searching the entire Python SDK for _envd_api.aclose, _envd_api.close, _transport.aclose, _transport.close returns zero results.
The same issue exists in the sync SDK (sandbox_sync/main.py).
Impact
flowchart LR
A[Sandbox.create] --> B[httpx.AsyncClient created]
B --> C[sandbox.kill / __aexit__]
C --> D[API call to kill remote sandbox]
C -.->|MISSING| E[await _envd_api.aclose]
D --> F[Client abandoned with open connections]
F --> G[TCP sockets + FDs leak]
- File descriptor exhaustion: Each unclosed
httpx.AsyncClientholds open sockets. In long-running applications creating many sandboxes (the primary use case for AI agent orchestrators), this leads toOSError: [Errno 24] Too many open files - ResourceWarning: Python emits
ResourceWarning: unclosedon garbage collection - Connection pool bloat: httpx maintains a connection pool per client; unclosed clients keep pools alive
Reproduction
import asyncio
from e2b import AsyncSandbox
async def leak():
for i in range(100):
async with AsyncSandbox.create() as sandbox:
await sandbox.commands.run("echo hello")
# After each iteration: httpx client leaked
# After 100 iterations: 100 unclosed clients
# Eventually: OSError: Too many open files
asyncio.run(leak())Proposed Fix
Add cleanup in __aexit__ (and __exit__ for sync):
async def __aexit__(self, exc_type, exc_value, traceback):
await self.kill()
await self._envd_api.aclose()And for the sync SDK:
def __exit__(self, exc_type, exc_value, traceback):
self.kill()
self._envd_api.close()Consider also adding a close() method for users who don't use the context manager.
| Current | Proposed | |
|---|---|---|
| Resource cleanup | None | Proper httpx client shutdown |
| Breaking changes | None | None |
| Complexity | N/A | +1 line per SDK variant |
Environment
- Python SDK: latest
- Files:
sandbox_async/main.py,sandbox_sync/main.py
I'm happy to open a PR for this fix if you'd like.