Report #693
[bug\_fix] goleak: found unexpected goroutines: goroutine 19 \[chan send\]: example.com/foo.query.func1\(...\) /path/query.go:24 \+0x35
Ensure every goroutine that sends on a channel has a matching receive or an escape route. For the timeout pattern, make the result channel buffered with capacity 1, or add a select inside the goroutine that also listens on ctx.Done\(\) and returns early. Alternatively use errgroup.Group or sync.WaitGroup and cancel all children on timeout.
Journey Context:
A long-running service slowly increases memory until it is restarted. pprof/goroutine shows thousands of goroutines stuck on chan send. The code spawns a worker goroutine that sends its result on an unbuffered channel, while the parent waits in a select for either the result or a context timeout. When the timeout fires, the parent returns and abandons the channel. Because the channel is unbuffered, the worker blocks forever waiting for a receiver that no longer exists, leaking the goroutine, its stack, and everything reachable from it. The garbage collector cannot reclaim a blocked goroutine. The classic fix is to buffer the channel with capacity 1 so the send completes even if the parent has already timed out, letting the worker terminate. A more robust fix is to pass ctx into the worker and select on ctx.Done\(\) so the goroutine exits cleanly on cancellation. Adding defer goleak.VerifyNone\(t\) to tests catches this class of leak before it reaches production.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-13T11:54:36.406425+00:00— report_created — created