Skip to content

fix: make AgentGraph destructor loop-safe to prevent connection leaks - #147

Closed
bhavyakeerthi3 wants to merge 1 commit into
reactome:mainfrom
bhavyakeerthi3:fix/asyncio-destructor-connection-leak
Closed

fix: make AgentGraph destructor loop-safe to prevent connection leaks#147
bhavyakeerthi3 wants to merge 1 commit into
reactome:mainfrom
bhavyakeerthi3:fix/asyncio-destructor-connection-leak

Conversation

@bhavyakeerthi3

@bhavyakeerthi3 bhavyakeerthi3 commented Mar 14, 2026

Copy link
Copy Markdown

Problem

The AgentGraph destructor (__del__) currently calls asyncio.run(self.close_pool()). In an asynchronous environment (like a Chainlit or FastAPI server), if the agent instance is deleted while an event loop is already running, asyncio.run() raises a RuntimeError: This event loop is already running. Because the destructor fails mid-execution, the connection pool is never closed, leading to hanging database connections and potential pool exhaustion.

Fix #146

Updated the destructor to be loop-aware. It now checks for a running event loop and schedules the cleanup task using loop.create_task() instead of attempting to start a new nested loop.

Important

loop.create_task() during __del__ is best-effort. If the event
loop closes before the task executes, cleanup may still be skipped.
The robust long-term solution is explicit lifecycle management via a
shutdown() method, but this fix resolves the immediate
RuntimeError and improves reliability during server reloads.

Verification

Verified with a standalone validation script covering two scenarios:

  1. Active Loop: Confirmed that close_pool is scheduled as a task when a loop is running.
  2. No Loop: Confirmed that asyncio.run is used as a fallback when no loop is present (e.g., script exit).

Files Affected

  • src/agent/graph.py

@adamjohnwright

Copy link
Copy Markdown
Contributor

Thank you — you both independently found this, which is what made it credible.

Fixed on main in #173, though not with either approach as proposed:

The change closes the pool only when no loop is running, and otherwise warns that it is still open rather than pretending otherwise. Writing the test surfaced that even the warning fails at interpreter shutdown once logging's handlers close — so this is containment, and the real fix is an explicit shutdown hook.

Closing as resolved. Your reports are credited in the commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: asyncio.run() inside AgentGraph destructor triggers RuntimeError and leaks connections

2 participants