> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Detecting Goroutine Leaks Like Memory Leaks? goroutineleak Helps Us Pinpoint Goroutine Leaks Faster
- URL: https://huizhou92.com/detecting-goroutine-leaks-like-memory-leaks-goroutineleak-helps-us-pinpoint-goroutine-leaks-faster/
- Published: 2025-11-17T15:07:32.000Z
- Updated: 2025-11-17T15:07:32.000Z
- Description: Spotting Stuck Goroutines Before They Sink Your Service: Using Go’s Experimental goroutineleak + pprof to Treat Goroutine Leaks Like Memory…
- Author: huizhou92
- Tags: #Migrated-1788833207488, #Import 2026-09-08 02:07

#### Spotting Stuck Goroutines Before They Sink Your Service: Using Go’s Experimental `goroutineleak` \+ `pprof` to Treat Goroutine Leaks Like Memory Leaks

### Introduction

In Go development, goroutine leaks are a common but subtle problem. When a goroutine is blocked on some synchronization primitive (such as a channel or mutex), and that primitive becomes permanently unreachable, the goroutine is effectively “leaked.”

Each leaked goroutine occupies at least 2 KB of stack memory. Over time, this can cause:

- Steadily increasing memory usage
- More pressure on the scheduler
- Eventually, OOM crashes or noticeable service performance degradation

Previously, developers typically relied on **manual code review** or **third-party tools** (such as `goleak`) to detect goroutine leaks—both low in efficiency and easy to miss issues.

