Go

What does this print? Can recover in main catch a panic from another goroutine?

Question 255HardGo 1.22 to 1.25
func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("recovered:", r)
		}
	}()
	go func() {
		panic("boom")
	}()
	time.Sleep(time.Second)
	fmt.Println("done")
}

It prints neither line. The process crashes with panic: boom and a goroutine stack trace, and exits with status 2.

  • recover only stops a panic when it is called directly from a deferred function in the goroutine that is panicking. The deferred function in main belongs to a different goroutine.
  • An unrecovered panic in any goroutine kills the whole program. There is no per-goroutine crash isolation, unlike threads in some runtimes.
  • Fatal runtime errors such as concurrent map writes or a deadlock cannot be recovered at all.
  • Since Go 1.21, panic(nil) becomes a *runtime.PanicNilError, so recover() never returns nil for a real panic.

If a goroutine runs code you do not trust, recover at the top of that goroutine and turn the panic into an error:

func SafeGo(errs chan<- error, f func() error) {
	go func() {
		defer func() {
			if r := recover(); r != nil {
				errs <- fmt.Errorf("panic: %v\n%s", r, debug.Stack())
			}
		}()
		if err := f(); err != nil {
			errs <- err
		}
	}()
}

What the interviewer is looking for: that panics are per-goroutine for recover but process-wide in effect. net/http recovers panics in handlers for you, but goroutines you start from a handler get no such protection.

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions