Goroutine Leaks

Causes: channel send/receive with no counterpart, mutex never unlocked, context never cancelled, infinite loop with no exit condition.

Why harmful: each goroutine holds stack (~2KB min, can grow), captured heap variables (block GC), open fd/connections.

Detection: - runtime.NumGoroutine() as Prometheus metric — monotonically increasing = leak - pprof goroutine profile — goroutines all blocked at same call site = leak - goleak in tests — fails if goroutines remain after test cleanup

Prevention: select on ctx.Done() in every long-running goroutine; close channels to signal completion; sync.WaitGroup for goroutine groups; always defer cancel() after context.WithTimeout/WithCancel.

Worked example: an HTTP handler spawns a goroutine to do slow work and send its result on an unbuffered channel: go func() { result := doWork(); resultChan <- result }(), then the handler does select { case r := <-resultChan: ...; case <-time.After(2*time.Second): return timeoutError }. If doWork() takes longer than 2 seconds, the handler hits the timeout branch and returns — but the spawned goroutine is still running, and when it finally finishes, it blocks forever on resultChan <- result because nothing is ever going to read from that channel again. Every timed-out request leaks exactly one goroutine, permanently parked on that send, still holding its captured result and whatever memory doWork allocated. At 100 timeouts per minute, that's 100 new permanently-blocked goroutines every minute, each retaining memory that GC can never reclaim (the goroutine's stack and captured variables are still reachable — the runtime thinks it might still run). Over a few days this shows up as runtime.NumGoroutine() climbing monotonically on a dashboard, and eventually OOM. The fix is to make the channel buffered with size 1 (resultChan := make(chan T, 1)) so the send never blocks even if no one is listening anymore, or to select on ctx.Done() inside the spawned goroutine so it can abandon the send once the handler has already given up.