or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-assertions.mdformat.mdgbytes.mdgcustom.mdgexec.mdghttp.mdgleak.mdgmeasure.mdgstruct.mdindex.mdmatchers.mdtypes-interfaces.md
tile.json

gleak.mddocs/

GLeak Package

Package: github.com/onsi/gomega/gleak

The gleak package provides goroutine leak detection capabilities for Gomega. It helps identify goroutines that remain running after tests complete, which can indicate resource leaks or improper cleanup.

Import

import "github.com/onsi/gomega/gleak"

Functions

Goroutines { .api }

func Goroutines() []Goroutine

Returns information about all current goroutines, including their IDs, states, top functions, creators, and backtraces.

Usage Example:

import (
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

// Take a snapshot of current goroutines
snapshot := gleak.Goroutines()

// Later, check for leaks
Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked(snapshot))

Current { .api }

func Current() Goroutine

Returns information about the current goroutine (the goroutine calling this function), including its ID, state, top function, creator, and backtrace.

Usage Example:

import (
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

// Get current goroutine info
current := gleak.Current()
fmt.Printf("Current goroutine ID: %d\n", current.ID)
fmt.Printf("Top function: %s\n", current.TopFunction)

G { .api }

func G(actual any, matchername string) (Goroutine, error)

Helper function that extracts a Goroutine from the actual value passed to a matcher. This is used internally by gleak matchers but can be useful when writing custom goroutine matchers.

Parameters:

  • actual - The value passed to the matcher (typically a Goroutine or []Goroutine)
  • matchername - The name of the matcher calling this function (for error messages)

Returns:

  • A Goroutine if extraction succeeds
  • An error if the actual value is not a valid goroutine type

Usage Example:

// Custom matcher example
func MyCustomGoroutineMatcher() types.GomegaMatcher {
    return gcustom.MakeMatcher(func(actual any) (bool, error) {
        g, err := gleak.G(actual, "MyCustomGoroutineMatcher")
        if err != nil {
            return false, err
        }
        // Use goroutine g for matching logic
        return g.TopFunction == "mypackage.myFunction", nil
    })
}

IgnoreGinkgoParallelClient { .api }

func IgnoreGinkgoParallelClient()

Configures gleak to ignore Ginkgo parallel client goroutines. This should be called once during test suite setup when using Ginkgo in parallel mode to prevent false positives from Ginkgo's internal goroutines.

This function adds a standard filter to ignore goroutines created by Ginkgo's parallel test execution framework.

Usage Example:

import (
    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

var _ = BeforeSuite(func() {
    // Configure gleak for parallel Ginkgo tests
    gleak.IgnoreGinkgoParallelClient()
})

var _ = Describe("MyTests", func() {
    It("detects leaks without false positives from Ginkgo", func() {
        Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked())
    })
})

HaveLeaked { .api }

func HaveLeaked(ignoring ...any) types.GomegaMatcher

Returns a matcher that succeeds if goroutines remain after filtering out expected goroutines. The matcher automatically filters well-known runtime and testing goroutines using built-in standard filters.

Parameters:

  • ignoring - Optional list of goroutines or filters to ignore. Can be:
    • string - Top function name (shorthand for IgnoringTopFunction)
    • []Goroutine - Slice of goroutines to ignore (shorthand for IgnoringGoroutines)
    • types.GomegaMatcher - Any goroutine filter matcher

Usage Examples:

import (
    "time"
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

// Basic leak detection (using Eventually, not Expect)
Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked())

// With custom timeout and polling
Eventually(gleak.Goroutines).
    WithTimeout(5 * time.Second).
    WithPolling(100 * time.Millisecond).
    ShouldNot(gleak.HaveLeaked())

// Ignore specific top function by name
Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked("foo.bar"))

// Ignore multiple functions
Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked(
    "foo.bar",
    "baz.qux",
))

// Use snapshot-based detection
snapshot := gleak.Goroutines()
DoSomething()
Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked(snapshot))

// Combine multiple filter types
Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked(
    snapshot,
    "mypackage.backgroundWorker",
    gleak.IgnoringInBacktrace("runtime.ensureSigM"),
))

Best Practice:

Always use Eventually with HaveLeaked, not Expect. This allows "pending" goroutines time to properly wind down:

// Correct - allows time for goroutines to terminate
Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked())

// Incorrect - may report false positives
Expect(gleak.Goroutines()).NotTo(gleak.HaveLeaked())

Filter Matchers

Filter matchers identify goroutines that should be ignored during leak detection. Each filter can be used standalone or combined with HaveLeaked.

IgnoringTopFunction { .api }

