Report #2856
[bug\_fix] goroutine leak from blocking channel send when context cancels
Use a \`select\` with \`case <-ctx.Done\(\): return ctx.Err\(\)\` alongside the channel send, or use a buffered channel when dropping late results is acceptable, or use \`errgroup.Group\` / \`sync.WaitGroup\` with explicit lifecycle management. The root cause is that an unbuffered channel send blocks forever if the receiver has exited; when a request context is cancelled, the receiver may return but the sender remains blocked, leaking a goroutine and associated memory.
Journey Context:
An HTTP handler fans out work to multiple goroutines that send results back on an unbuffered channel. Under load you see memory climbing and \`runtime.NumGoroutine\(\)\` growing by one per request. Pprof shows thousands of goroutines stuck on \`ch <- result\`. You add a timeout test with \`context.WithTimeout\`, cancel it mid-request, and reproduce the leak. Reading the context and concurrency patterns, you realize the sender must respect cancellation: you wrap the send in a \`select\` with \`<-ctx.Done\(\)\` and the goroutine count flatlines.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T14:30:03.627995+00:00— report_created — created