Bug: fatal error: concurrent map writes #76

Closed
opened 2026-02-18 20:17:15 +00:00 by brenner-axiom · 1 comment
brenner-axiom commented 2026-02-18 20:17:15 +00:00 (Migrated from codeberg.org)

When running commands, the forgejo-mcp binary panics with 'fatal error: concurrent map writes'. This indicates a race condition in the code, likely from multiple goroutines writing to the same map without a mutex. This makes the CLI tool unsafe for concurrent or repeated use. See stack trace:\n\n\nfatal error: concurrent map writes\n\ngoroutine 20 [running]:\nruntime.throw({0x55bca0?, 0x1?})\n\t/home/linuxbrew/.linuxbrew/Cellar/go/1.26.0/libexec/src/runtime/panic.go:1024 +0x5d\nruntime.mapassign_fast64(...)\n\t/home/linuxbrew/.linuxbrew/Cellar/go/1.26.0/libexec/src/runtime/map_fast64.go:217 +0x305\ncodeberg.org/goern/forgejo-mcp/v2/cmd.init.0()\n\t/home/ubuntu/.openclaw/workspace/forgejo-mcp/cmd/cmd.go:116 +0x79\n...\n\n

When running commands, the forgejo-mcp binary panics with 'fatal error: concurrent map writes'. This indicates a race condition in the code, likely from multiple goroutines writing to the same map without a mutex. This makes the CLI tool unsafe for concurrent or repeated use. See stack trace:\n\n```\nfatal error: concurrent map writes\n\ngoroutine 20 [running]:\nruntime.throw({0x55bca0?, 0x1?})\n\t/home/linuxbrew/.linuxbrew/Cellar/go/1.26.0/libexec/src/runtime/panic.go:1024 +0x5d\nruntime.mapassign_fast64(...)\n\t/home/linuxbrew/.linuxbrew/Cellar/go/1.26.0/libexec/src/runtime/map_fast64.go:217 +0x305\ncodeberg.org/goern/forgejo-mcp/v2/cmd.init.0()\n\t/home/ubuntu/.openclaw/workspace/forgejo-mcp/cmd/cmd.go:116 +0x79\n...\n```\n
brenner-axiom commented 2026-02-19 05:46:55 +00:00 (Migrated from codeberg.org)

Investigation & Reproducer

Created a reproducer test suite in test/race/race_test.go. PR incoming.

Test Suite

Run with: go test -race -count=10 -timeout 120s ./test/race/

Tests included:

  • TestConcurrentToolCalls: Hammers all 41 tools from 20 goroutines × 5 iterations
  • TestConcurrentSameToolRepeated: Per-tool concurrent stress test (50 goroutines × 10 calls)
  • TestConcurrentListAndRegister: Race between tool registration and listing

Findings

  1. Could not reproduce the exact mapassign_fast64 crash with current code under -race. mcp-go v0.43.2 properly synchronizes its internal tool maps with sync.RWMutex.

  2. Secondary bug: cmd.init() calls flag.Parse() on global flag.CommandLine during package initialization, making go test ./cmd/ impossible (test framework flags conflict). Should move flag parsing to Execute().

  3. Secondary bug: nil pointer dereference in tool handlers. Many handlers access resp.StatusCode BEFORE checking err != nil. When API returns (nil, nil, err), this panics:

    user, resp, err := forgejo.Client().GetMyUserInfo()
    forgejo.LogAPICall(ctx, "GET", "/user", duration, resp.StatusCode, err) // CRASH if resp==nil
    if err != nil { ... }
    

    This nil-pointer crash under concurrent load may be the actual trigger for the reported panic.

Possible Root Cause

The mapassign_fast64 indicates a map with int64 keys — none exist in project code. Most likely candidate: a nil-pointer panic in a handler (finding #3) cascading into undefined behavior under the mcp-go worker pool concurrent execution.

## Investigation & Reproducer Created a reproducer test suite in `test/race/race_test.go`. PR incoming. ### Test Suite Run with: `go test -race -count=10 -timeout 120s ./test/race/` Tests included: - **TestConcurrentToolCalls**: Hammers all 41 tools from 20 goroutines × 5 iterations - **TestConcurrentSameToolRepeated**: Per-tool concurrent stress test (50 goroutines × 10 calls) - **TestConcurrentListAndRegister**: Race between tool registration and listing ### Findings 1. **Could not reproduce the exact `mapassign_fast64` crash** with current code under `-race`. mcp-go v0.43.2 properly synchronizes its internal tool maps with `sync.RWMutex`. 2. **Secondary bug: `cmd.init()` calls `flag.Parse()` on global `flag.CommandLine`** during package initialization, making `go test ./cmd/` impossible (test framework flags conflict). Should move flag parsing to `Execute()`. 3. **Secondary bug: nil pointer dereference in tool handlers.** Many handlers access `resp.StatusCode` BEFORE checking `err != nil`. When API returns `(nil, nil, err)`, this panics: ```go user, resp, err := forgejo.Client().GetMyUserInfo() forgejo.LogAPICall(ctx, "GET", "/user", duration, resp.StatusCode, err) // CRASH if resp==nil if err != nil { ... } ``` This nil-pointer crash under concurrent load may be the actual trigger for the reported panic. ### Possible Root Cause The `mapassign_fast64` indicates a map with int64 keys — none exist in project code. Most likely candidate: a nil-pointer panic in a handler (finding #3) cascading into undefined behavior under the mcp-go worker pool concurrent execution.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
agentic-forges/forgejo-mcp#76
No description provided.