-
Notifications
You must be signed in to change notification settings - Fork 2.5k
fix: handle ClosedResourceError in StreamableHTTP message router #1384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: handle ClosedResourceError in StreamableHTTP message router #1384
Conversation
Hi, In anyio's Implementation1. Conditions for Iteration TerminationClass inheritance:
As we can see in the implementation of async def __anext__(self) -> T_co:
try:
return await self.receive()
except EndOfStream:
raise StopAsyncIteration from None That is, the 2. When to Raise EndOfStream or ClosedResourceError
def receive_nowait(self) -> T_co:
"""
Receive the next item if it can be done without waiting.
:return: the received item
:raises ~anyio.ClosedResourceError: if this send stream has been closed
:raises ~anyio.EndOfStream: if the buffer is empty and this stream has been
closed from the sending end
:raises ~anyio.WouldBlock: if there are no items in the buffer and no tasks
waiting to send
""" All if self._closed:
raise ClosedResourceError And of course, class MemoryObjectReceiveStream:
...
...
def close(self) -> None:
"""
Close the stream.
This works the exact same way as :meth:`aclose`, but is provided as a special
case for the benefit of synchronous callbacks.
"""
if not self._closed:
self._closed = True
self._state.open_receive_channels -= 1
if self._state.open_receive_channels == 0:
send_events = list(self._state.waiting_senders.keys())
for event in send_events:
event.set() Review of Known IssuesIn issue #1219, the debug information clearly shows The traceback in issue #1190 also lists the root cause of the error. It occurs when In fact, looking at the anyio implementation above, it's very clear that Why This Implementation is AppropriateThis implementation is not "silencing the error". In fact, in scenarios where multiple coroutines operate on the same stream simultaneously, checking whether the stream has been closed is a necessary operation. Since When checking externally, we simultaneously check |
Fix Race Condition in StreamableHTTP Transport (Closes #1363)
Motivation and Context
Starting from v1.12.0, MCP servers in HTTP Streamable mode experience a race condition that causes
ClosedResourceError
exceptions when requests fail validation early (e.g., due to incorrect Accept headers). This issue affects server reliability and can be reproduced consistently with fast-failing requests.The race condition occurs because:
async for write_stream_reader
loopwrite_stream_reader
callscheckpoint()
inreceive()
, yielding controlwrite_stream_reader
ClosedResourceError
This fix ensures graceful handling of stream closure scenarios without propagating exceptions that could destabilize the server.
How Has This Been Tested?
Test Suite
Added comprehensive test suite in
tests/issues/test_1363_race_condition_streamable_http.py
that reproduces the race condition:Invalid Accept Headers Test:
application/json
in Accept headertext/event-stream
in Accept headerInvalid Content-Type Test:
Log Analysis:
ClosedResourceError
exceptions occurTest Execution
Breaking Changes
None. This is a bug fix that maintains full backward compatibility.
Types of changes
Checklist
Additional context
Implementation Details
The fix adds explicit exception handling for
anyio.ClosedResourceError
in the message router loop:This approach:
Related Issues