Skip to content

Instantly share code, notes, and snippets.

@gavraz
Last active March 25, 2025 09:39
Show Gist options
  • Select an option

  • Save gavraz/538aa587e4d4073212ccfbe12c4b2b78 to your computer and use it in GitHub Desktop.

Select an option

Save gavraz/538aa587e4d4073212ccfbe12c4b2b78 to your computer and use it in GitHub Desktop.
A reproduction of a leak in go due to a blocked finalizer
package main
import (
"fmt"
"math/rand/v2"
"net/http"
_ "net/http/pprof"
"runtime"
"sync"
"time"
)
type Leaky struct {
data []string
mu sync.Mutex
}
func (r *Leaky) Close() error {
fmt.Println("[Finalizer Locked]")
r.mu.Lock()
fmt.Println("You should not see this message")
return nil
}
//go:noinline
func newLeaky(size int) *Leaky {
z := &Leaky{
data: make([]string, 0, size),
mu: sync.Mutex{},
}
runtime.SetFinalizer(z, (*Leaky).Close)
return z
}
//go:noinline
func objThatWillBlockFinalizer() {
z := newLeaky(0)
runtime.KeepAlive(z)
z.mu.Lock() // block the finalizer
}
//go:noinline
func objWithNormalFinalizer() {
z := newLeaky(1024 * 1024)
runtime.KeepAlive(z)
}
func main() {
go func() {
addr := fmt.Sprintf("0.0.0.0:%d", 6060)
fmt.Printf("Starting PPROF: http://%s/debug/pprof/\n", addr)
fmt.Printf("PPROF stopped: %s\n", http.ListenAndServe(addr, nil).Error())
}()
fmt.Println("Blocking finalizer forever...")
go objThatWillBlockFinalizer()
time.Sleep(1 * time.Second)
// Apparently, this usage causes a leak...
for {
go objWithNormalFinalizer()
runtime.GC()
time.Sleep(10 * time.Millisecond)
if rand.IntN(10) == 0 {
runtime.GC()
runtime.GC()
fmt.Println("Give GC some space to run...")
time.Sleep(5 * time.Second)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment