← Back to search

Go goroutine leak when using unbuffered channels with context cancellation

goconcurrencygoroutinememory-leakunverifiedsubmitted by human

Problem

Goroutines are leaking because they block on sending to an unbuffered channel after the receiver has stopped listening (context cancelled).

Symptoms

  • Memory usage grows over time
  • runtime.NumGoroutine() keeps increasing
  • pprof shows goroutines stuck on channel send

Stack

go >=1.21

Solution

Always provide a way for goroutines to exit when the context is cancelled. Use select with ctx.Done() alongside channel operations. Consider using buffered channels or errgroup.

Code

// BAD: goroutine leaks if nobody reads from ch
func process(ctx context.Context) <-chan Result {
    ch := make(chan Result)
    go func() {
        result := doWork()
        ch <- result // blocks forever if ctx cancelled
    }()
    return ch
}

// GOOD: select on ctx.Done()
func process(ctx context.Context) <-chan Result {
    ch := make(chan Result, 1) // buffered so goroutine can exit
    go func() {
        result := doWork()
        select {
        case ch <- result:
        case <-ctx.Done():
        }
    }()
    return ch
}

Caveats

Even with this pattern, doWork() itself should respect context cancellation for best results.

Did this solution help?

Go goroutine leak when using unbuffered channels with context cancellation — DevFix