fix(runtime): prevent streaming-bridge deadlock on client disconnect (#482)#563
Merged
Merged
Conversation
Contributor
✅ No Breaking Changes DetectedNo public API breaking changes found in this PR. |
Contributor
|
Claude Security Review: no high-confidence findings. (run) |
7b87269 to
ff641f5
Compare
Contributor
|
Claude Security Review: no high-confidence findings. (run) |
ff641f5 to
8681acf
Compare
Contributor
|
Claude Security Review: no high-confidence findings. (run) |
1a45b73 to
8bfcb21
Compare
Contributor
|
Claude Security Review: no high-confidence findings. (run) |
…482) The async->sync streaming bridge (_async_gen_to_sync_gen) ran its producer coroutine on the single shared worker event loop and used a blocking queue.Queue.put(). When an SSE consumer stopped draining (client disconnect or the 15-min read timeout), the bounded queue filled and the blocking put froze the worker-loop thread process-wide, starving every other session's handler on the microVM. Symptoms: invocations hang with no logs while /ping stays healthy. Fix: - Producer now uses a non-blocking put_nowait and yields the loop via asyncio.sleep when the queue is full, so it never blocks the shared loop. - A stop event, set in the consumer's finally (fires on GeneratorExit at client disconnect), tears the orphaned producer down and frees a queue slot so a parked producer wakes and exits. The source async generator is aclosed. Adds unit tests (worker loop survives an abandoned stream; a second session survives the first's disconnect) and a real-server integration test that reproduces the disconnect over a full HTTP stack.
8bfcb21 to
1e9504f
Compare
Contributor
|
Claude Security Review: no high-confidence findings. (run) |
jesseturner21
approved these changes
Jul 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #482
_async_gen_to_sync_genruns its producer on the shared worker event loop and buffers through a boundedqueue.Queue(maxsize=100)with a blockingq.put(). When an SSE consumer stops draining (client disconnect or the 15-min read timeout), the queue fills and the blocking put freezes the worker-loop thread process-wide — every other session on the microVM is starved (invocations hang, no logs, while/pingstays healthy).Fix
put_nowait()andawait asyncio.sleep()onqueue.Full, so it never blocks the shared loop (happy path keeps the fast path, no added latency).stopevent set in the consumer'sfinally(fires onGeneratorExitat disconnect) tears the orphaned producer down and frees a slot so a parked producer wakes; the source generator isaclose()d.Testing
/pinghealthy and fresh session gets200.