Conversation
fire_event() called loop.create_task() without storing the returned Task. asyncio only keeps weak references to such tasks, so Python's GC can collect and silently discard them before _handle_event completes. Added a module-level _BG_TASKS set (matching the existing pattern in routes/chat_helpers.py and src/builtin_mcp.py) that holds strong references to tasks until they finish. Added regression tests verifying the task is tracked during execution, removed after completion, and survives forced garbage collection.
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
fire_event()insrc/event_bus.pycreates asyncio tasks vialoop.create_task()without storing the returnedTaskobject. asyncio only keeps weak references to such tasks, so Python's GC can collect them before_handle_eventcompletes — silently dropping the event handler with no error or log. This PR adds a_BG_TASKSset (matching the existing pattern inroutes/chat_helpers.py:56-69andsrc/builtin_mcp.py:93-105) that holds strong references until tasks finish.Linked Issue
Fixes #6288
Type of Change
What Changed
_BG_TASKS: set[asyncio.Task] = set()tosrc/event_bus.pyfire_event()to store the task in_BG_TASKSand register a done callback to discard it after completionWhy
fire_event()runs from 46 call sites across 16 files, triggeringmessage_sent,session_created,document_created,email_received,memory_added,skill_added, andresearch_completedevents. The returnedTaskwas never stored — asyncio only keeps weak references to it, so the GC can collect and silently discard it before_handle_eventcompletes. The codebase itself documents this hazard inapp.py:1054-1057and already uses the_BG_TASKSpattern in two other locations.Diff
How to Test
python -m pytest tests/test_event_bus_task_gc.py -v --noconftest— 4 tests should passfire_event()now stores the task in_BG_TASKSand removes it after completiongc.collect()during handler execution does not lose the taskChecklist
dev