Skip to content

Fix listener resource leaks in serve commands#1945

Merged
dgageot merged 1 commit intodocker:mainfrom
dgageot:board/docker-agent-issues-cb91df7b
Mar 6, 2026
Merged

Fix listener resource leaks in serve commands#1945
dgageot merged 1 commit intodocker:mainfrom
dgageot:board/docker-agent-issues-cb91df7b

Conversation

@dgageot
Copy link
Member

@dgageot dgageot commented Mar 5, 2026

Refactor listenAndCloseOnCancel into newListener, which returns a cleanup function alongside the listener. Callers now defer cleanup(), ensuring the listener is always closed on return — whether the context was cancelled or the downstream Serve/Run call returned an error.

Changes:

  • Replace the raw goroutine with context.AfterFunc, eliminating a potential goroutine leak when the server exits before context cancellation.
  • The cleanup function calls stop() (to cancel the context callback if it hasn't fired) then closes the listener.
  • All three callers (mcp.go, api.go, a2a.go) now use the same defer cleanup() pattern.

Fixes #1934
Fixes #1935

@dgageot dgageot requested a review from a team as a code owner March 5, 2026 18:30
Copy link

@docker-agent docker-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Assessment: 🟢 APPROVE

This PR successfully fixes the listener resource leaks by refactoring listenAndCloseOnCancel into newListener with a proper cleanup function pattern. The implementation correctly uses context.AfterFunc and its returned stop function to ensure proper synchronization and prevent resource leaks.

Key improvements:

  • ✅ Replaces raw goroutine with context.AfterFunc, eliminating goroutine leak potential
  • ✅ Cleanup function properly calls stop() first to prevent callback execution
  • ✅ All callers consistently use defer cleanup() pattern
  • ✅ The stop() function provides the necessary synchronization to prevent race conditions

Analysis:
The pattern of calling stop() followed by ln.Close() is correct and thread-safe. The stop() function from context.AfterFunc ensures that either the callback hasn't run (and won't run), or it already ran. This eliminates any race condition concerns around double-closing the listener.

No bugs found in the changed code. The refactoring is clean and follows Go best practices for resource cleanup with context cancellation.


Note: This review analyzed only the added/modified code in this PR.

Rename listenAndCloseOnCancel to newListener and have it return a
cleanup function alongside the listener. Callers now defer the cleanup
function, which ensures the listener is always closed on return,
regardless of whether the context was cancelled or the downstream
Serve/Run call returned an error.

Previously, the helper only closed the listener on context cancellation
(via a goroutine), leaving a resource leak if the server returned an
error before the context was cancelled. The callers had to remember to
add their own defer ln.Close() — a responsibility that was easy to
miss. Now the cleanup is centralized in newListener.

This also fixes a2a.go which had the same missing defer ln.Close().

Fixes docker#1934
Fixes docker#1935

Assisted-By: cagent
@dgageot dgageot force-pushed the board/docker-agent-issues-cb91df7b branch from cc74347 to cb7b342 Compare March 6, 2026 06:34
@dgageot dgageot merged commit d71d15d into docker:main Mar 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Potential resource leak due to unclosed listener in mcp.go [bug] Potential resource leak due to unclosed listener in api.go

2 participants