Report #2054
[bug\_fix] Goroutine leak: workers or pipeline stages block forever sending on a channel because the consumer exited early or context was cancelled without cancelling the send.
Plumb a done/cancellation channel \(or context.Context\) through every stage and use select with <-ctx.Done\(\) \(or <-done\) on every send and receive. Use defer close\(out\) and defer wg.Done\(\) so all return paths clean up, and verify with go.uber.org/goleak in tests.
Journey Context:
A long-running data processor slowly grows goroutine count until it exhausts memory. pprof shows thousands of goroutines stuck on "ch <- result" inside worker functions. The developer adds logging and discovers that the consumer reads only the first error and returns, leaving producers blocked on an unbuffered channel send forever. They first try buffering the channel, which masks the symptom until a larger input arrives. Reading the Go pipelines article makes them realize goroutines are not garbage collected and a blocked send must be cancelled explicitly. They refactor each stage to accept ctx context.Context, replace every send with select \{ case out <- v: case <-ctx.Done\(\): return \}, and make the top-level caller cancel ctx on error. They add TestMain with goleak.VerifyNone and watch the leak disappear. The fix works because cancellation gives every blocked sender an alternative receive case, allowing the goroutine to exit instead of waiting on a channel that will never be read.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T09:52:30.590727+00:00— report_created — created