Detecting Goroutine Leaks with Go's Experimental goroutineleak Feature

This article explains how Go's experimental goroutineleak experiment integrates leak detection into the garbage collector, describes the underlying sudog mechanism, and provides step‑by‑step instructions to enable the feature, run a demo, and analyze leaks using pprof.

Radish, Keep Going!
Radish, Keep Going!
Radish, Keep Going!
Detecting Goroutine Leaks with Go's Experimental goroutineleak Feature

Goroutine Leak Problem

In Go, a goroutine leak occurs when a goroutine blocks on a synchronization primitive (e.g., channel, mutex) that becomes permanently unreachable. Each leaked goroutine retains at least 2 KB of stack, causing memory growth, scheduler pressure, and possible OOM.

GC‑Based Leak Detection ( goroutineleak )

Runtime Mechanism

The runtime tracks blocked goroutines with the sudog struct, which stores pointers to the goroutine, the primitive, and wait‑queue links.

Detection Cycle

Special GC pass : an experimental GC cycle includes leak‑detection logic.

Unreachability check : identifies goroutines blocked on primitives that the GC marks as unreachable.

Status marking : such goroutines are marked with the _Gleaked state.

Result exposure : the profile is served at /debug/pprof/goroutineleak and can be inspected with the standard pprof tool.

Using the Feature

1. Install the experimental toolchain

go install golang.org/dl/gotip@latest
gotip download

2. Run a demo program

The program starts a pprof server, creates a goroutine that blocks forever on an unclosed channel, and waits for a termination signal.

func main() {
    go func() {
        log.Printf("pprof server started at http://localhost:6060")
        if err := http.ListenAndServe(":6060", nil); err != nil {
            log.Fatalf("failed to start pprof server: %v", err)
        }
    }()
    createLeakedGoroutine()
    fmt.Println("Demo program running...")
    fmt.Println("Use this command to check for leaks:")
    fmt.Println("  GOEXPERIMENT=goroutineleakprofile gotip tool pprof http://localhost:6060/debug/pprof/goroutineleak")
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
    <-sigCh
    log.Println("Shutting down demo program...")
}

func createLeakedGoroutine() {
    ch := make(chan int)
    go func() {
        fmt.Println("Leaked goroutine started - waiting for channel data")
        <-ch // blocks forever
        fmt.Println("Leaked goroutine should never reach this line")
    }()
    fmt.Println("Created a leaked goroutine - channel is now unreachable")
}

Run with the experiment enabled:

GOEXPERIMENT=goroutineleakprofile gotip run main.go

3. Collect the leak profile

GOEXPERIMENT=goroutineleakprofile gotip tool pprof http://localhost:6060/debug/pprof/goroutineleak

4. Analyze the output

(pprof) top
(pprof) list main.createLeakedGoroutine.func1

The profile shows the blocked goroutine and the source line where it receives from the unreachable channel.

Implications

Unified debugging: use familiar pprof commands without additional tools.

Faster root‑cause identification: the GC automatically pinpoints leaked goroutine locations.

Proactive detection: can be integrated into CI/CD pipelines or run in production.

Deeper runtime insight: illustrates how the GC, sudog structures, and synchronization primitives interact.

Limitations

The experiment only reports leaked goroutines; it does not free them. Developers must fix the underlying cause (e.g., close channels, keep primitives reachable).

References

goroutineleak design proposal : https://go.googlesource.com/proposal/+/master/design/74609-goroutine-leak-detection-gc.md

sudog implementation : https://github.com/golang/go/blob/c58d075e9a457fce92bdf60e2d1870c8c4df7dc5/src/runtime/runtime2.go#L406

debuggingGopprofRuntimegcgoroutine-leakgotip
Radish, Keep Going!
Written by

Radish, Keep Going!

Personal sharing

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.