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 a WaitGroup (shown as semacquire in some versions)
  • [sync.Mutex.Lock]: waiting for a lock
  • [select] or [select (no cases)]: waiting in a select

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/pprof and open /debug/pprof/goroutine?debug=2 to 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.