Go deadlock — all goroutines are asleep
all goroutines are asleep - deadlock!
Verified against Go spec: Channel types, Go docs: sync.Mutex, Go blog: Share Memory By Communicating · Updated June 2026
> quick_fix
Go's runtime detected that all goroutines are blocked and the program can never make progress. Read the goroutine dump in the error output — each blocked goroutine shows what it's waiting on. The most common cause is an unbuffered channel send with no receiver, or a mutex locked twice by the same goroutine.
// Classic deadlock — send on unbuffered channel with no receiver
ch := make(chan int)
ch <- 42 // blocks forever — no goroutine is reading
// Fix A — use a buffered channel
ch := make(chan int, 1)
ch <- 42 // doesn't block — buffer holds the value
// Fix B — send in a goroutine
ch := make(chan int)
go func() { ch <- 42 }() // goroutine sends
val := <-ch // main receivesWhat causes this error
Go's runtime detects a deadlock when every goroutine is blocked: on a channel send with no receiver, on a channel receive with no sender, waiting to acquire a mutex that will never be released, or in a select with no cases that can proceed. Partial deadlocks (some goroutines still running) are not detected by the runtime and require external tooling like `pprof`.
How to fix it
- 01
step 1
Read the goroutine dump in the panic output
Go prints the stack trace of every goroutine when a deadlock is detected. Each blocked goroutine shows the exact file and line where it's waiting. Find the goroutines waiting on channels or mutexes.
# Deadlock output includes all goroutine stacks: # goroutine 1 [chan receive]: # main.main() # /app/main.go:10 +0x... ← blocked here # goroutine 6 [chan send]: # main.worker() # /app/worker.go:25 +0x... ← blocked here # Enable full goroutine dump on any signal kill -SIGQUIT <pid> # prints all goroutine stacks to stderr - 02
step 2
Fix channel direction — ensure every send has a receiver
Every unbuffered channel send must have a matching receive on the other side in a different goroutine — both sides must be ready simultaneously. If the sender is in main() and there's no goroutine reading, it deadlocks immediately.
// BAD — send and receive both in main() sequentially ch := make(chan int) ch <- 1 // deadlock: main blocks here, never reaches receive val := <-ch // GOOD — send in goroutine, receive in main ch := make(chan int) go func() { ch <- 1 // goroutine sends }() val := <-ch // main receives — works - 03
step 3
Check for double-lock on sync.Mutex
Calling `mu.Lock()` twice from the same goroutine causes a deadlock — Go's `sync.Mutex` is not reentrant. A function that locks, then calls another function that also locks the same mutex, will deadlock.
var mu sync.Mutex func A() { mu.Lock() defer mu.Unlock() B() // B also locks mu — DEADLOCK } func B() { mu.Lock() // blocks forever — A already holds the lock defer mu.Unlock() // ... } // Fix: use sync.RWMutex, or restructure so B is called without the lock held, // or extract the shared logic into an unexported function that assumes the lock is held. - 04
step 4
Use select with a default or context cancellation for non-blocking channel ops
When you want to try sending/receiving on a channel without blocking indefinitely, use `select` with a `default` case or a `context.Done()` case.
// Non-blocking receive select { case val := <-ch: fmt.Println("got:", val) default: fmt.Println("no value ready") } // With context cancellation (prevents goroutine leak) func worker(ctx context.Context, ch <-chan int) { for { select { case val := <-ch: process(val) case <-ctx.Done(): return // clean exit } } }
How to verify the fix
- The program completes without printing 'all goroutines are asleep'.
- Run `go run -race ./...` — the race detector also catches some deadlock patterns.
- Use `pprof` mutex profile to confirm no goroutines are permanently blocked.
Why all goroutines are asleep - deadlock! happens at the runtime level
Go's scheduler is cooperative and preemptive. The runtime tracks goroutine states: running, runnable, blocked. When every goroutine enters a blocked state (waiting on a channel operation or mutex acquisition) and none can ever be unblocked by another goroutine, the runtime detects this via its goroutine count invariant and calls `runtime.throw("all goroutines are asleep - deadlock!")`. This detection only works for complete deadlocks. Partial deadlocks — goroutine leaks — require pprof profiling because some goroutines continue running, making the invariant appear valid.
Common debug mistakes for all goroutines are asleep - deadlock!
- Sending on an unbuffered channel in the same goroutine that would receive — neither operation can proceed.
- Calling Lock() inside a function that's called while the same mutex is held (non-reentrant lock).
- WaitGroup.Wait() called before WaitGroup.Add() in a goroutine — the Add happens concurrently and the Wait returns immediately with a zero count.
- Forgetting to close a done channel or cancel a context — goroutines waiting on it block forever.
- select with only channel cases and no default — if all channels are empty and no goroutine will ever send, permanent block.
When all goroutines are asleep - deadlock! signals a deeper problem
Deadlocks in real Go programs almost always trace back to improper goroutine lifecycle management. Every goroutine spawned must have a defined shutdown mechanism — a `context.Context`, a done channel, or a WaitGroup. Libraries and frameworks that spawn goroutines internally must document how to stop them. The Go community norm is: 'if you start it, you can stop it.' The architectural pattern is context propagation: every long-running goroutine accepts a `context.Context` and respects its cancellation. `errgroup.WithContext` from `golang.org/x/sync/errgroup` handles this idiom cleanly for concurrent workloads.
Editor's take
The Go deadlock error is refreshingly honest compared to what you get in other ecosystems. When Java or Python deadlocks, the program just hangs silently. Go panics, prints every goroutine's stack trace, and tells you exactly where they're all blocked. That directness makes the initial error easier to debug than it sounds.
The deadlock that wastes the most time isn't the obvious 'send with no receiver' — that's caught within seconds of running the code. The expensive one is the production partial deadlock: a goroutine pool where one goroutine leaks because it's waiting on a channel that the sender thinks is already closed, but the close happened before the goroutine started. The program keeps running, the pool shrinks by one goroutine each time this race hits, and after 24 hours of production traffic the pool is empty and throughput drops to zero. No panic, no error log — just a gradual degradation caught only by a throughput alert or a pprof goroutine dump showing 1,000 goroutines all blocked on `chan receive`.
The tool combination for diagnosing this: `go tool pprof http://localhost:6060/debug/pprof/goroutine?debug=2` gives a full text dump of every goroutine and what it's waiting on. Sorting by state shows all the blocked ones immediately. Combined with `curl http://localhost:6060/debug/pprof/mutex` to see mutex contention, these two profiles catch every class of deadlock and near-deadlock in production without requiring a restart. Setting up the pprof HTTP endpoint in your service (`import _ "net/http/pprof"`) takes two lines and is standard practice for production Go services.
By Bikram Nath · Curator · Updated June 2026
Frequently asked questions
Why does Go detect deadlocks when other languages don't?
Go's runtime maintains a count of goroutines that are currently executing vs blocked. When the executing count drops to zero (all goroutines blocked), the runtime knows the program can never make progress and panics with the deadlock message. This only works for full deadlocks — partial deadlocks (goroutine leak, where some goroutines are still running but others are permanently blocked) require external tools like `runtime/pprof`.
What is a goroutine leak and how is it different from a deadlock?
A deadlock is when ALL goroutines are blocked — the program is completely frozen. A goroutine leak is when SOME goroutines are permanently blocked while others continue running. Leaks don't trigger the runtime deadlock detector. They grow memory usage over time and are detected via `pprof`'s goroutine profile: `go tool pprof http://localhost:6060/debug/pprof/goroutine` and looking for goroutines stuck in `chan receive` or `sync.Mutex.Lock`.
Does closing a channel fix a receive deadlock?
Yes. Receiving from a closed channel immediately returns the zero value and `false` for the ok flag. If a goroutine is blocked waiting to receive and the sender closes the channel, the receiver unblocks. This is the standard pattern for signaling goroutine shutdown. Never close a channel from the receiver side — close channels only from the sender.