https://github.com/stevenacoffman/errgroup
Drop in replacement for errgroup that converts panics to errors
https://github.com/stevenacoffman/errgroup
Last synced: about 1 year ago
JSON representation
Drop in replacement for errgroup that converts panics to errors
- Host: GitHub
- URL: https://github.com/stevenacoffman/errgroup
- Owner: StevenACoffman
- License: mit
- Created: 2021-01-15T19:11:48.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2021-01-15T22:13:29.000Z (over 5 years ago)
- Last Synced: 2025-04-10T16:23:15.574Z (about 1 year ago)
- Language: Go
- Size: 9.77 KB
- Stars: 15
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/neilotoole/errgroup/actions?query=workflow%3AGo)
[](https://goreportcard.com/report/StevenACoffman/errgroup)
[](https://pkg.go.dev/github.com/StevenACoffman/errgroup)
[](./LICENSE)
# StevenACoffman/errgroup
`StevenACoffman/errgroup` is a drop-in alternative to Go's wonderful
[`sync/errgroup`](https://pkg.go.dev/golang.org/x/sync/errgroup) but it converts goroutine panics to errors.
While `net/http` installs a panic handler with each request-serving goroutine,
goroutines **do not** and **cannot** inherit panic handlers from parent goroutines,
so a `panic()` in one of the child goroutines will kill the whole program.
So whenever you use an `sync.errgroup`, with some discipline, you can always remember to add a
deferred `recover()` to every goroutine. This library just avoids that boilerplate and does that for you.
You can [see it in use](https://play.golang.org/p/S8Gmr_sWZIi)
```go
package main
import (
"fmt"
"github.com/StevenACoffman/errgroup"
)
func main() {
g := new(errgroup.Group)
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
for i := range urls {
// Launch a goroutine to fetch the URL.
i := i // https://golang.org/doc/faq#closures_and_goroutines
g.Go(func() error {
// deliberate index out of bounds triggered
fmt.Println("Fetching:", i, urls[i+1])
return nil
})
}
// Wait for all HTTP fetches to complete.
err := g.Wait()
if err == nil {
fmt.Println("Successfully fetched all URLs.")
} else {
fmt.Println(err)
}
}
```
This work was done by my co-worker [Ben Kraft](https://github.com/benjaminjkraft), and, with his permission, I lightly modified it to
lift it out of our repository for Go community discussion.
### Counterpoint
There is [an interesting discussion](https://github.com/oklog/run/issues/10) which has an alternative view that,
with few exceptions, panics **should** crash your program.
### Prior Art
With only a cursory search, I found a few existing open source examples.
#### [Kratos](https://github.com/go-kratos/kratos errgroup
Kratos Go framework for microservices has a similar [errgroup](https://github.com/go-kratos/kratos/blob/master/pkg/sync/errgroup/errgroup.go)
solution.
#### PanicGroup by Sergey Alexandrovich
In the article [Errors in Go:
From denial to acceptance](https://evilmartians.com/chronicles/errors-in-go-from-denial-to-acceptance),
(which advocates panic based flow control 😱), they have a PanicGroup that's roughly equivalent:
```
type PanicGroup struct {
wg sync.WaitGroup
errOnce sync.Once
err error
}
func (g *PanicGroup) Wait() error {
g.wg.Wait()
return g.err
}
func (g *PanicGroup) Go(f func()) {
g.wg.Add(1)
go func() {
defer g.wg.Done()
defer func(){
if r := recover(); r != nil {
if err, ok := r.(error); ok {
// We need only the first error, sync.Once is useful here.
g.errOnce.Do(func() {
g.err = err
})
} else {
panic(r)
}
}
}()
f()
}()
}
```