But Go’s experimental feature [goroutineleak](https://go.googlesource.com/proposal/+/master/design/74609-goroutine-leak-detection-gc.md?ref=huizhou92.com) changes that.

It integrates goroutine leak detection directly into the garbage collector (GC), so we can locate leaking goroutines via `pprof`, almost as easily as we detect memory leaks.

### How GC Detects Goroutine Leaks

#### 1) Lifecycle characteristics of leaked goroutines

A normal goroutine goes through a complete lifecycle: **create → run → exit**.  
A leaked goroutine, on the other hand, gets stuck in a **blocked** state, and the synchronization primitive it’s blocked on has been deemed unreachable by GC.

![](https://cdn-images-1.medium.com/max/800/0*45HazH2m9WW9A6Bo.png)

#### 2) `sudog`: the bridge between goroutines and synchronization primitives

The Go runtime tracks goroutines blocked on synchronization primitives via the [sudog](https://github.com/golang/go/blob/c58d075e9a457fce92bdf60e2d1870c8c4df7dc5/src/runtime/runtime2.go?ref=huizhou92.com#L406) struct. Each `sudog` contains:

- A pointer to the blocked goroutine
- A pointer to the synchronization primitive (channel, mutex, etc.)
- Linked-list information for the wait queue

![](https://cdn-images-1.medium.com/max/800/0*CpbZ-58cHyqCJkID.png)

#### 3) GC-based leak detection workflow

`goroutineleak` extends the GC to perform leak detection. The core workflow looks like this:

![](https://cdn-images-1.medium.com/max/800/0*R6tv4Zx1pPKiK0ni.png)

Key technical points:

- **Special GC cycle**: a GC cycle is triggered with additional leak detection logic
- **Unreachability check**: identify all goroutines blocked on unreachable synchronization primitives
- **State marking**: mark leaked goroutines with the `_Gleaked` state
- **Result exposure**: expose results via the `/debug/pprof/goroutineleak` endpoint in `pprof`

### 3\. Usage: As Simple as `pprof`

#### 1) Environment setup: install `gotip`

Since `goroutineleak` is currently under development and not available in a stable Go release, we can try it out in advance using `gotip`:

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

#### 2) Running a leak detection demo

Let’s write a small demo that intentionally creates a goroutine leak:

```golang
func main() { 
	// Start pprof server to expose goroutineleak profile 
	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) 
		} 
	}() 
	// Create a goroutine leak 
	createLeakedGoroutine() 
	// Keep the program running to allow pprof inspection 
	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") 
	fmt.Println("Press Ctrl+C to exit") 
	// Wait for interrupt signal to gracefully exit 
	sigCh := make(chan os.Signal, 1) 
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 
	<-sigCh 
	log.Println("Shutting down demo program...") 
} 
// createLeakedGoroutine intentionally creates a leaked goroutine 
func createLeakedGoroutine() { 
	// Create a channel but never write to it or close it 
	ch := make(chan int) 
	// Start a goroutine that blocks forever on channel receive 
	go func() { 
		fmt.Println("Leaked goroutine started - waiting for channel data") 
		<-ch // This goroutine will never return 
		fmt.Println("Leaked goroutine should never reach this line") 
	}() 
	// The channel 'ch' is not accessible after this function returns, 
	// so both the channel and the goroutine are leaked 
	fmt.Println("Created a leaked goroutine - channel is now unreachable") 
}
```

Then run:

```bash
# Start the program with leak detection enabled 
GOEXPERIMENT=goroutineleakprofile gotip run main.go
```

#### 3) Using `pprof` to locate leaks

In another terminal, run:

```bash
# Use the dedicated leak detection profile 
GOEXPERIMENT=goroutineleakprofile gotip tool pprof http://localhost:6060/debug/pprof/goroutineleak
```

#### 4) Analyzing `pprof` results

```bash
# View leaked goroutines 
(pprof) top 
Showing nodes accounting for 1, 100% of 1 total 
      flat  flat%   sum%        cum   cum% 
         1   100%   100%          1   100%  runtime.gopark 
         0     0%   100%          1   100%  main.createLeakedGoroutine.func1 
         0     0%   100%          1   100%  runtime.chanrecv 
         0     0%   100%          1   100%  runtime.chanrecv1 
# View the exact leaking code location 
(pprof) list main.createLeakedGoroutine.func1 
Total: 1 
ROUTINE ======================== main.createLeakedGoroutine.func1 in /path/to/goroutineleak_example/main.go 
         1          1 (flat, cum)   100% of Total 
            .          .     27:func createLeakedGoroutine() { 
            .          .     28:	// Create a channel but never write to it or close it 
            .          .     29:	ch := make(chan int) 
            .          .     30: 
            .          .     31:	// Start a goroutine that blocks forever on channel receive 
            .          .     32:	go func() { 
            .          .     33:		fmt.Println("Leaked goroutine started - waiting for channel data") 
         1          1     34:		<-ch // This goroutine will never return 
            .          .     35:		fmt.Println("Leaked goroutine should never reach this line") 
            .          .     36:	}() 
            .          .     37: 
            .          .     38:	// The channel 'ch' is not accessible after this function returns, 
            .          .     39:	// so both the channel and the goroutine are leaked 
            .          .     40:	fmt.Println("Created a leaked goroutine - channel is now unreachable") 
            .          .     41:}
```

Pretty convenient?

### 4\. What This Means for Developers

#### 1) A unified debugging experience

Developers can reuse the `pprof` tooling they already know to detect goroutine leaks—no need to learn new tools or modify application code.

#### 2) Higher debugging efficiency

- Automatically pinpoint the exact code location of leaks
- No need to manually track each goroutine’s lifecycle
- Combine with stack traces to quickly understand the root cause

#### 3) From “reactive firefighting” to “proactive prevention”

- Integrate into CI/CD during development
- Periodically check in production to catch issues early
- Reduce performance degradation and outages caused by leaks

#### 4) Deeper understanding of the Go runtime

By using `goroutineleak`, developers can better understand:

- How the Go runtime manages goroutines
- How synchronization primitives interact with goroutines
- The role of GC in resource management

### Conclusion

The experimental `goroutineleak` feature gives Go developers an efficient, built-in way to detect goroutine leaks, turning a previously hidden problem into something as straightforward to inspect as memory leaks. Although it’s still experimental, it showcases the Go team’s ongoing effort to improve developer experience and service reliability.

For high-performance, highly reliable Go applications, `goroutineleak` is well worth trying—it helps you find problems faster and write more robust code.

> *Note: Right now, goroutineleak can only detect leaked goroutines; it cannot automatically free them. You still need to fix the underlying issues in your code (e.g., closing channels, ensuring synchronization primitives remain reachable, etc.) based on the detection results.*

---

[Analyzing Go 1.26 runtime.free – Instant Memory Recycling Without GC for 2X SpeedGoodbye GC Pauses! Go Explores a “Semi-Manual” Memory Path: From Arena to runtime.free. Dissecting Compiler…![](https://cdn-images-1.medium.com/fit/c/160/160/0*rzQ28FdJ7mQjdSUI)](https://pub.huizhou92.com/analyzing-go-1-26-runtime-free-instant-memory-recycling-without-gc-for-2x-speed-6e42edb709c4?ref=huizhou92.com)

[Go’s Official HTTP/3 Proposal: Why Are We Still Waiting?A Deep Dive into QUIC Implementation Challenges, Community Expectations, and the Future of HTTP/3 in Go![](https://cdn-images-1.medium.com/fit/c/160/160/0*lPiMvbFH60Awkr_J.png)](https://pub.huizhou92.com/gos-official-http-3-proposal-why-are-we-still-waiting-be4e3ef3675a?ref=huizhou92.com)

[Go’s bytes.Buffer.Peek: A 200x Performance Win Hidden in Plain SightHow a 5-line function can save 4GB of memory allocations![](https://cdn-images-1.medium.com/fit/c/160/160/1*XxKgZsUEm9gdiWjCiV_Uvg.png)](https://pub.huizhou92.com/gos-bytes-buffer-peek-a-200x-performance-win-hidden-in-plain-sight-c33bb51006be?ref=huizhou92.com)