When sse_reader encounters an error before receiving the endpoint event,
the except handler tried to send the exception to read_stream_writer.
With a zero-buffer stream and no reader (the caller is still blocked in
tg.start() waiting for task_status.started()), send() blocks forever.
Track whether started() has fired. Before it, re-raise so the exception
propagates through tg.start(). After it, send to the stream as before.
This also adds a guard for the case where a server sends a message event
before the endpoint event, which would deadlock on the same send() call.
The dedicated SSEError handler from #975 is removed since the started
flag now handles all pre-endpoint exceptions uniformly.
Github-Issue: #447
Fixes #447
Problem
sse_client launches sse_reader via tg.start(), which blocks until task_status.started() is called. When an error occurs before the endpoint event is received, the except Exception handler tries to send the exception to read_stream_writer. The stream has buffer size 0 and nobody is reading (the caller is still blocked in tg.start()), so send() blocks forever.
This deadlocks in three scenarios:
Fix
Track whether task_status.started() has fired with a started flag. Before it fires, re-raise exceptions so they propagate through tg.start() to the caller. After it fires, send them to the stream as before — the caller is reading by then.
This generalizes the approach from #975, which fixed the same deadlock but only for SSEError. The dedicated SSEError handler is removed since the flag now covers all pre-endpoint exceptions uniformly.
Behavior change
Previously, SSEError raised mid-stream (after the endpoint was received) would crash the task group. Now it is delivered on the read stream like other post-endpoint errors, letting the session layer handle it. This aligns with how stdio and websocket transports surface mid-stream errors.
Testing
Four new regression tests in tests/shared/test_sse.py cover:
All tests use anyio.fail_after(5) guards — if the deadlock regresses, tests fail with timeout rather than hanging CI.
AI Disclaimer