From 49a90d03fa4dc46d24613adf0c048d3c6bcbd357 Mon Sep 17 00:00:00 2001 From: flydream Date: Mon, 6 Jul 2026 14:37:56 +0800 Subject: [PATCH] fix(multiagent): guard MCPExecutionBinder map with RWMutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Concurrent tool callbacks in eino_adk_run_loop and the MCP monitor goroutine call Bind/ExecutionID on the same binder. The previous implementation used a plain map[string]string, causing intermittent 'concurrent map read and map write' panics — the same race that TestMCPExecutionBinder_ConcurrentBind was added to guard against. Wrap the map with sync.RWMutex (write lock in Bind, read lock in ExecutionID). The TrimSpace of toolCallID/executionID stays outside the critical section to keep it minimal. go vet ./internal/multiagent/... ✓ go test -race ./internal/multiagent/... -run MCPExecutionBinder ✓ --- internal/multiagent/mcp_execution_binder.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/multiagent/mcp_execution_binder.go b/internal/multiagent/mcp_execution_binder.go index 3e33b724..79fec45b 100644 --- a/internal/multiagent/mcp_execution_binder.go +++ b/internal/multiagent/mcp_execution_binder.go @@ -1,9 +1,13 @@ package multiagent -import "strings" +import ( + "strings" + "sync" +) // MCPExecutionBinder maps ADK toolCallID → MCP monitor execution ID for a single agent run. type MCPExecutionBinder struct { + mu sync.RWMutex byToolCall map[string]string } @@ -20,12 +24,17 @@ func (b *MCPExecutionBinder) Bind(toolCallID, executionID string) { if tid == "" || eid == "" { return } + b.mu.Lock() b.byToolCall[tid] = eid + b.mu.Unlock() } func (b *MCPExecutionBinder) ExecutionID(toolCallID string) string { if b == nil { return "" } - return b.byToolCall[strings.TrimSpace(toolCallID)] + tid := strings.TrimSpace(toolCallID) + b.mu.RLock() + defer b.mu.RUnlock() + return b.byToolCall[tid] }