Goroutine spawned without context or stop channel

A goroutine is spawned with an infinite loop and no context cancellation, stop channel, or WaitGroup — it will leak when the calling function returns.

goroutine-leak high confidence runtime go

Goroutine spawned without context or stop channel

What this failure means

A goroutine is spawned with an infinite loop and no context cancellation, stop channel, or WaitGroup — it will leak when the calling function returns.

Diagnosis

A goroutine is started with go func() and runs an infinite or long-running loop without any mechanism to stop it. When the parent context or server shuts down, this goroutine keeps running, leaking memory and CPU over time.

Common patterns:

  • A background poller that loops with time.Sleep and no shutdown signal
  • A cache-refresh goroutine that runs forever without a done channel
  • A background worker spawned in init() or main() without lifecycle control

Fix steps

  1. Accept a context.Context parameter and check ctx.Done() in the loop:

    func StartWorker(ctx context.Context) {
        go func() {
            for {
                select {
                case <-ctx.Done():
                    return
                default:
                    doWork()
                    time.Sleep(time.Second)
                }
            }
        }()
    }
    
  2. Alternatively, use a stop channel: stop <- struct{}{} from the caller and case <-stop: return in the goroutine select.

  3. Track goroutines with a sync.WaitGroup so callers can wait for clean shutdown.

  4. For periodic background work, prefer time.NewTicker with a select over time.Sleep in a loop — the ticker can be stopped.

Validation

  • Run faultline inspect . from the repository root and confirm this source finding is absent or intentionally mitigated.
  • Run go test -race ./... to catch races introduced during cleanup.
  • Confirm that the background goroutine exits cleanly when the context is cancelled.

Why it matters

Goroutine leaks accumulate silently. Each leaked goroutine holds its stack and any variables it closes over. In long-running services, hundreds of leaked goroutines can exhaust memory, hold open database connections, or prevent clean shutdown of a Kubernetes pod, causing the process to be force-killed instead of draining gracefully.

Try it locally

make test
rg -n 'go func' .
go test -race ./...
go vet ./...

How Faultline detects it

Use faultline explain goroutine-leak to see the full playbook.

faultline analyze build.log
faultline explain goroutine-leak

Generated from playbooks/bundled/source/goroutine-leak.yaml. Do not edit directly.

Try it on your own failed log

$ faultline analyze failed.log
Want this across every CI run? Faultline Teams tracks recurring failures across all your repos and surfaces patterns in a shared dashboard.