Help us shape the future of Go! Take the 2026 Go Developer Survey

leakcheck

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2026 License: MIT Imports: 11 Imported by: 0

README

leakcheck

CI Go Reference

A library for monitoring the runtime goroutine stack: wait for goroutines to exit, detect leaks in tests.

  • context.Context first design
  • concurrent leak detection
  • no dependencies
  • handy low-level APIs to extend the library

Provenance

leakcheck is a Satellite module of wand. It is a snapshot of gotrace v0.6.0 (commit b96e976), imported without git history and maintained here by headlesslab; see NOTICE. The module path changed, the exported identifiers did not.

Leak detection attributes goroutines to the test that started them through the runtime's ancestor tracebacks, so the test binary must run with GODEBUG=tracebackancestors=N (N large enough, such as 1000); IgnoreNonChildren panics otherwise.

Usage

import "github.com/headlesslab/leakcheck"

The tests are the documentation: see examples_test.go.

Versioning

v0.x: no breaking change without a major version bump. Releases are cut by hand when the module changes; wand pins each release exactly.

License

MIT, see LICENSE.

Documentation

Overview

Package leakcheck monitors the runtime goroutine stack, to wait for goroutines to exit or to detect leaks in tests.

Index

Examples

Constants

This section is empty.

Variables

View Source
var Backoff = func(t time.Duration) time.Duration {
	const maxSleep = 300 * time.Millisecond
	if t == 0 {
		return time.Microsecond
	}

	t *= 2

	if t > maxSleep {
		return maxSleep
	}

	return t
}

Backoff is the default algorithm for sleep backoff

View Source
var Exit = os.Exit

Exit to os.Exit

View Source
var GetStack = func(all bool) string {
	for i := 1024 * 1024; ; i *= 2 {
		buf := make([]byte, i)
		if n := runtime.Stack(buf, all); n < i {
			return string(buf[:n-1])
		}
	}
}

GetStack of current runtime

View Source
var TraceAncestorsEnabled = regexp.MustCompile(`tracebackancestors=\d+`).MatchString(os.Getenv("GODEBUG"))

TraceAncestorsEnabled returns true if GODEBUG="tracebackancestors=N" is set

Functions

func Check

func Check(timeout time.Duration, ignores ...Ignore) error

Check if there's goroutine leak

func CheckLeak

func CheckLeak(t T, timeout time.Duration, ignores ...Ignore)

CheckLeak reports error if the test is leaking goroutine. Default timeout is 3s. Default ignore is leakcheck.IgnoreNonChildren() .

func CheckMainLeak

func CheckMainLeak(m M, timeout time.Duration, ignores ...Ignore)

CheckMainLeak reports error if goroutines are leaking after all tests are done. Default timeout is 3s. It's powerful but less accurate than Check, if you only use CheckMainLeak it will be hard to tell which test is the cause of the leak.

func CheckWithContext

func CheckWithContext(ctx context.Context, ignores ...Ignore) error

CheckWithContext if there's goroutine leak

func Signal

func Signal(signals ...os.Signal) context.Context

Signal to cancel the returned context, default signal is CTRL+C .

Example
package main

import (
	"fmt"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/headlesslab/leakcheck"
)

func main() {
	// Skip the test for Windows because it can't send signal programatically.
	if runtime.GOOS == "windows" {
		fmt.Println("true")
		return
	}

	go func() {
		traces := leakcheck.Wait(leakcheck.Signal())
		fmt.Println(strings.Contains(traces.String(), "leakcheck_test.ExampleSignal"))
	}()

	time.Sleep(100 * time.Millisecond)

	p, _ := os.FindProcess(os.Getpid())
	_ = p.Signal(os.Interrupt)

	time.Sleep(100 * time.Millisecond)

}
Output:
true

func Timeout

func Timeout(d time.Duration) context.Context

Timeout shortcut for context.WithTimeout(context.Background(), d)

Types

type Ignore

type Ignore func(t *Trace) bool

Ignore returns true to ignore t

func CombineIgnores

func CombineIgnores(list ...Ignore) Ignore

CombineIgnores into one

Example
package main

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/headlesslab/leakcheck"
)

