Skip to content
gohacks.dev
Concurrency

Fatal Error All Goroutines Are Asleep Deadlock Explained

Fix fatal error all goroutines are asleep deadlock in Go: the causes with reproductions, how to read the trace, and why partial deadlocks are never reported.

7 min read
Summarize in AI
Flat illustration of one flow fanning out into parallel lanes of goroutines and joining back.

This message ends Go programs quickly and explains itself well once you know how to read it. The runtime keeps count of goroutines that are able to run. When that count drops to zero and nobody is waiting on a timer or the network, the program can never make progress again, and the runtime says so and exits with status 2. It is a mercy: most hangs give no message at all.

Tested with Go 1.25.6, macOS on Apple Silicon (arm64)

This page reproduces the common causes, shows the trace in each case, and then covers the dangerous variant, the deadlock that is never reported. It belongs to the concurrency material and relates to channels, WaitGroups and mutexes. Everything ran on Go 1.25.6, and the traces are real output.

The smallest case

main.goGo
package main

import "fmt"

func main() {
	ch := make(chan int)
	ch <- 1 // nobody is receiving
	fmt.Println("never reached")
}
Text
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	example.test/lab/main.go:7 +0x34
exit status 2

An unbuffered send waits for a receiver. There is only one goroutine, and it is the one waiting. The output names the state: goroutine 1 [chan send]: followed by the line of the send. That bracketed text is the most useful part of the trace.

Reading the trace

The runtime prints the fatal error, a blank line, and then every goroutine with its state in brackets. The state says what the goroutine is waiting on:

StateThe goroutine is waiting on
chan senda send on a channel that has no receiver ready
chan receivea receive on a channel with nothing to deliver
chan receive (nil chan) / chan send (nil chan)an operation on a nil channel, which blocks forever
select / select (no cases)a select where no case can proceed; select {} has no cases at all
sync.WaitGroup.Waita WaitGroup whose counter never reaches zero
sync.Mutex.Locka mutex held by someone who will never release it
semacquireother sync primitives, in older Go versions and some types

Below the state is the call stack, with the file and line of the blocked operation. Find it in your source and ask the question: what would have to happen for this to proceed, and can that happen? If the answer is "another goroutine would have to send", look for that goroutine in the trace; it is either missing, blocked itself, or has exited.

Cause 1: a send or receive with no partner

The basic case, in its realistic forms. A worker sends results and nobody collects them, or a main loop waits for a result from a goroutine that returned early:

main.goGo
package main

import "fmt"

func main() {
	results := make(chan int)

	go func() {
		results <- 42
	}()

	fmt.Println(<-results)
	fmt.Println(<-results) // a second receive, but only one value was ever sent
}
Text
42
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
	example.test/lab/main.go:13 +0xc4
exit status 2

The first receive succeeds. The second waits for a send that will never come, and the goroutine has exited, so nothing is left to run. The state is chan receive. The fix is to match sends and receives: count how many values are produced and read exactly that many, or close the channel from the sender and range over it.

Cause 2: a WaitGroup that never reaches zero

wg.Wait() blocks until the counter hits zero. If a goroutine returns without Done, or Add was called more times than there are Dones, the wait never ends:

main.goGo
package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func() {
			if i == 1 {
				return // forgot wg.Done() on this path
			}
			defer wg.Done()
			fmt.Println("done", i)
		}()
	}
	wg.Wait()
}
Text
done 2
done 0
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [sync.WaitGroup.Wait]:
sync.runtime_SemacquireWaitGroup(0x1025b7ff0?, 0x30?)
	runtime/sema.go:114 +0x38
sync.(*WaitGroup).Wait(0x14000114020)
	sync/waitgroup.go:206 +0xa8
main.main()
	example.test/lab/main.go:20 +0xa8
exit status 2

One goroutine exits without decrementing the counter, so Wait blocks (the trace shows sync.WaitGroup.Wait) and the runtime reports the deadlock. The fix is to put defer wg.Done() first in the function, before any early return, or to use wg.Go, which pairs the two for you (WaitGroup.Go and the WaitGroup guide). The order in the demo, defer after a conditional return, is a real-world way to write this bug.

Cause 3: ranging over a channel that is never closed

for v := range ch ends only when ch is closed. If the producer finishes and forgets to close, the consumer waits forever:

main.goGo
package main

import "fmt"

