Repository navigation
fix: clean up pending agent stream reads on exit - #133
Shubham-Padkonde wants to merge 1 commit into
Conversation
|
Hit this one too on Symptom: when an SSE client disconnects mid-turn, the merged generator is abandoned at its Minimal repro, no server needed: import asyncio, gc
from chatkit.agents import _AsyncQueueIterator, _merge_generators
async def agent_events(n):
for i in range(n):
await asyncio.sleep(0.01)
yield f"event-{i}"
async def main():
queue = asyncio.Queue()
merged = _merge_generators(agent_events(5), _AsyncQueueIterator(queue))
n = 0
async for _ in merged:
n += 1
if n >= 2:
break # abandon, as a disconnect does
del merged, queue
for _ in range(3):
gc.collect()
await asyncio.sleep(0.05)
asyncio.run(main())I ran your patch against that and against a live app: leak gone, normal completion path unchanged. Not blocking — it's log noise, the orphaned task just waits on a queue. But it does bury genuine |
When an agent stream fails or its consumer is cancelled,
_merge_generatorsleaves the other source's pending read running. Normal exhaustion cancels outstanding reads but does not wait for their asynchronous cleanup.Cancel and await remaining reads in a
finallyblock on every exit path. Keep the existing event-merging and stop-on-exhaustion behavior. Annotate the helper's actual async-generator return type so its close operation can be checked by Pyright.Validation: four regression cases fail before the fix and pass afterward, covering source failure, consumer cancellation, explicit close, and source exhaustion. All 134 tests pass; full Ruff and Pyright checks pass. These are offline tests with no API calls.