You have to "inject" some code into the function that is launched as a new goroutine: you have to call a deferred function in which you call recover()
. This is the only way to recover from a panicing state. See related: Why does `defer recover()` not catch panics?
For example:
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}()
panic("catch me")
}()
This will output (try it on the Go Playground):
Caught: catch me
It is unfeasible to do this in every goroutine you launch, but of course you can move the recovering-logging functionality to a named function, and just call that (but deferred of course):
func main() {
go func() {
defer logger()
panic("catch me")
}()
time.Sleep(time.Second)
}
func logger() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}
This will output the same (try it on the Go Playground).
Yet another, more convenient and even more compact solution is to create a utility function, a "wrapper" which receives the function, and takes care of the recovering.
This is how it could look like:
func wrap(f func()) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}()
f()
}
And now using it is even simpler:
go wrap(func() {
panic("catch me")
})
go wrap(func() {
panic("catch me too")
})
It will output (try it on the Go Playground):
Caught: catch me
Caught: catch me too
Final note:
Note that launching an actual goroutine happens outside of wrap()
. This gives the caller the option to decide if a new goroutine is required just by prefixing the wrap()
call with go
. Usually this approach is preferred in Go. This allows you to execute arbitrary functions by passing them to wrap()
, and it will "protect" its execution (by recovering from panics, properly logging / reporting it) even if you do not wish to run it concurrently in a new goroutine. On the other hand if you'd move go
inside wrap()
it wouldn't even work anymore as the recover()
call would not happen on the panicking goroutine.