Go

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

All 37 Error Handling & panics questions