func main() {
	ch := make(chan int)
	go func() {
		for i := 1; i <= 3; i++ {
			ch <- i
		}
		// missing: close(ch)
	}()

	for v := range ch {
		fmt.Println(v)
	}
	fmt.Println("done")
}
Text
1
2
3
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
	example.test/lab/main.go:14 +0xb8
exit status 2

The three values print, then the loop waits for a fourth, the producer has exited, and the runtime reports the deadlock. The fix is defer close(ch) in the producer (who closes).

Cause 4: locking a mutex twice

sync.Mutex is not reentrant. A goroutine that locks and then calls something that locks the same mutex waits on itself:

main.goGo
package main

import (
	"fmt"
	"sync"
)

type Counter struct {
	mu sync.Mutex
	n  int
}

func (c *Counter) Inc() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.n++
}

func (c *Counter) IncTwice() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.Inc() // locks the same mutex again
	c.Inc()
}

func main() {
	var c Counter
	c.IncTwice()
	fmt.Println(c.n)
}
Text
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [sync.Mutex.Lock]:
internal/sync.runtime_SemacquireMutex(0x14000110e88?, 0x6c?, 0x100583160?)
	runtime/sema.go:95 +0x28
internal/sync.(*Mutex).lockSlow(0x14000108020)
	internal/sync/mutex.go:149 +0x170
internal/sync.(*Mutex).Lock(...)
	internal/sync/mutex.go:70
sync.(*Mutex).Lock(...)
	sync/mutex.go:46
main.(*Counter).Inc(0x14000108020)
	example.test/lab/main.go:14 +0x74
main.(*Counter).IncTwice(0x14000108020)
	example.test/lab/main.go:22 +0x98
main.main()
	example.test/lab/main.go:28 +0x2c
exit status 2

IncTwice holds the lock and calls Inc, which tries to take it. The goroutine is blocked on itself, and the runtime reports fatal error: all goroutines are asleep - deadlock! with the state sync.Mutex.Lock. The fix is to split each method into a locking public one and a non-locking internal one (inc), which the public ones call while holding the lock (mutexes shows the pattern).

Cause 5: an empty select, or only a nil channel

select {} blocks forever by design, and in a program where nothing else runs, the runtime treats that as a deadlock. The same happens with a receive from a nil channel. Programs sometimes use select {} at the end of main to wait for background goroutines, which works as long as one of them keeps running and is a trap when they all finish.

The dangerous version: the deadlock that is not reported

The detector fires only when every goroutine is blocked and there is nothing that could wake one. If any goroutine can still run, or if any is waiting on a timer or network event, the runtime cannot prove a deadlock and says nothing:

main.goGo
package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	stuck := make(chan int)

	go func() {
		<-stuck // blocked forever
	}()

	go func() {
		for { // a goroutine that keeps the program "alive"
			time.Sleep(10 * time.Millisecond)
		}
	}()

	go func() {
		time.Sleep(100 * time.Millisecond)
		fmt.Println("still running, no deadlock was reported; exiting by hand")
		os.Exit(0)
	}()

	<-stuck // main is also stuck
}
Text
still running, no deadlock was reported; exiting by hand

Main and one goroutine are stuck forever, but a timer-driven goroutine is alive, so the runtime does not report anything. Without the os.Exit this program would hang until killed. This is the reality of production services: they always have goroutines running (the HTTP server, tickers, metrics), so a deadlocked subsystem never triggers the fatal error. It shows up as requests that hang, queues that stop draining or a health check that times out.

For those, the tools are different. Send SIGQUIT to dump all goroutine stacks, or fetch /debug/pprof/goroutine?debug=2 from a service that exposes pprof, and look for many goroutines in the same blocked state on the same line (goroutine dumps). The block and mutex profiles show where goroutines wait (mutex and block profiles), and a context deadline on every blocking operation converts a silent hang into an error you can log.

Preventing deadlocks

  1. Give every blocking operation a way out. select with ctx.Done() or a timeout around channel operations.
  2. Close what you range over, from the sender, with defer.
  3. Pair Add and Done mechanically: wg.Go, or defer wg.Done() as the first line.
  4. Keep lock scopes small and never call out to unknown code while holding a lock.
  5. Take multiple locks in a fixed order, because two goroutines that take A then B and B then A can deadlock each other, and that case is reported only if nothing else is running.
  6. Run tests with -race and a timeout (go test -timeout 30s). The timeout turns a silent hang into a test failure with a dump of all goroutines.

For the runtime's own description of the error, see the runtime package documentation and the Go blog's Go Concurrency Patterns, whose rules about channel ownership prevent most of these.

Also inTooling
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.