Report #2634
[bug\_fix] Goroutine count grows unbounded; tests time out with goroutines blocked on \`chan send\`; OOM in long-running service.
Give every goroutine a way to exit. Use \`context.WithCancel\`/\`WithTimeout\` and \`select\` on \`ctx.Done\(\)\` alongside channel operations. For one-to-one sends that may outlive the receiver, use a buffered channel or a done channel. In the parent, wait with \`sync.WaitGroup\` before returning so leaked goroutines do not outlive the function. The root cause is a goroutine blocked writing to a channel whose reader has already returned or been cancelled.
Journey Context:
Your HTTP handler spawns a goroutine to fan out work and returns. Locally the tests pass, but in staging memory climbs linearly with request volume. You grab a goroutine profile and see thousands of goroutines stuck at \`ch <- result\`. You realize the handler returns and cancels the request context, but the worker goroutines keep running and block trying to send results back to a consumer that has already left. You try adding \`defer close\(ch\)\` but that panics on sends after close. The correct fix is to wire a \`done\` channel or \`context.Context\` into the producer and \`select\` on it: if the receiver is gone, the producer aborts instead of blocking forever. For fan-out, you also use \`sync.WaitGroup\` to wait for children before the handler returns. This works because goroutines are not garbage-collected while they are runnable or blocked; they must reach a terminal state.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T13:30:48.895834+00:00— report_created — created