Repository navigation
fix: prevent control callback starvation when message consumers stall - #1481
MonikaDvorackova wants to merge 4 commits into
Conversation
mangeshraut712
left a comment
There was a problem hiding this comment.
The diagnosis is right. _read_messages does await self._message_send.send(message) for regular frames, so once the 100-slot buffer fills, the same loop that dispatches control_request frames (via _spawn_control_request_handler) is parked, and hooks/permissions behind the burst never get answered. The tests reproduce exactly that, and asserting that order is preserved after the control round-trip is a nice touch.
| self._message_send, self._message_receive = anyio.create_memory_object_stream[ | ||
| dict[str, Any] | ||
| ](max_buffer_size=100) | ||
| ](max_buffer_size=math.inf) |
There was a problem hiding this comment.
Two consequences of going fully unbounded that might be worth handling here:
- Lost backpressure is now silent. Previously a stalled consumer eventually blocked the reader, which stopped draining the CLI's stdout and throttled the subprocess. Now a host that stops reading after a
ResultMessagebut keeps the client alive (or a slow UI withinclude_partial_messages, which produces manystream_eventframes) grows memory with nothing in the logs. A one-timelogger.warningwhenself._message_send.statistics().current_buffer_usedcrosses a high-water mark (say 1,000) would keep the deadlock fix and still make the leak diagnosable. report_mirror_errorstill usessend_nowaitwith a# buffer-full edge casehandler, and its docstring says messages are "dropped rather than back-pressuring the read loop". Withmath.inf, that branch can't trigger any more. Updating the docstring (or removing the handler) would keep the two in sync.
There was a problem hiding this comment.
Addressed both points in commit aaafbbc: added a one-time warning at 1,000 unread messages with regression coverage, and updated report_mirror_error documentation and error handling. All 6 backpressure tests pass under asyncio and trio; Ruff lint and formatting checks pass. Thanks for the review.
Prevents control callback starvation when consumers stop reading the SDK message stream. Replaces the bounded message buffer with an unbounded AnyIO stream and adds regression tests for hook routing and message ordering.
Validation: 4 tests passed (asyncio and trio); Ruff passed.
Trade-off: memory usage may grow if messages remain unconsumed. This change does not fix stale ResultMessage attribution and is independent of #1464.
Related: #1463, #558.
AI-assisted implementation and tests; local validation performed by contributor.