-
Notifications
You must be signed in to change notification settings - Fork 19
Description
What happened?
The REST client's streaming methods (send_streaming_message and subscribe_to_task) fail intermittently with RuntimeError: Event loop is closed during async cleanup.
Possible Root Cause
The REST client re-uses a shared AsyncClient for streaming. While the JSON-RPC client creates a new client per request. When pytest-asyncio creates a new event loop for each test (default behavior: asyncio_default_test_loop_scope=function), the shared AsyncClient from a previous test can become bound to a closed event loop, causing the cleanup errors for the REST scenario.
Possible Solutions
Option 1: Change pytest-asyncio loop scope. Add to pyproject.toml: asyncio_default_test_loop_scope = "session"
This shares a single event loop across all tests, keeping the shared client valid. However, this seems a workaround that will affects all tests as this option is global and may have unintended side effects. Don't know if it's the cleanest solution.
Option 2: Create new client per request
Update REST streaming methods to match JSON-RPC behavior
Before:
async with self.async_client.stream("POST", url, json=payload, headers=headers) as response:After:
async with AsyncClient(timeout=self.timeout, verify=self._create_ssl_context()) as client:
async with client.stream("POST", url, json=payload, headers=headers) as response:I let you analyze this issue that was discovered while working on this PR in a2a-js sdk. See discussion here for more details. LMK if you need further details to help you debug the situation. If analysis is valid and possible solutions seems acceptable to you, I'd rather go with option 2, but I let you decide, just a proposition.
Relevant log output
====================================================================================================== FAILURES ======================================================================================================
__________________________________________________________________________________________ test_sse_event_format_compliance __________________________________________________________________________________________
tck/transport/rest_client.py:284: in send_streaming_message
async with self.async_client.stream("POST", url, json=payload, headers=headers) as response:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.local/share/uv/python/cpython-3.14.0-macos-aarch64-none/lib/python3.14/contextlib.py:214: in __aenter__
return await anext(self.gen)
^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14/site-packages/httpx/_client.py:1583: in stream
response = await self.send(
.venv/lib/python3.14/site-packages/httpx/_client.py:1629: in send
response = await self._send_handling_auth(
.venv/lib/python3.14/site-packages/httpx/_client.py:1657: in _send_handling_auth
response = await self._send_handling_redirects(
.venv/lib/python3.14/site-packages/httpx/_client.py:1694: in _send_handling_redirects
response = await self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14/site-packages/httpx/_client.py:1730: in _send_single_request
response = await transport.handle_async_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14/site-packages/httpx/_transports/default.py:394: in handle_async_request
resp = await self._pool.handle_async_request(req)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14/site-packages/httpcore/_async/connection_pool.py:256: in handle_async_request
raise exc from None
.venv/lib/python3.14/site-packages/httpcore/_async/connection_pool.py:236: in handle_async_request
response = await connection.handle_async_request(
.venv/lib/python3.14/site-packages/httpcore/_async/connection.py:103: in handle_async_request
return await self._connection.handle_async_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14/site-packages/httpcore/_async/http11.py:135: in handle_async_request
await self._response_closed()
.venv/lib/python3.14/site-packages/httpcore/_async/http11.py:250: in _response_closed
await self.aclose()
.venv/lib/python3.14/site-packages/httpcore/_async/http11.py:258: in aclose
await self._network_stream.aclose()
.venv/lib/python3.14/site-packages/httpcore/_backends/anyio.py:53: in aclose
await self._stream.aclose()
.venv/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:1352: in aclose
self._transport.close()
../../../.local/share/uv/python/cpython-3.14.0-macos-aarch64-none/lib/python3.14/asyncio/selector_events.py:1209: in close
super().close()
../../../.local/share/uv/python/cpython-3.14.0-macos-aarch64-none/lib/python3.14/asyncio/selector_events.py:869: in close
self._loop.call_soon(self._call_connection_lost, None)
../../../.local/share/uv/python/cpython-3.14.0-macos-aarch64-none/lib/python3.14/asyncio/base_events.py:827: in call_soon
self._check_closed()
../../../.local/share/uv/python/cpython-3.14.0-macos-aarch64-none/lib/python3.14/asyncio/base_events.py:550: in _check_closed
raise RuntimeError('Event loop is closed')
E RuntimeError: Event loop is closed
During handling of the above exception, another exception occurred:
tests/optional/capabilities/test_streaming_methods.py:589: in test_sse_event_format_compliance
async for event in stream:
tck/transport/rest_client.py:329: in send_streaming_message
raise TransportError(error_msg, TransportType.REST)
E tck.transport.base_client.TransportError: [REST] Unexpected error in REST streaming: Event loop is closed
------------------------------------------------------------------------------------------------- Captured log call --------------------------------------------------------------------------------------------------
ERROR tck.transport.rest_client:rest_client.py:328 Unexpected error in REST streaming: Event loop is closed
============================================================================================== short test summary info ===============================================================================================
FAILED tests/optional/capabilities/test_streaming_methods.py::test_sse_event_format_compliance - tck.transport.base_client.TransportError: [REST] Unexpected error in REST streaming: Event loop is closed
================================================================================ 1 failed, 20 passed, 52 skipped, 1 xfailed in 51.61s ================================================================================
⬅️ [rest] Exit code: 1Code of Conduct
- I agree to follow this project's Code of Conduct