Don't block the event loop on sync resource and prompt functions (#2380)

This commit is contained in:
Max Isbey 2026-03-31 13:43:56 -04:00 • committed by GitHub
commit 3ce0f76e6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,3 +1,7 @@
import threading
import anyio
import anyio.from_thread
import pytest
from pydantic import BaseModel
@ -190,3 +194,51 @@ class TestFunctionResourceMetadata:
)
assert resource.meta is None
@pytest.mark.anyio
async def test_sync_fn_runs_in_worker_thread():
"""Sync resource functions must run in a worker thread, not the event loop."""
main_thread = threading.get_ident()
fn_thread: list[int] = []
def blocking_fn() -> str:
fn_thread.append(threading.get_ident())
return "data"
resource = FunctionResource(uri="resource://test", name="test", fn=blocking_fn)
result = await resource.read()
assert result == "data"
assert fn_thread[0] != main_thread
@pytest.mark.anyio
async def test_sync_fn_does_not_block_event_loop():
"""A blocking sync resource function must not stall the event loop.
On regression (sync runs inline), anyio.from_thread.run_sync raises
RuntimeError because there is no worker-thread context, failing fast.
"""
handler_entered = anyio.Event()
release = threading.Event()
def blocking_fn() -> str:
anyio.from_thread.run_sync(handler_entered.set)
release.wait()
return "done"
resource = FunctionResource(uri="resource://test", name="test", fn=blocking_fn)
result: list[str | bytes] = []
async def run() -> None:
result.append(await resource.read())
with anyio.fail_after(5):
async with anyio.create_task_group() as tg:
tg.start_soon(run)
await handler_entered.wait()
release.set()
assert result == ["done"]