func IgnoringTopFunction(topfname string) types.GomegaMatcher

Matches goroutines where the topmost function in the backtrace has the specified name.

Format Options:

  • "topfunction-name" - Exact match
  • "topfunction-name..." - Prefix match (one level deeper)
  • "topfunction-name [state]" - Exact match with state requirement

Usage Examples:

import (
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

// Ignore exact top function name
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(gleak.IgnoringTopFunction("foo.bar")),
)

// Ignore with prefix match (matches "foo.bar.baz" but not "foo.bar")
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(gleak.IgnoringTopFunction("foo.bar...")),
)

// Ignore only when in specific state
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(gleak.IgnoringTopFunction("foo.bar [running]")),
)

// Ignore channel operations
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(gleak.IgnoringTopFunction("foo.bar [chan receive]")),
)

IgnoringGoroutines { .api }

func IgnoringGoroutines(goroutines []Goroutine) types.GomegaMatcher

Matches goroutines by their unique IDs. Typically used with snapshots taken before a test to filter out pre-existing goroutines.

Usage Example:

import (
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

// Take snapshot before test
snapshot := gleak.Goroutines()

// Run code that may create goroutines
server := StartServer()
defer server.Stop()

// Verify no leaks (ignoring snapshot goroutines)
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(gleak.IgnoringGoroutines(snapshot)),
)

// Shorthand form (same as above)
Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked(snapshot))

IgnoringInBacktrace { .api }

func IgnoringInBacktrace(substring string) types.GomegaMatcher

Matches goroutines where the specified substring appears anywhere in the backtrace.

Usage Example:

import (
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

// Ignore goroutines with specific function in backtrace
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(gleak.IgnoringInBacktrace("runtime.ensureSigM")),
)

// Ignore goroutines from specific package
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(gleak.IgnoringInBacktrace("myapp/internal/worker")),
)

IgnoringCreator { .api }

func IgnoringCreator(creatorfname string) types.GomegaMatcher

Matches goroutines created by a function with the specified name.

Format Options:

  • "creatorfunction-name" - Exact match
  • "creatorfunction-name..." - Prefix match (one level deeper)

Usage Examples:

import (
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

// Ignore goroutines created by specific function
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(gleak.IgnoringCreator("foo.StartWorker")),
)

// Ignore with prefix match (matches "foo.bar.baz" but not "foo.bar")
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(gleak.IgnoringCreator("foo.bar...")),
)

// Useful for framework goroutines
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(
        gleak.IgnoringCreator("github.com/onsi/ginkgo/v2/internal.(*Suite).runNode"),
    ),
)

Types

Goroutine { .api }

type Goroutine struct {
    ID              uint64 // unique goroutine ID
    State           string // goroutine state, such as "running", "chan receive", etc.
    TopFunction     string // topmost function on goroutine's stack
    CreatorFunction string // name of function creating this goroutine, if any
    BornAt          string // location where goroutine was started; format "file-path:line-number"
    Backtrace       string // goroutine's full backtrace (stack trace)
}

Represents information about a single goroutine.

Goroutine IDs:

  • Unique 64-bit integers assigned by Go runtime
  • Start with 1 for the main goroutine
  • Never reused (monotonically increasing)
  • Gaps may exist due to runtime optimizations

State Values:

Common state strings (without brackets):

  • "idle" - Idle goroutine
  • "runnable" - Ready to run
  • "running" - Currently executing
  • "syscall" - In system call
  • "chan receive" - Blocked on channel receive
  • "chan send" - Blocked on channel send
  • "select" - Blocked in select statement
  • "sleep" - In time.Sleep
  • "copystack" - Stack is being copied
  • "preempted" - Preempted goroutine

State may include additional information:

  • "(scan)" - Goroutine is being scanned by GC
  • "X minutes" - Blocked for X minutes or more
  • "locked to thread" - Locked to OS thread

Usage Example:

import (
    "fmt"
    "github.com/onsi/gomega/gleak"
)

// Inspect current goroutines
goroutines := gleak.Goroutines()
for _, g := range goroutines {
    fmt.Printf("Goroutine %d [%s]: %s\n", g.ID, g.State, g.TopFunction)
    if g.CreatorFunction != "" {
        fmt.Printf("  Created by: %s at %s\n", g.CreatorFunction, g.BornAt)
    }
}

Methods:

func (g Goroutine) String() string

Returns a short textual description without the backtrace.

func (g Goroutine) GomegaString() string

Returns Gomega-formatted representation without the backtrace.

Configuration Variables

ReportFilenameWithPath { .api }

var ReportFilenameWithPath = false

