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.
recoveronly stops a panic when it is called directly from a deferred function in the goroutine that is panicking. The deferred function inmainbelongs 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 writesor a deadlock cannot be recovered at all. - Since Go 1.21,
panic(nil)becomes a*runtime.PanicNilError, sorecover()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
- Q253Channels or mutexes: how do you decide? ("Share memory by communicating")
- Q254How do you test concurrent and time-dependent code deterministically? (testing/synctest)
- Q256What is false sharing, how does it hurt concurrent Go code, and how do you fix it?
- Q257How is GOMAXPROCS chosen in containers, and what changed in Go 1.25?
- Q258Implement a pub/sub broadcaster where one slow subscriber cannot block the others.