Goroutine leaks represent the most insidious form of memory degradation in Go backend systems. Unlike unreferenced heap objects, a suspended goroutine retains its entire stack frame, active pointers, and referenced buffers indefinitely, making it completely invisible to garbage collector sweeps.
1. Common Goroutine Leak Anti-Patterns
A goroutine leaks whenever it is blocked waiting for an event that will never materialize:
- Writing to an unbuffered channel whose receiving party exited prematurely due to an error.
- Reading from an unclosed channel where the producer crashed without signaling EOF.
- Performing unbounded network I/O lacking strict timeout deadlines.
leak_prevention.go
// ANTI-PATTERN: Leaks a goroutine on slow downstream
func UnsafeQuery() string {
ch := make(chan string) // Unbuffered
go func() {
res := callExternalService()
ch <- res // BLOCKS FOREVER if caller times out!
}()
select {
case r := <-ch:
return r
case <-time.After(200 * time.Millisecond):
return "timeout"
}
}
// PRODUCTION-SAFE: Buffered channel + Context Cancellation
func SafeQuery(ctx context.Context) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
ch := make(chan string, 1) // Buffer size 1 prevents block
go func() {
res := callExternalService()
select {
case ch <- res:
case <-ctx.Done():
}
}()
select {
case <-ctx.Done():
return "", ctx.Err()
case r := <-ch:
return r, nil
}
}
Advertisement / Sponsored