Controls whether leak reports show abbreviated filenames (package name + filename) or full paths.

Default Behavior (false):

foo/bar.go:123

With Full Paths (true):

/home/goworld/coolprojects/mymodule/foo/bar.go:123

Usage Example:

import "github.com/onsi/gomega/gleak"

// Enable full paths in leak reports
gleak.ReportFilenameWithPath = true

// Now leak reports will show complete file paths
Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked())

// Restore default
gleak.ReportFilenameWithPath = false

Complete Usage Example

package mypackage_test

import (
    "testing"
    "time"

    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

func TestMyPackage(t *testing.T) {
    RegisterFailHandler(Fail)
    RunSpecs(t, "MyPackage Suite")
}

var _ = Describe("Service", func() {
    var snapshot []gleak.Goroutine

    BeforeEach(func() {
        // Take snapshot before each test
        snapshot = gleak.Goroutines()
    })

    AfterEach(func() {
        // Verify no goroutines leaked during the test
        Eventually(gleak.Goroutines).
            WithTimeout(2 * time.Second).
            WithPolling(250 * time.Millisecond).
            ShouldNot(gleak.HaveLeaked(snapshot))
    })

    It("processes requests without leaking goroutines", func() {
        service := NewService()
        service.Start()
        defer service.Stop()

        // Test service operations
        service.ProcessRequest("test")

        // Leak detection happens automatically in AfterEach
    })

    Context("with background workers", func() {
        It("ignores expected background goroutines", func() {
            service := NewService()
            service.Start()
            defer service.Stop()

            // Check for leaks, ignoring known background workers
            Eventually(gleak.Goroutines).ShouldNot(
                gleak.HaveLeaked(
                    snapshot,
                    gleak.IgnoringTopFunction("mypackage.(*Service).backgroundWorker"),
                    gleak.IgnoringCreator("mypackage.(*Service).Start"),
                ),
            )
        })
    })
})

Standard Filters

HaveLeaked automatically includes built-in filters for common runtime and testing goroutines:

Ginkgo Framework:

  • IgnoringTopFunction("github.com/onsi/ginkgo/v2/internal.(*Suite).runNode")
  • IgnoringTopFunction("github.com/onsi/ginkgo/v2/internal.(*Suite).runNode...")
  • IgnoringTopFunction("github.com/onsi/ginkgo/v2/internal/interrupt_handler.(*InterruptHandler).registerForInterrupts...")
  • Various Ginkgo creator functions

Go Testing Package:

  • IgnoringTopFunction("testing.RunTests [chan receive]")
  • IgnoringTopFunction("testing.(*T).Run [chan receive]")
  • IgnoringTopFunction("testing.(*T).Parallel [chan receive]")

Go Runtime:

  • IgnoringTopFunction("os/signal.signal_recv")
  • IgnoringTopFunction("os/signal.loop")
  • IgnoringInBacktrace("runtime.ensureSigM")
  • IgnoringInBacktrace("runtime.ReadTrace")

These filters ensure that legitimate framework and runtime goroutines are not reported as leaks.

Advanced Pattern: Combining Filters

import (
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/gleak"
)

// Combine multiple filter strategies
Eventually(gleak.Goroutines).ShouldNot(
    gleak.HaveLeaked(
        // Ignore pre-existing goroutines
        snapshot,

        // Ignore specific top functions
        "myapp.backgroundMonitor",
        "myapp.metricsCollector",

        // Ignore by creator
        gleak.IgnoringCreator("myapp.(*Server).Start"),

        // Ignore anything with specific backtrace content
        gleak.IgnoringInBacktrace("myapp/internal/cache"),

        // Ignore with state specification
        gleak.IgnoringTopFunction("myapp.worker [chan receive]"),
    ),
)

Troubleshooting

Problem: False positives - legitimate goroutines reported as leaks

Solutions:

  1. Use Eventually instead of Expect to allow time for cleanup
  2. Take snapshots before tests and use them with HaveLeaked
  3. Add appropriate filters for known background goroutines
  4. Increase timeout: .WithTimeout(5 * time.Second)

Problem: Unclear which goroutines are leaking

Solutions:

  1. Enable full paths: gleak.ReportFilenameWithPath = true
  2. Examine the Backtrace field of leaked Goroutine objects
  3. Use .String() method on Goroutine for readable output

Problem: Need to debug filter matching

Solutions:

  1. Use individual filter matchers directly with Expect for testing
  2. Print goroutine details: fmt.Printf("%+v\n", gleak.Goroutines())
  3. Inspect specific fields like TopFunction, CreatorFunction, State