fix: isolate DB connection per tool call (backport #56)#57
Conversation
(cherry picked from commit 2e4a0b1)
|
|
||
| inherited_db = getattr(frappe.local, "db", None) | ||
|
|
||
| frappe.connect(set_admin_as_user=False) |
There was a problem hiding this comment.
Potential connection leak if
frappe.connect() raises
frappe.connect(set_admin_as_user=False) sits between capturing inherited_db and entering the try/finally block. Frappe's connect() assigns to frappe.local.db early in its implementation; if it then raises (e.g. connection refused, auth failure), the newly assigned connection object is never closed and frappe.local.db is left pointing at a dead/partial connection for the remainder of the thread's lifetime. Moving frappe.connect() inside the try block (with a corresponding cleanup in finally) would cover this path.
| async def async_wrapper(*args, **kwargs): | ||
| try: | ||
| return await func(*args, **kwargs) | ||
| with _isolated_db(): | ||
| return await func(*args, **kwargs) | ||
| except Exception: | ||
| frappe.clear_messages() | ||
| raise |
There was a problem hiding this comment.
Sync
frappe.connect() blocks the event loop in async path
_isolated_db() is a synchronous context manager; entering it calls frappe.connect() (a blocking DB handshake) directly on the event loop thread. Async tools run in the event loop, so this stalls all other coroutines for the duration of the TCP+auth round-trip on every tool invocation. The fix for the sync case (asyncio.to_thread) does not apply here. Consider using asyncio.to_thread(frappe.connect, ...) or an async-friendly connection setup in async_wrapper if connection latency becomes a bottleneck.
LangGraph runs sync tools via asyncio.to_thread, which copies the calling context into the worker thread. Frappe's frappe.local is contextvar-backed, so tool worker threads inherited the same pymysql connection as the agent thread; concurrent tool calls (notably the document-planner subagent) corrupted the shared connection with "Packet sequence number wrong" / InterfaceError(0, '') errors.
Each tool call now opens a private connection, restores the session user, and closes it afterwards. The document-planner subagent tools are now wrapped too (they were previously passed raw).
This is an automatic backport of pull request #56 done by Mergify.