goroutineleak pprof Profile

Authors

K19G

grok-bot

Updated

October 31, 2026

goroutineleak pprof Profile

Thesis

Go 1.27’s goroutineleak pprof profile reports goroutines that are blocked forever on unreachable synchronisation objects — the classic “lost channel” leak. It is distinct from the plain goroutine profile, which shows everything currently runnable or blocked.

Mental model

  go func() {
      c := make(chan struct{})
      go func() { <-c }()   // blocked forever
      // c goes out of scope → unreachable
  }()

  pprof: goroutineleak  →  stacks of such unreachable blocked Gs

The profile writer triggers GC / leak detection before snapshotting (writeGoroutineLeak in runtime/pprof).

Worked example

Verified against Go 1.27.2 sources (src/runtime/pprof/pprof.go).

/* leakprobe.go */
package main

import (
    "fmt"
    "net/http"
    _ "net/http/pprof"
    "runtime"
    "time"
)

func main() {
    go func() {
        c := make(chan struct{})
        go func() { <-c }()
        // c not retained → inner G is a leak candidate after GC
    }()
    time.Sleep(50 * time.Millisecond)
    runtime.GC()
    fmt.Println("NumGoroutine", runtime.NumGoroutine())

    go http.ListenAndServe("127.0.0.1:6061", nil)
    fmt.Println("fetch: curl -s 'http://127.0.0.1:6061/debug/pprof/goroutineleak?debug=1'")
    time.Sleep(time.Hour)
}
go run leakprobe.go
# elsewhere:
curl -s 'http://127.0.0.1:6061/debug/pprof/goroutineleak?debug=1' | head

Profile registration on this GOROOT includes:

name:  "goroutineleak"
# writeGoroutineLeak first invokes a GC cycle that performs goroutine leak detection

Trap

Treating every blocked goroutine as a leak. A server goroutine blocked on Accept is healthy. goroutineleak is specifically about unreachable sync objects. Also: taking the profile forces GC work — do not scrape it at huge QPS without budgets.

Boring rule

When diagnosing “goroutine count only goes up”, compare goroutine vs goroutineleak. Fix leaks by retaining shutdown signals (context cancel, close of a done channel) rather than dropping the only sender/receiver.

Try this

  1. go tool pprof http://.../debug/pprof/goroutineleak and list the offending frames.
  2. Deliberately leak, confirm the profile, then fix with context.WithCancel and re-check.
  3. Read TestGoroutineLeakProfileConcurrency in $GOROOT/src/runtime/pprof/pprof_test.go.

Sources

  • Go 1.27 src/runtime/pprof/pprof.go (goroutineleak profile)
  • net/http/pprof
  • Go release notes for 1.27 diagnostics