A goroutine panics. Can a recover in main catch it? How do you protect background goroutines?
Question 307HardGo 1.22 to 1.25
No. Panics are per goroutine. A panic unwinds only its own goroutine's stack. If nothing in that goroutine recovers, the whole process crashes, however many recovers sit in other goroutines. This is a common production outage: a handler starts go work(), work dereferences nil, and the server dies.
func main() {
defer func() { fmt.Println("main recovered:", recover()) }() // never runs for the child's panic
go func() { panic("boom") }()
time.Sleep(time.Second) // process exits with "panic: boom"
}
// Fix: recover inside each goroutine and report the panic as an error.
func SafeGo(errc chan<- error, fn func() error) {
go func() {
defer func() {
if r := recover(); r != nil {
errc <- fmt.Errorf("panic: %v\n%s", r, debug.Stack())
}
}()
errc <- fn()
}()
}
Because recovery is per goroutine, net/http's per-request recovery does not cover goroutines your handler spawns. Recover only at goroutine boundaries you own, such as worker pools and job runners. Log the stack, and think about whether the process state is still consistent. Sometimes crashing is the safer choice.
More on Error Handling & panics
- Q305When is it appropriate to
panicinstead of returning an error? - Q306What does this print? (Rules of
recover) - Q308What does this print? (Defer order during panic)
- Q309How do you convert a panic into an error returned from a function?
- Q310What happens with
panic(nil)and with re-panicking? How did Go 1.21 change this? - Q311Which runtime failures can't be recovered with
recover?