2023-06-05 19:08:57 +03:00
|
|
|
// Package semerrgroup wraps an error group with a semaphore with configurable
|
2018-07-09 22:04:25 -07:00
|
|
|
// size, so you can control the number of tasks being executed simultaneously.
|
2018-07-09 21:38:00 -07:00
|
|
|
package semerrgroup
|
|
|
|
|
2019-10-06 14:58:33 -03:00
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
|
2024-05-26 15:02:57 -03:00
|
|
|
"github.com/goreleaser/goreleaser/v2/internal/pipe"
|
2024-01-07 14:12:50 -03:00
|
|
|
"github.com/hashicorp/go-multierror"
|
2019-10-06 14:58:33 -03:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
)
|
2018-07-09 21:38:00 -07:00
|
|
|
|
2023-06-05 19:08:57 +03:00
|
|
|
// Group is the Semaphore ErrorGroup itself.
|
2019-08-05 04:36:14 -07:00
|
|
|
type Group interface {
|
|
|
|
Go(func() error)
|
|
|
|
Wait() error
|
2018-07-09 21:38:00 -07:00
|
|
|
}
|
|
|
|
|
2024-06-29 19:00:52 -03:00
|
|
|
type blockingFirstGroup struct {
|
|
|
|
g Group
|
|
|
|
|
|
|
|
firstMu sync.Mutex
|
|
|
|
firstDone bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *blockingFirstGroup) Go(fn func() error) {
|
|
|
|
g.firstMu.Lock()
|
|
|
|
if g.firstDone {
|
|
|
|
g.g.Go(fn)
|
|
|
|
g.firstMu.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := fn(); err != nil {
|
|
|
|
g.g.Go(func() error { return err })
|
|
|
|
}
|
|
|
|
g.firstDone = true
|
|
|
|
g.firstMu.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *blockingFirstGroup) Wait() error {
|
|
|
|
return g.g.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewBlockingFirst creates a new group that runs the first item,
|
|
|
|
// waiting for its return, and only then starts scheduling/running the
|
|
|
|
// other tasks.
|
|
|
|
func NewBlockingFirst(g Group) Group {
|
|
|
|
return &blockingFirstGroup{
|
|
|
|
g: g,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 21:38:00 -07:00
|
|
|
// New returns a new Group of a given size.
|
2019-08-05 04:36:14 -07:00
|
|
|
func New(size int) Group {
|
2022-06-11 16:54:55 -03:00
|
|
|
var g errgroup.Group
|
|
|
|
g.SetLimit(size)
|
|
|
|
return &g
|
2019-08-05 04:36:14 -07:00
|
|
|
}
|
2019-10-06 14:58:33 -03:00
|
|
|
|
|
|
|
var _ Group = &skipAwareGroup{}
|
|
|
|
|
|
|
|
// NewSkipAware returns a new Group of a given size and aware of pipe skips.
|
|
|
|
func NewSkipAware(g Group) Group {
|
|
|
|
return &skipAwareGroup{g: g}
|
|
|
|
}
|
|
|
|
|
|
|
|
type skipAwareGroup struct {
|
2024-01-07 14:12:50 -03:00
|
|
|
g Group
|
|
|
|
skipErr *multierror.Error
|
|
|
|
l sync.Mutex
|
2019-10-06 14:58:33 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Go execs runs `fn` and saves the result if no error has been encountered.
|
|
|
|
func (s *skipAwareGroup) Go(fn func() error) {
|
|
|
|
s.g.Go(func() error {
|
2021-04-25 14:20:49 -03:00
|
|
|
err := fn()
|
2019-10-06 14:58:33 -03:00
|
|
|
// if the err is a skip, set it for later, but return nil for now so the
|
2022-08-30 22:42:29 +08:00
|
|
|
// group proceeds.
|
2019-10-06 14:58:33 -03:00
|
|
|
if pipe.IsSkip(err) {
|
2024-01-07 14:12:50 -03:00
|
|
|
s.l.Lock()
|
|
|
|
defer s.l.Unlock()
|
|
|
|
s.skipErr = multierror.Append(s.skipErr, err)
|
2019-10-06 14:58:33 -03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait waits for Go to complete and returns the first error encountered.
|
|
|
|
func (s *skipAwareGroup) Wait() error {
|
|
|
|
// if we got a "real error", return it, otherwise return skipErr or nil.
|
|
|
|
if err := s.g.Wait(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-07 14:12:50 -03:00
|
|
|
if s.skipErr == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.skipErr.Len() == 1 {
|
|
|
|
return s.skipErr.Errors[0]
|
|
|
|
}
|
|
|
|
|
2019-10-06 14:58:33 -03:00
|
|
|
return s.skipErr
|
|
|
|
}
|