Go Deadlocks - all goroutines are asleep and How to Fix It
What fatal error all goroutines are asleep - deadlock! means, the five most common causes with runnable fixes, and how to find deadlocks that Go cannot detect.
fatal error: all goroutines are asleep - deadlock!
This is probably the most searched Go concurrency error. It means every goroutine in the program is waiting for something that can never happen: a channel send with no receiver, a receive with no sender, a lock nobody will release, or a WaitGroup that will never reach zero. The runtime notices that nothing can move and stops the program.
Reading the Message
Below the first line, Go prints each stuck goroutine and what it was waiting on:
goroutine 1 [chan send]:
main.main()
/app/main.go:7 +0x36
exit status 2
The word in brackets is the clue:
[chan send]: waiting to send on a channel[chan receive]: waiting to receive[sync.WaitGroup.Wait]: waiting for aWaitGroup(shown assemacquirein some versions)[sync.Mutex.Lock]: waiting for a lock[select]or[select (no cases)]: waiting in aselect
The file and line show exactly where.
Cause 1: Sending on an Unbuffered Channel in the Same Goroutine
ch := make(chan int)
ch <- 1 // waits for a receiver that does not exist yet
fmt.Println(<-ch)
The send waits for a receiver, but the receive is on the next line of the same goroutine, which never gets there. Fix it by sending from another goroutine:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() { ch <- 1 }()
fmt.Println(<-ch)
}
This prints 1.
Cause 2: Ranging Over a Channel That Is Never Closed
for v := range results { ... } // waits forever after the last value
The loop only ends when the channel is closed. Close it after the last send, or after all senders finish:
package main
import (
"fmt"
"sync"
)
func main() {
results := make(chan int)
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
results <- i * i
}()
}
go func() {
wg.Wait()
close(results)
}()
sum := 0
for v := range results {
sum += v
}
fmt.Println("sum:", sum)
}
This prints sum: 14.
Cause 3: Waiting Before Receiving
wg.Wait() // senders are blocked, nobody is receiving yet
for v := range results {}
The senders cannot finish because nobody receives, and Wait cannot return until they finish. The fix is the same as above: move wg.Wait() and close into their own goroutine, or give the channel a buffer large enough for all results.
Cause 4: WaitGroup Counter Never Reaches Zero
Forgetting Done on one path, calling Add too many times, or passing the WaitGroup by value all leave the counter above zero:
func worker(wg sync.WaitGroup) { // copy: Done affects the copy
defer wg.Done()
}
Use defer wg.Done() as the first line of each goroutine and pass *sync.WaitGroup or use a closure. go vet flags the copy with passes lock by value.
Cause 5: Locking a Mutex Twice
mu.Lock()
mu.Lock() // the same goroutine waits for itself
Go mutexes are not reentrant. This usually hides behind one method calling another, both locking. Restructure so only the outer method locks.
Go only detects deadlocks when everything is stuck:
The message appears only when all goroutines are blocked. In a server, the HTTP listener and other background goroutines keep running, so a deadlocked request handler just hangs forever with no error. Those are the hardest deadlocks to find.
Finding Deadlocks Go Cannot Detect
When a program hangs instead of crashing, you can make Go print every goroutine's stack:
- Press Ctrl+\ in the terminal on Linux or macOS (sends
SIGQUIT). The program exits and prints all goroutines with their wait reasons. - In a server, import
net/http/pprofand open/debug/pprof/goroutine?debug=2to see all goroutines without stopping the program.
Look for many goroutines stuck on the same line, all waiting on chan send or sync.Mutex.Lock.
Timeouts as a safety net:
Waiting with select and a timeout or a context deadline turns a silent hang into an error you can log. It does not fix the bug, but it shows you where it is.
Deadlock-free habits:
Every send has a receiver in another goroutine, every range loop has a sender that closes, Wait never blocks the receiver, and no goroutine locks a mutex it already holds.
Summary
all goroutines are asleep - deadlock! means every goroutine is waiting on something that can never happen, and the bracketed state in the trace tells you what. The usual causes are sends without receivers, unclosed channels in range loops, waiting before receiving, unbalanced WaitGroup counters and double locking. In servers, deadlocks hang silently, so use SIGQUIT or pprof goroutine dumps and timeouts to find them.