Panic Handling
The panics
package provides low-level utilities for catching and managing panics. While most conc
components like WaitGroup
and Pool
have panic handling built-in, you can use the panics
package directly if you are managing your own goroutines and need a structured way to deal with panics.
panics.Catcher
The central type is panics.Catcher
. It is used to catch the first panic that occurs across multiple goroutines.
Usage
- Create a
panics.Catcher
. - Wrap the code that might panic in a call to
catcher.Try(f func())
. - After all calls to
Try
are complete, check for a panic withRecovered()
or propagate it withRepanic()
.
import "github.com/sourcegraph/conc/panics"
var pc panics.Catcher
i := 0
pc.Try(func() { i += 1 })
// This goroutine will panic
pc.Try(func() { panic("abort!") })
pc.Try(func() { i += 1 })
// Recovered() gets the captured panic.
rc := pc.Recovered()
fmt.Println(i) // Output: 2
fmt.Println(rc.Value) // Output: abort!
Propagating Panics with Repanic()
If you simply want to re-throw a captured panic in the calling goroutine (a common pattern in conc
), use Repanic()
.
var pc panics.Catcher
pc.Try(func() { panic("mayday!") })
// This will panic with the recovered value
pc.Repanic()
panics.Recovered
When a panic is caught, it is stored in a panics.Recovered
struct. This struct contains not only the original panic value but also stack trace information, which is invaluable for debugging.
// Recovered is a panic that was caught with recover().
type Recovered struct {
// The original value of the panic.
Value any
// The caller list as returned by runtime.Callers when the panic was
// recovered.
Callers []uintptr
// The formatted stacktrace from the goroutine where the panic was recovered.
Stack []byte
}
Handling Panics as Errors
Sometimes it's useful to treat a panic as a regular error. The AsError()
method converts a *Recovered
into an error
.
If the original panic value was an error
, it can be unwrapped using errors.Unwrap
.
helper := func() error {
var pc panics.Catcher
pc.Try(func() { panic(errors.New("critical error")) })
return pc.Recovered().AsError()
}
err := helper()
if err != nil {
// err.Error() includes the full stack trace.
fmt.Println(err.Error())
// We can unwrap the original error.
cause := errors.Unwrap(err)
fmt.Println(cause) // Output: critical error
}
panics.Try
For the simple case of running a single function and catching a potential panic, you can use the panics.Try
helper function. It's a convenient shorthand for creating a Catcher
and calling Try
once.
import "github.com/sourcegraph/conc/panics"
// No panic
recovered := panics.Try(func() {})
// recovered is nil
// With panic
recovered = panics.Try(func() { panic("oh no") })
// recovered is not nil
fmt.Println(recovered.Value) // Output: oh no