A goroutine wrapper for creating and running panic-safe goroutines and task groups.
The purpose of this package is to provide a simple wrapper function for goroutines which automatically handles panics. Starting a new goroutine without recovering from a possible panic inside it could crash the whole application.
The Go function runs an arbitrary function f in a separate goroutine, recovering from any panic and returning the outcome via a channel.
go get -u github.com/sknr/goroutineInstead of running:
go func() {
panic("Panic raised in goroutine")
}()simply call:
Go(func() {
panic("Panic raised in goroutine")
})to create a panic-safe goroutine.
Functions with multiple input parameters must be wrapped within an anonymous function:
Go(func() {
func(a, b int) {
panic(a+b)
}(21, 21)
})package main
import (
"fmt"
"github.com/sknr/goroutine"
"log"
)
func init() {
// Override the default recover function.
goroutine.SetDefaultRecoverFunc(func(v any, done chan<- error) {
log.Printf("panic recovered: %v", v)
done <- fmt.Errorf("panic in goroutine successfully recovered")
})
}
func main() {
for i := -3; i <= 3; i++ {
err := <-goroutine.Go(func() {
func(a, b int) {
log.Println(a, "/", b, "=", a/b)
}(10, i)
})
if err != nil {
log.Println("error:", err)
}
}
}To override the default recover function globally for new goroutines (created with Go(func()) or New(func())), pass a custom recover function of type RecoverFunc to SetDefaultRecoverFunc:
goroutine.SetDefaultRecoverFunc(func(v any, done chan<- error) {
done <- fmt.Errorf("recovered: %v", v)
})If you need different recover functions for different goroutines, use New and the fluent WithRecover method:
goroutine.New(func() {
func(name string) {
panic(fmt.Sprintln("Hallo", name))
}("Welt")
}).WithRecover(func(v any, done chan<- error) {
log.Printf("Custom recover: %v", v)
done <- fmt.Errorf("custom recovery: %v", v)
}).Go()For coordinating groups of multiple panic-safe goroutines, use goGroup. It tracks tasks using a wait group and executes a registered error listener asynchronously when errors occur.
package main
import (
"fmt"
"github.com/sknr/goroutine"
"log"
)
func main() {
gg := goroutine.NewGoGroup()
// Register a thread-safe listener for errors
gg.ErrorListener(func(err error) {
log.Println("Listener captured error:", err)
})
// Spawn concurrent safe tasks
gg.Go(func() {
log.Println("Running task 1...")
})
gg.Go(func() {
panic("task 2 failed")
})
// Block until all tasks finish
gg.Wait()
}All panic errors returned by this library are wrapped in *goroutine.PanicError. This enables checking the recovered panic value and unwrapping inner errors.
Type-assert the error to *goroutine.PanicError and call Value():
err := <-goroutine.Go(func() {
panic("critical system error")
})
if err != nil {
if panicErr, ok := err.(*goroutine.PanicError); ok {
fmt.Println("Panic value:", panicErr.Value()) // Output: critical system error
}
}If the goroutine panics with a standard error object, PanicError implements Unwrap() error. This allows standard utilities like errors.Is or errors.As to function seamlessly:
targetErr := errors.New("connection failed")
err := <-goroutine.Go(func() {
panic(targetErr)
})
if errors.Is(err, targetErr) {
fmt.Println("Successfully matched unwrapped error!")
}- Gopher by Gophers