fix: close done channel on nil response to prevent goroutine leak#766
fix: close done channel on nil response to prevent goroutine leak#766ezynda3 merged 1 commit intomark3labs:mainfrom
Conversation
WalkthroughThis change fixes a race condition in the HTTP Streamable Server where nil responses (JSON-RPC notifications) failed to properly coordinate goroutine shutdown. The fix closes the Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
server/streamable_http_test.go (1)
2861-2872: Userequirehere and give the client a timeout.That keeps the loop fail-fast, aligns this test with the rest of the testify-based suite, and avoids wedging the package if one POST stops returning.
Suggested cleanup
- client := &http.Client{Transport: &http.Transport{DisableKeepAlives: true}} + client := &http.Client{ + Timeout: 2 * time.Second, + Transport: &http.Transport{DisableKeepAlives: true}, + } @@ - resp, err := client.Post(ts.URL+"/mcp", "application/json", strings.NewReader(body)) - if err != nil { - t.Fatalf("iteration %d: %v", i, err) - } + resp, err := client.Post(ts.URL+"/mcp", "application/json", strings.NewReader(body)) + require.NoError(t, err, "iteration %d", i) resp.Body.Close() - if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK { - t.Fatalf("iteration %d: expected 200 or 202, got %d", i, resp.StatusCode) - } + require.True( + t, + resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusOK, + "iteration %d: expected 200 or 202, got %d", + i, + resp.StatusCode, + ) }As per coding guidelines,
**/*_test.go: Testing: use testify/assert and testify/require.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/streamable_http_test.go` around lines 2861 - 2872, Replace the plain http.Client with one that has a timeout (e.g. client := &http.Client{Transport: &http.Transport{DisableKeepAlives: true}, Timeout: 5*time.Second}) and switch the loop's t.Fatalf checks to testify/require calls so the test fails fast: use require.NoError(t, err, "iteration %d", i) for the POST error and require.Truef(t, resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusOK, "iteration %d: expected 200 or 202, got %d", i, resp.StatusCode) for status validation; import testify/require and time as needed and keep resp.Body.Close().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@server/streamable_http_test.go`:
- Around line 2861-2872: Replace the plain http.Client with one that has a
timeout (e.g. client := &http.Client{Transport:
&http.Transport{DisableKeepAlives: true}, Timeout: 5*time.Second}) and switch
the loop's t.Fatalf checks to testify/require calls so the test fails fast: use
require.NoError(t, err, "iteration %d", i) for the POST error and
require.Truef(t, resp.StatusCode == http.StatusAccepted || resp.StatusCode ==
http.StatusOK, "iteration %d: expected 200 or 202, got %d", i, resp.StatusCode)
for status validation; import testify/require and time as needed and keep
resp.Body.Close().
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0570b2bd-c4f2-420e-832a-01f6d7d7a19f
📒 Files selected for processing (2)
server/streamable_http.goserver/streamable_http_test.go
|
This is actively affecting me right now, so would love to get this merged. Is there anything I can do to help move this along? |
|
This error is blocking us right now: Can this fix be merged soon? |
|
Thanks for confirming the issue, @michaelrios @Shweta-Deshpande! The stack trace you shared matches exactly what this PR addresses. Hopefully a maintainer can take a look soon. |
Summary
Fixes a race condition panic in
handlePostwhen processing notifications (nil responses).When
HandleMessagereturnsnil(e.g. fornotifications/initialized), the handler sent 202 and returned without closing thedonechannel. The background notification pump goroutine kept running and could write to a deadResponseWriter, causing panics like:http: superfluous response.WriteHeader callpanic: nil pointer dereferenceinbufio.(*Writer).FlushThe fix closes the
donechannel under the mutex before returning, matching the pattern already used in the non-nil response path. Also checksupgradedHeaderto avoid a superfluousWriteHeaderif the goroutine already wrote SSE headers.Includes a race-detector test (
TestStreamableHTTPNotificationRace) that reproduces the issue.Fixes #763
Summary by CodeRabbit