Skip to content

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

@bhavyakeerthi3

Description

@bhavyakeerthi3

Bug Description

The AgentGraph destructor (__del__) calls asyncio.run(self.close_pool()). In an asynchronous environment (such as a Chainlit or FastAPI server), if the agent is deleted while an event loop is already running, asyncio.run() raises a RuntimeError: This event loop is already running.

Since the destructor fails mid-execution, the connection pool is never closed, leading to hanging database connections and potential pool exhaustion in long-running deployments.

Minimal Reproduction

import asyncio
from agent.graph import AgentGraph

async def reproduce():
    graph = AgentGraph(profiles=["react_to_me"])
    await graph.initialize()
    del graph  # Triggers __del__ while loop is running
    # Result: RuntimeError and postgres connection remains open

asyncio.run(reproduce())

Why It's Non-Obvious

Python's __del__ is called by the garbage collector with no awareness of the async context. There is no exception raised at the call site — the RuntimeError appears only in logs, making this invisible during development and only visible under production load when connection counts begin climbing.

Fix

Update the destructor to be loop-aware:

def __del__(self) -> None:
    if self.pool:
        try:
            loop = asyncio.get_running_loop()
            if loop.is_running():
                loop.create_task(self.close_pool())
            else:
                asyncio.run(self.close_pool())
        except RuntimeError:
            asyncio.run(self.close_pool())

Note: 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 called by the
application, rather than relying on __del__ at all.

Files Affected

  • src/agent/graph.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions