From 5e553121938ef1c5e48c53b76eb1dee999c2ef8b Mon Sep 17 00:00:00 2001 From: Anas Khan <83116240+anxkhn@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:17:41 +0530 Subject: [PATCH] fix(integrations): close AsyncDaytona client in DaytonaEnvironment.close() DaytonaEnvironment.close() deleted the sandbox and then dropped the AsyncDaytona client by setting self._client = None, without awaiting the client's close(). That client owns the underlying aiohttp session plus the REST and toolbox API clients, so discarding the reference leaked sockets on every create/close cycle. The Daytona SDK documents that AsyncDaytona.close() should be called to release those HTTP sessions and avoid resource leaks (or the client used as an async context manager). Await self._client.close() before clearing the reference. close() is idempotent, so the existing double-close no-op behavior is preserved. The unit test now gives the mock client an AsyncMock close() and asserts DaytonaEnvironment.close() awaits it exactly once. --- src/google/adk/integrations/daytona/_daytona_environment.py | 4 ++++ .../integrations/daytona/test_daytona_environment.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/google/adk/integrations/daytona/_daytona_environment.py b/src/google/adk/integrations/daytona/_daytona_environment.py index 6c8990c86f..ca5cbecfeb 100644 --- a/src/google/adk/integrations/daytona/_daytona_environment.py +++ b/src/google/adk/integrations/daytona/_daytona_environment.py @@ -96,6 +96,10 @@ async def close(self) -> None: if self._sandbox is not None: await self._sandbox.delete() self._sandbox = None + if self._client is not None: + # Close the AsyncDaytona client to release its underlying HTTP + # sessions and avoid leaking sockets across create/close cycles. + await self._client.close() self._client = None self._is_initialized = False diff --git a/tests/unittests/integrations/daytona/test_daytona_environment.py b/tests/unittests/integrations/daytona/test_daytona_environment.py index c4ee0b1b80..8436959f68 100644 --- a/tests/unittests/integrations/daytona/test_daytona_environment.py +++ b/tests/unittests/integrations/daytona/test_daytona_environment.py @@ -47,6 +47,7 @@ def _daytona_patch(sandbox: mock.MagicMock): """Patch AsyncDaytona to return a mock client.""" mock_client = mock.MagicMock(name="AsyncDaytona") mock_client.create = mock.AsyncMock(return_value=sandbox) + mock_client.close = mock.AsyncMock() with mock.patch.object(daytona, "AsyncDaytona", autospec=True) as mock_class: mock_class.return_value = mock_client @@ -103,14 +104,18 @@ async def test_close_deletes_sandbox_and_is_idempotent(daytona_patch, sandbox): env = DaytonaEnvironment() await env.initialize() assert env.is_initialized is True + client = daytona_patch.return_value await env.close() sandbox.delete.assert_awaited_once() + client.close.assert_awaited_once() assert env._sandbox is None + assert env._client is None assert env.is_initialized is False # Second close is a no-op. await env.close() sandbox.delete.assert_awaited_once() + client.close.assert_awaited_once() async def test_working_dir_requires_initialize():