blob: 64b535f51c87efaa6bf9fc1590d1f90a67dbc237 [file] [log] [blame]
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"io"
"os"
"runtime"
"runtime/pprof"
"sync"
"time"
)
const spawnGCMaxDepth = 5
func init() {
register("SpawnGC", SpawnGC)
register("DaisyChain", DaisyChain)
}
func spawnGC(i int) {
prof := pprof.Lookup("goroutineleak")
if i == 0 {
return
}
wg := &sync.WaitGroup{}
wg.Add(i + 1)
go func() {
wg.Done()
<-make(chan int)
}()
for j := 0; j < i; j++ {
go func() {
wg.Done()
spawnGC(i - 1)
}()
}
wg.Wait()
runtime.Gosched()
if i == spawnGCMaxDepth {
prof.WriteTo(os.Stdout, 2)
} else {
// We want to concurrently trigger the profile in order to concurrently run
// the GC, but we don't want to stream all the profiles to standard output.
//
// Only output the profile for the root call to spawnGC, and otherwise stream
// the profile outputs to /dev/null to avoid jumbling.
prof.WriteTo(io.Discard, 2)
}
}
// SpawnGC spawns a tree of goroutine leaks and calls the goroutine leak profiler
// for each node in the tree. It is supposed to stress the goroutine leak profiler
// under a heavily concurrent workload.
func SpawnGC() {
spawnGC(spawnGCMaxDepth)
}
// DaisyChain spawns a daisy-chain of runnable goroutines.
//
// Each goroutine in the chain creates a new channel and goroutine.
//
// This illustrates a pathological worstcase for the goroutine leak GC complexity,
// as opposed to the regular GC, which is not negatively affected by this pattern.
func DaisyChain() {
prof := pprof.Lookup("goroutineleak")
defer func() {
time.Sleep(time.Second)
prof.WriteTo(os.Stdout, 2)
}()
var chain func(i int, ch chan struct{})
chain = func(i int, ch chan struct{}) {
if i <= 0 {
go func() {
time.Sleep(time.Hour)
ch <- struct{}{}
}()
return
}
ch2 := make(chan struct{})
go chain(i-1, ch2)
<-ch2
ch <- struct{}{}
}
// The channel buffer avoids goroutine leaks.
go chain(1000, make(chan struct{}, 1))
}