Skip to content

Commit a3403d0

Browse files
author
BashNetCorp
committed
[v1.x] test(stdio): regression test for #1960 BrokenResourceError race
Covers the fix in the previous commit. Spawns a subprocess that emits a burst of valid JSONRPC notifications, exits the stdio_client context immediately, asserts no exception propagates. Fails before the fix (ExceptionGroup / BrokenResourceError), passes after. Wrapped in anyio.fail_after(5.0) per AGENTS.md. Github-Issue:#1960
1 parent 20209d6 commit a3403d0

File tree

1 file changed

+39
-0
lines changed

1 file changed

+39
-0
lines changed

tests/client/test_stdio.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,45 @@ async def test_stdio_context_manager_exiting():
3333
pass
3434

3535

36+
@pytest.mark.anyio
37+
async def test_stdio_client_exits_cleanly_while_server_still_writing():
38+
"""Regression test for #1960.
39+
40+
Exiting the ``stdio_client`` context while the subprocess is still writing to
41+
stdout used to surface ``anyio.BrokenResourceError`` through the task group
42+
(as an ``ExceptionGroup``). The ``finally`` block closes
43+
``read_stream_writer`` while the background ``stdout_reader`` task is
44+
mid-``send``.
45+
46+
The fix makes ``stdout_reader`` catch both ``ClosedResourceError`` and
47+
``BrokenResourceError`` and return cleanly, so exiting the context is a
48+
no-op no matter what the subprocess is doing.
49+
"""
50+
# A server that emits a large burst of valid JSON-RPC notifications without
51+
# ever reading stdin. When we exit the context below, the subprocess is
52+
# still in the middle of that burst, which is the exact shape of the race.
53+
noisy_script = textwrap.dedent(
54+
"""
55+
import sys
56+
for i in range(1000):
57+
sys.stdout.write(
58+
'{"jsonrpc":"2.0","method":"notifications/message",'
59+
'"params":{"level":"info","data":"line ' + str(i) + '"}}\\n'
60+
)
61+
sys.stdout.flush()
62+
"""
63+
)
64+
65+
server_params = StdioServerParameters(command=sys.executable, args=["-c", noisy_script])
66+
67+
# The ``async with`` must complete without an ``ExceptionGroup`` /
68+
# ``BrokenResourceError`` propagating. ``anyio.fail_after`` prevents a
69+
# regression from hanging CI.
70+
with anyio.fail_after(5.0):
71+
async with stdio_client(server_params) as (_, _):
72+
pass
73+
74+
3675
@pytest.mark.anyio
3776
@pytest.mark.skipif(tee is None, reason="could not find tee command")
3877
async def test_stdio_client():

0 commit comments

Comments
 (0)