Skip to content

Commit 8711255

Browse files
committed
Add event-loop responsiveness test for sync resource handlers
Proves the behavioral fix directly: the handler blocks on a threading.Event in a worker thread while the async side awaits an anyio.Event. The handler signals back into the event loop via anyio.from_thread.run_sync, so the async side's await resolves without polling or sleeps. On regression (sync runs inline), anyio.from_thread.run_sync raises RuntimeError immediately since there is no worker-thread context, failing fast rather than waiting out the fail_after timeout.
1 parent 904dc9f commit 8711255

File tree

1 file changed

+32
-0
lines changed

1 file changed

+32
-0
lines changed

tests/server/mcpserver/resources/test_function_resources.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import threading
22

3+
import anyio
4+
import anyio.from_thread
35
import pytest
46
from pydantic import BaseModel
57

@@ -210,3 +212,33 @@ def blocking_fn() -> str:
210212

211213
assert result == "data"
212214
assert fn_thread[0] != main_thread
215+
216+
217+
@pytest.mark.anyio
218+
async def test_sync_fn_does_not_block_event_loop():
219+
"""A blocking sync resource function must not stall the event loop.
220+
221+
On regression (sync runs inline), anyio.from_thread.run_sync raises
222+
RuntimeError because there is no worker-thread context, failing fast.
223+
"""
224+
handler_entered = anyio.Event()
225+
release = threading.Event()
226+
227+
def blocking_fn() -> str:
228+
anyio.from_thread.run_sync(handler_entered.set)
229+
release.wait()
230+
return "done"
231+
232+
resource = FunctionResource(uri="resource://test", name="test", fn=blocking_fn)
233+
result: list[str | bytes] = []
234+
235+
async def run() -> None:
236+
result.append(await resource.read())
237+
238+
with anyio.fail_after(5):
239+
async with anyio.create_task_group() as tg:
240+
tg.start_soon(run)
241+
await handler_entered.wait()
242+
release.set()
243+
244+
assert result == ["done"]

0 commit comments

Comments
 (0)