Skip to content

Commit 3ccdfad

Browse files
committed
revert: Revert "fix(toolbox-core): Prevent ToolboxClient from closing externally ma…"
This reverts commit 7520adf.
1 parent 2638f66 commit 3ccdfad

File tree

4 files changed

+75
-51
lines changed

4 files changed

+75
-51
lines changed

packages/toolbox-core/src/toolbox_core/client.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,21 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
141141
"""
142142
await self.close()
143143

144-
def __del__(self):
145-
# This method is a "best-effort" safety net.
146-
# It should NOT be relied upon for guaranteed resource cleanup.
147-
# Explicitly using "async with" or calling "await client.close()" is the correct way.
148-
if self.__manage_session:
149-
try:
150-
loop = get_running_loop()
151-
except RuntimeError:
152-
loop = None
153-
154-
if loop and loop.is_running():
155-
# If a loop is running, try to schedule the close operation.
156-
# This is "fire-and-forget"; there's no guarantee it will complete
157-
# before the loop or interpreter shuts down.
158-
loop.create_task(self.__session.close())
144+
# def __del__(self): TODO: Remove
145+
# # This method is a "best-effort" safety net.
146+
# # It should NOT be relied upon for guaranteed resource cleanup.
147+
# # Explicitly using "async with" or calling "await client.close()" is the correct way.
148+
# if self.__manage_session:
149+
# try:
150+
# loop = get_running_loop()
151+
# except RuntimeError:
152+
# loop = None
153+
154+
# if loop and loop.is_running():
155+
# # If a loop is running, try to schedule the close operation.
156+
# # This is "fire-and-forget"; there's no guarantee it will complete
157+
# # before the loop or interpreter shuts down.
158+
# loop.create_task(self.__session.close())
159159

160160
async def close(self):
161161
"""

packages/toolbox-core/src/toolbox_core/sync_client.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ def close(self):
6868
any tools created by this Client to cease to function.
6969
"""
7070
coro = self.__async_client.close()
71-
run_coroutine_threadsafe(coro, self.__loop).result(timeout=5)
71+
# run_coroutine_threadsafe(coro, self.__loop).result(timeout=5) TODO: Remove
72+
run_coroutine_threadsafe(coro, self.__loop).result()
7273

73-
def __del__(self):
74-
self.close()
74+
# def __del__(self): TODO: Remove
75+
# self.close()
7576

7677
def load_tool(
7778
self,

packages/toolbox-core/tests/conftest.py

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,13 @@
2222
import subprocess
2323
import tempfile
2424
import time
25-
from asyncio import run_coroutine_threadsafe
2625
from typing import Generator
2726

2827
import google
2928
import pytest_asyncio
3029
from google.auth import compute_engine
3130
from google.cloud import secretmanager, storage
3231

33-
from toolbox_core import ToolboxSyncClient
34-
3532

3633
#### Define Utility Functions
3734
def get_env_var(key: str) -> str:
@@ -95,36 +92,36 @@ def get_auth_token(client_id: str) -> str:
9592

9693

9794
#### Define Fixtures
98-
@pytest_asyncio.fixture(autouse=True)
99-
def patch_sync_client_for_deadlock(monkeypatch):
100-
"""
101-
Automatically replaces the blocking `ToolboxSyncClient.close()` method
102-
with a non-blocking version for the entire test run.
103-
104-
The original `ToolboxSyncClient.close()` is a blocking method because it
105-
calls `.result()`. In the pytest environment, this blocking call creates a
106-
deadlock during the test teardown phase when it conflicts with other fixtures
107-
(like `sync_client_environment` or `toolbox_server`) that are also managing
108-
background processes and threads.
109-
110-
By replacing `close` with this safe, non-blocking version, we prevent the
111-
deadlock and allow the test suite's fixtures to tear down cleanly.
112-
This change is only active during the test run.
113-
"""
114-
115-
def non_blocking_close(self):
116-
"""A replacement for close() that doesn't block."""
117-
if hasattr(self.__class__, "_ToolboxSyncClient__loop") and hasattr(
118-
self, "_ToolboxSyncClient__async_client"
119-
):
120-
loop = self.__class__._ToolboxSyncClient__loop
121-
async_client = self._ToolboxSyncClient__async_client
122-
123-
if loop and loop.is_running():
124-
coro = async_client.close()
125-
run_coroutine_threadsafe(coro, loop)
126-
127-
monkeypatch.setattr(ToolboxSyncClient, "close", non_blocking_close)
95+
# @pytest_asyncio.fixture(autouse=True) TODO: Remove
96+
# def patch_sync_client_for_deadlock(monkeypatch):
97+
# """
98+
# Automatically replaces the blocking `ToolboxSyncClient.close()` method
99+
# with a non-blocking version for the entire test run.
100+
101+
# The original `ToolboxSyncClient.close()` is a blocking method because it
102+
# calls `.result()`. In the pytest environment, this blocking call creates a
103+
# deadlock during the test teardown phase when it conflicts with other fixtures
104+
# (like `sync_client_environment` or `toolbox_server`) that are also managing
105+
# background processes and threads.
106+
107+
# By replacing `close` with this safe, non-blocking version, we prevent the
108+
# deadlock and allow the test suite's fixtures to tear down cleanly.
109+
# This change is only active during the test run.
110+
# """
111+
112+
# def non_blocking_close(self):
113+
# """A replacement for close() that doesn't block."""
114+
# if hasattr(self.__class__, "_ToolboxSyncClient__loop") and hasattr(
115+
# self, "_ToolboxSyncClient__async_client"
116+
# ):
117+
# loop = self.__class__._ToolboxSyncClient__loop
118+
# async_client = self._ToolboxSyncClient__async_client
119+
120+
# if loop and loop.is_running():
121+
# coro = async_client.close()
122+
# run_coroutine_threadsafe(coro, loop)
123+
124+
# monkeypatch.setattr(ToolboxSyncClient, "close", non_blocking_close)
128125