func main() {
	ignore := leakcheck.CombineIgnores(
		leakcheck.IgnoreCurrent(),
		func(t *leakcheck.Trace) bool {
			return strings.Contains(t.Raw, "ExampleCombineIgnores.func2")
		},
	)

	go func() {
		time.Sleep(2 * time.Second)
	}()

	go func() {
		time.Sleep(time.Second)
	}()

	start := time.Now()
	leakcheck.Wait(context.TODO(), ignore)
	end := time.Since(start)

	if time.Second < end && end < 2*time.Second {
		fmt.Println("only waits for the second goroutine")
	}

}
Output:
only waits for the second goroutine

func IgnoreCurrent

func IgnoreCurrent() Ignore

IgnoreCurrent running goroutines

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/headlesslab/leakcheck"
)

func main() {
	ignore := leakcheck.IgnoreCurrent()

	go func() {
		time.Sleep(time.Second)
	}()

	start := time.Now()
	leakcheck.Wait(context.TODO(), ignore)
	end := time.Since(start)

	if end > time.Second {
		fmt.Println("waited for 1 second")
	}

}
Output:
waited for 1 second

func IgnoreFuncs

func IgnoreFuncs(names ...string) Ignore

IgnoreFuncs ignores a Trace if it's first Stack's Func equals one of the names.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/headlesslab/leakcheck"
)

func main() {
	ignoreCurrent := leakcheck.IgnoreCurrent()
	ignore := leakcheck.IgnoreFuncs("internal/poll.runtime_pollWait")

	go func() {
		time.Sleep(time.Second)
	}()

	start := time.Now()
	leakcheck.Wait(context.TODO(), ignore, ignoreCurrent)
	end := time.Since(start)

	if end > time.Second {
		fmt.Println("waited for 1 second")
	}

}
Output:
waited for 1 second

func IgnoreList

func IgnoreList(list Traces) Ignore

IgnoreList of traces

func IgnoreNonChildren

func IgnoreNonChildren() Ignore

IgnoreNonChildren goroutines

type M

type M interface {
	Run() int
}

M interface for testing.M

type Stack

type Stack struct {
	Func string
	Loc  string
}

Stack info

type T

type T interface {
	Helper()
	Fail()
	Failed() bool
	Cleanup(f func())
	Logf(format string, args ...interface{})
}

T interface for testing.T

type Trace

type Trace struct {
	Raw                  string
	GoroutineID          int64
	GoroutineAncestorIDs []int64 // Need GODEBUG="tracebackancestors=N" to be set
	WaitReason           string  // https://github.com/golang/go/blob/874b3132a84cf76da6a48978826c04c380a37a50/src/runtime/runtime2.go#L997
	Stacks               []Stack
	// contains filtered or unexported fields
}

Trace of one goroutine

func (Trace) HasParent

func (t Trace) HasParent(id int64) bool

HasParent goroutine id

func (Trace) String

func (t Trace) String() string

String interface for fmt

type Traces

type Traces []*Trace

Traces of goroutines

func Get

func Get(all bool) Traces

Get the Trace of the calling goroutine. If all is true, all other goroutines' Traces will be appended into the result too.

Example
package main

import (
	"fmt"

	"github.com/headlesslab/leakcheck"
)

func main() {
	list := leakcheck.Get(true)

	fmt.Println("id of current:", list[0].GoroutineID)
	fmt.Println("caller of current:", list[0].Stacks[2].Func)

}
Output:

id of current: 1
caller of current: github.com/headlesslab/leakcheck_test.ExampleGet

func Wait

func Wait(ctx context.Context, ignores ...Ignore) (remain Traces)

Wait uses Backoff for WaitWithBackoff

func WaitWithBackoff

func WaitWithBackoff(ctx context.Context, backoff func(time.Duration) time.Duration, ignores ...Ignore) (remain Traces)

WaitWithBackoff algorithm. Wait for other goroutines that are not ignored to exit. It returns the ones that are still active. It keeps counting the active goroutines that are not ignored, if the number is zero return.

func (Traces) Any

func (list Traces) Any() bool

Any item exists in the list

func (Traces) Filter

func (list Traces) Filter(ignore Ignore) Traces

Filter returns the remain Traces that are ignored

func (Traces) String

func (list Traces) String() string

String interface for fmt. It will merge similar trace together and print counts.

Example
package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/headlesslab/leakcheck"
)

func main() {
	go func() {
		time.Sleep(time.Second)
	}()

	traces := leakcheck.Wait(leakcheck.Timeout(0))

	str := fmt.Sprintf("%v %v", traces[0], traces)

	fmt.Println(strings.Contains(str, "leakcheck_test.ExampleTraces_String"))

}
Output:
true

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL