# Goroutine Leaks and Deadlocks — Go Concurrency Patterns

Source: https://www.geekswithgeeks.com/en/go-concurrency/gc-leaks

> Goroutines that never finish, and how to find them.

## Blocked forever

A **goroutine leak** is a goroutine blocked forever, typically on a send nobody receives, a receive from a channel nobody closes, or a wait with no cancellation path. Each leak holds its stack and everything it references, so a service that leaks a few per request slowly runs out of memory. A **deadlock** is a cycle of goroutines waiting for each other (for example, two mutexes taken in opposite orders). The runtime only reports `all goroutines are asleep - deadlock!` when **every** goroutine is blocked; a partial deadlock in a server goes silent. Tools: watch `runtime.NumGoroutine()` as a metric; import `net/http/pprof` and read `/debug/pprof/goroutine?debug=2` to see every goroutine's stack grouped by where it is blocked; in tests, use `go.uber.org/goleak` to fail if goroutines are still running at the end. Prevention: give every goroutine an exit path, select on `ctx.Done()` at every blocking point, and acquire locks in a consistent order.

## A classic leak, its fix, and a leak check in tests

Returning early leaves the sender stuck unless the channel has room.

```go
package fetch

import (
	"context"
	"testing"
	"time"

	"go.uber.org/goleak"
)

// LEAKS: on timeout nobody receives, so the goroutine blocks on send forever.
func leaky(ctx context.Context, work func() string) (string, error) {
	ch := make(chan string)
	go func() { ch <- work() }()
	select {
	case v := <-ch:
		return v, nil
	case <-ctx.Done():
		return "", ctx.Err()
	}
}

// FIXED: a buffer of 1 lets the send complete even if nobody receives.
func fixed(ctx context.Context, work func() string) (string, error) {
	ch := make(chan string, 1)
	go func() { ch <- work() }()
	select {
	case v := <-ch:
		return v, nil
	case <-ctx.Done():
		return "", ctx.Err()
	}
}

func TestFixedDoesNotLeak(t *testing.T) {
	defer goleak.VerifyNone(t) // fails if unexpected goroutines remain

	release := make(chan struct{})
	work := func() string { <-release; return "data" }

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
	defer cancel()
	if _, err := fixed(ctx, work); err == nil {
		t.Fatal("expected a timeout")
	}
	close(release) // work finishes; its send succeeds into the buffer
	// With leaky instead of fixed, the worker would stay blocked and
	// goleak.VerifyNone would report it.
}

// To inspect a running server, import _ "net/http/pprof" and fetch:
//   curl 'http://localhost:6060/debug/pprof/goroutine?debug=2'

```

## Graph the goroutine count

Export `runtime.NumGoroutine()` (most metrics libraries do it for you). A count that rises with traffic and never falls back is the clearest early sign of a leak.

**Quiz:** When does the Go runtime itself report "all goroutines are asleep - deadlock!"?

- [x] Only when every goroutine in the program is blocked
- [ ] Whenever any two goroutines wait on each other
- [ ] Whenever a goroutine blocks for more than a second
- [ ] Whenever a channel is left open at exit

*Answer:* Only when every goroutine in the program is blocked. Partial deadlocks and leaks go unreported, which is why profiles and leak tests matter.