129126

130127
@pytest_asyncio.fixture(scope="session")

packages/toolbox-core/tests/test_sync_client.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def sync_client_environment():
4747
if original_loop and original_loop.is_running():
4848
original_loop.call_soon_threadsafe(original_loop.stop)
4949
if original_thread and original_thread.is_alive():
50+
# original_thread.join(timeout=5) TODO: Remove
5051
original_thread.join()
5152
ToolboxSyncClient._ToolboxSyncClient__loop = None
5253
ToolboxSyncClient._ToolboxSyncClient__thread = None
@@ -63,6 +64,7 @@ def sync_client_environment():
6364
if test_loop and test_loop.is_running():
6465
test_loop.call_soon_threadsafe(test_loop.stop)
6566
if test_thread and test_thread.is_alive():
67+
# test_thread.join(timeout=5) TODO Remove
6668
test_thread.join()
6769

6870
# Explicitly set to None to ensure a clean state for the next fixture use/test.
@@ -288,6 +290,30 @@ def test_sync_client_creation_in_isolated_env(self, sync_client):
288290
sync_client._ToolboxSyncClient__async_client, ToolboxClient
289291
), "Async client should be ToolboxClient instance"
290292

293+
# @pytest.mark.usefixtures("sync_client_environment") TODO: Remove
294+
# def test_sync_client_close_method(self):
295+
# """
296+
# Tests the close() method of ToolboxSyncClient when manually created.
297+
# The sync_client_environment ensures loop/thread cleanup.
298+
# """
299+
# mock_async_client_instance = AsyncMock(spec=ToolboxClient)
300+
# # AsyncMock methods are already AsyncMocks
301+
# # mock_async_client_instance.close = AsyncMock(return_value=None)
302+
303+
# with patch(
304+
# "toolbox_core.sync_client.ToolboxClient",
305+
# return_value=mock_async_client_instance,
306+
# ) as MockedAsyncClientConst:
307+
# client = ToolboxSyncClient(TEST_BASE_URL)
308+
# # The sync client passes its internal loop to the async client.
309+
# MockedAsyncClientConst.assert_called_once_with(
310+
# TEST_BASE_URL, client_headers=None
311+
# )
312+
313+
# client.close() # This call closes the async_client's session.
314+
# mock_async_client_instance.close.assert_awaited_once()
315+
# # The sync_client_environment fixture handles stopping the loop/thread.
316+
291317
@pytest.mark.usefixtures("sync_client_environment")
292318
def test_sync_client_context_manager(self, aioresponses, tool_schema_minimal):
293319
"""

0 commit comments

Comments
 (0)