Report #104424
[bug\_fix] goroutine leak caused by unbuffered channel send without receiver
Ensure that the channel is buffered \(e.g., \`ch := make\(chan int, 1\)\`\) or that a receiver is always ready before sending. Alternatively, use a select statement with a default case or a context timeout to avoid blocking indefinitely.
Journey Context:
A developer wrote a concurrent worker pool that used an unbuffered channel to pass tasks. The main goroutine sent tasks to the channel, and workers received them. Under low load, it worked fine. Under high load, the main goroutine would block on a send when all workers were busy, causing a goroutine leak because the main goroutine was stuck and not returning. The developer noticed the number of goroutines in the process growing monotonically via pprof. They initially tried to increase the number of workers, but that only delayed the leak. The root cause was the unbuffered channel's synchronous nature: a send blocks until a receive is ready, and if all workers are blocked on previous sends, the system deadlocks. The fix was to use a buffered channel with a capacity large enough to handle bursts \(e.g., \`make\(chan Task, 100\)\`\), and to implement a graceful shutdown with \`context.WithCancel\`. This pattern is well-documented in Go's concurrency patterns.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-08-16T20:05:55.115676+00:00— report_created — created