In _async_gen_to_sync_gen:
q: queue.Queue = queue.Queue(maxsize=100)
async def _produce() -> None:
async for chunk in async_gen:
q.put((True, chunk)) # ← BLOCKING put, no timeout
q.put((True, _DONE))
Sync consumer
while True:
ok, value = q.get() # ← stops iterating when SSE disconnects
yield value
Here's the deadlock chain:
SSE client disconnects → Starlette stops iterating the sync generator
_produce() is still running on the worker loop thread, producing chunks
Queue fills up to 100 items → q.put() blocks the worker loop thread
Worker loop thread is now frozen → container can't process ANY new requests
Container stays deadlocked until idle timeout
This is not something you can work around on your side — q.put() is a blocking call with no timeout or cancellation mechanism. The fix needs to be in the SDK.
Code ref: app.py L737-L750
In
_async_gen_to_sync_gen:q: queue.Queue = queue.Queue(maxsize=100)
async def _produce() -> None:
async for chunk in async_gen:
q.put((True, chunk)) # ← BLOCKING put, no timeout
q.put((True, _DONE))
Sync consumer
while True:
ok, value = q.get() # ← stops iterating when SSE disconnects
yield value
Here's the deadlock chain:
SSE client disconnects → Starlette stops iterating the sync generator
_produce() is still running on the worker loop thread, producing chunks
Queue fills up to 100 items → q.put() blocks the worker loop thread
Worker loop thread is now frozen → container can't process ANY new requests
Container stays deadlocked until idle timeout
This is not something you can work around on your side — q.put() is a blocking call with no timeout or cancellation mechanism. The fix needs to be in the SDK.
Code ref: app.py L737-L750