Go Channel Deadlock: Fatal Error "All Goroutines Are Asleep"
Unlike a panic, "all goroutines are asleep - deadlock!" is a fatal error the Go runtime itself detects when literally every goroutine in the program is blocked waiting on something, with no other goroutine left running that could ever unblock any of them — the program can't make progress, so the runtime terminates it immediately rather than hanging forever silently.
The Problem
The program crashes immediately (or after some processing) with:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/app/main.go:8 +0x25
This is distinct from a regular panic — it's specifically the Go runtime's own deadlock detector recognizing that the entire program has ground to a permanent halt.
Why It Happens
This fatal error appears specifically when the main goroutine (or all goroutines collectively) are blocked on channel operations, and no other goroutine exists that could ever perform the matching operation to unblock them. Common causes:
- Sending on, or receiving from, an unbuffered channel with no other goroutine ever launched to perform the matching operation — the single goroutine in the program blocks on a channel operation that, by definition, requires a second party to complete
- A goroutine that was supposed to send or receive was never actually started (a missing
gokeyword, or a conditional that skipped launching it under the specific conditions that occurred) - Every goroutine in the program is simultaneously blocked waiting on channels that only each other could unblock, forming a genuine circular wait with no goroutine left free to break the cycle
- A
sync.WaitGroupwhose counter never reaches zero, because a correspondingDone()call is missing on some code path (particularly an early return or an error path that skips it), causingWait()to block forever with nothing left running that could ever call the missingDone()
The Fix
For the simplest case — a single goroutine trying to send or receive on an unbuffered channel alone — confirm you actually launched the goroutine meant to be on the other end of that channel operation:
ch := make(chan int)
// This blocks forever with nothing to receive it - deadlock
ch <- 1
// Correct: launch a goroutine to actually receive
go func() {
value := <-ch
fmt.Println(value)
}()
ch <- 1
If you specifically don't need a synchronous handoff between two goroutines, using a buffered channel avoids this particular flavor of deadlock, since a send to a buffered channel with available capacity doesn't require a receiver to be ready at that exact moment:
ch := make(chan int, 1) // buffered - send succeeds immediately without a waiting receiver
ch <- 1
For sync.WaitGroup-related deadlocks, audit every code path following Add() to confirm Done() is guaranteed to run, typically via defer immediately after the goroutine starts, so it fires regardless of how that goroutine's function actually exits:
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done() // guaranteed to run even if the function below panics or returns early
doWork()
}()
wg.Wait()
For more complex deadlocks involving multiple goroutines each waiting on the others, use select with a timeout as a diagnostic tool (not necessarily the final fix, but useful for identifying which specific operation is actually stuck) to at least prevent the fatal crash while you investigate further:
select {
case value := <-ch:
fmt.Println(value)
case <-time.After(5 * time.Second):
fmt.Println("timed out waiting - this operation is the one that's stuck")
}
Replacing a blocking operation with this pattern temporarily during debugging often quickly identifies which specific channel operation in a larger, more complex program is the one that never gets its match, without needing to inspect the entire goroutine graph manually.
Still Not Working?
If the deadlock only appears under specific conditions rather than consistently, and the stack trace alone isn't enough to pinpoint the cause, use Go's built-in blocking profiler to capture a full picture of every goroutine's state at the moment of the deadlock, which is significantly more informative than the single goroutine stack trace shown in the fatal error output alone:
import _ "net/http/pprof"
// then, right before or during the hang: visit
// http://localhost:6060/debug/pprof/goroutine?debug=2
This dumps every currently running goroutine's full stack trace simultaneously, making it possible to see the complete picture of which goroutines are waiting on which channels or locks, rather than only the single goroutine the runtime happened to report in the initial fatal error message.