Last active
June 18, 2026 20:35
-
-
Save rednafi/4f6fe8545580adfa8f6df2e5ed7b9a0e to your computer and use it in GitHub Desktop.
Go 1.27 goroutine leak profile (golang/go#74609): formats, HTTP, and a failing test. Run with GOEXPERIMENT=goroutineleakprofile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Goroutine leak profile, accepted for Go 1.27 (golang/go#74609). | |
| // Run: GOEXPERIMENT=goroutineleakprofile go run formats.go | |
| package main | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "os" | |
| "runtime" | |
| "runtime/pprof" | |
| ) | |
| func leakSend() { | |
| ch := make(chan int) | |
| go func() { ch <- 1 }() // nobody receives, the send blocks forever | |
| } | |
| func leakRange() { | |
| ch := make(chan int) | |
| go func() { | |
| for range ch { // ch is never closed, the range never ends | |
| } | |
| }() | |
| } | |
| func main() { | |
| leakSend() | |
| leakRange() | |
| runtime.Gosched() // let both goroutines reach their blocked state | |
| p := pprof.Lookup("goroutineleak") | |
| // Gotcha: Count() is 0 until a WriteTo runs the leak-detecting GC cycle. | |
| fmt.Println("Count() before WriteTo:", p.Count()) | |
| var proto bytes.Buffer | |
| p.WriteTo(&proto, 0) // debug=0: gzipped pprof protobuf for `go tool pprof` | |
| fmt.Printf("debug=0: %d gzipped bytes\n", proto.Len()) | |
| fmt.Println("Count() after WriteTo:", p.Count()) | |
| fmt.Println("--- debug=1: leaked goroutines as text ---") | |
| p.WriteTo(os.Stdout, 1) | |
| fmt.Println("--- debug=2: full dump, leaked ones tagged (leaked) ---") | |
| p.WriteTo(os.Stdout, 2) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Fail a test on leaks the way uber-go/goleak does, but from the stdlib. | |
| // Run: GOEXPERIMENT=goroutineleakprofile go test | |
| package main | |
| import ( | |
| "bytes" | |
| "runtime" | |
| "runtime/pprof" | |
| "testing" | |
| ) | |
| // verifyNone mirrors goleak.VerifyNone: fail the test on any leaked goroutine. | |
| func verifyNone(t *testing.T) { | |
| var b bytes.Buffer | |
| p := pprof.Lookup("goroutineleak") | |
| p.WriteTo(&b, 1) // WriteTo runs detection; Count() reads 0 without it | |
| if p.Count() > 0 { | |
| t.Fatalf("leaked goroutines:\n%s", b.String()) | |
| } | |
| } | |
| func TestRun(t *testing.T) { | |
| defer verifyNone(t) // like defer goleak.VerifyNone(t) | |
| ch := make(chan int) | |
| go func() { ch <- 1 }() // the code under test leaks a goroutine | |
| runtime.Gosched() | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Pull leaks from a running service over HTTP. | |
| // Run: GOEXPERIMENT=goroutineleakprofile go run server.go | |
| // Then: curl 'localhost:6060/debug/pprof/goroutineleak?debug=1' | |
| package main | |
| import ( | |
| "net/http" | |
| _ "net/http/pprof" // registers /debug/pprof/goroutineleak | |
| ) | |
| func main() { | |
| ch := make(chan int) | |
| go func() { ch <- 1 }() // leak | |
| http.ListenAndServe("localhost:6060", nil) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment