fix: MCP call_tool threading.Lock held across await deadlocks event loop#1554
Open
PaoloC68 wants to merge 2 commits into
Open
fix: MCP call_tool threading.Lock held across await deadlocks event loop#1554PaoloC68 wants to merge 2 commits into
PaoloC68 wants to merge 2 commits into
Conversation
Two bugs in helpers/mcp_handler.py: 1. threading.Lock held across await in MCPServerRemote/MCPServerLocal.call_tool caused the entire event loop thread to block whenever a slow MCP tool (e.g. Perplexity deep research) was running, making A0 unresponsive to all new chats until the global timeout expired. Fix: use asyncio.Lock for call_tool; keep threading.Lock for sync methods. 2. MCPClientBase.call_tool always used the global mcp_client_tool_timeout, ignoring per-server tool_timeout overrides. Fix: prefer self.server.tool_timeout, fall back to global setting.
The real deadlock: MCPConfig.call_tool held threading.Lock while awaiting server.call_tool(), blocking the entire event loop thread for all other coroutines (new chats, any other MCP calls). Fix: resolve the target server under the lock (sync, instant), release the lock, then await outside it.
1154592 to
beab5fc
Compare
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.
Fixes #1553
Problem
Three
threading.Lockinstances inhelpers/mcp_handler.pyare held acrossawaitcalls. Athreading.Lockis an OS-level blocking mutex — holding it across anawaitsuspends the coroutine while keeping the lock acquired. Any other coroutine that tries to acquire the same lock blocks the entire event loop thread, making Agent Zero completely unresponsive to new chats while a slow MCP tool (e.g. Perplexity deep research) is running.A secondary bug:
MCPClientBase.call_toolalways used the globalmcp_client_tool_timeout, ignoring per-servertool_timeoutoverrides.Changes
helpers/mcp_handler.py— 3 fixes, 1 file:MCPServerRemote.call_tool: addasyncio.Lockinstance (__async_lock), useasync with self.__async_lockinstead ofwith self.__lock. Sync methods (get_error,get_tools,has_tool,update) keepthreading.Lock— correct, they're called from sync context.MCPServerLocal.call_tool: same fix as above.MCPConfig.call_tool: resolve the target server underthreading.Lock(sync, instant list lookup), release the lock, thenawait server.call_tool()outside it. This is the outermost and most impactful site — it's the dispatcher that all MCP tool calls pass through.MCPClientBase.call_tool:tool_timeout = self.server.tool_timeout or set["mcp_client_tool_timeout"]— per-server override now takes precedence over global default.Tests
Verified on a live Agent Zero instance (Proxmox LXC, Docker):
Existing test suite:
pytest— no new failures introduced.Notes
threading.Lockis intentionally kept for all sync accessors — only the asynccall_toolpath is changed