1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2024-12-27 01:33:39 +02:00
goreleaser/internal/semerrgroup/sem.go
Carlos Alexandro Becker f0b4db184e
fix: snapcraft temporary directory + concurrency (#4963)
this bug comes and goes every couple of versions it seems.

this will change the snapcraft implementation to run the first item
without concurrency, so all needed shared directories can be created
without issues, and then grows the limit of the wait group so the other
ones can run in parallel.

I haven't tested this yet, but I think it'll work.

- [x] test
- [x] godoc

refs https://github.com/goreleaser/goreleaser/issues/1715 refs
https://bugs.launchpad.net/snapcraft/+bug/1889741
2024-06-29 19:00:52 -03:00

105 lines
2.2 KiB
Go

// Package semerrgroup wraps an error group with a semaphore with configurable
// size, so you can control the number of tasks being executed simultaneously.
package semerrgroup
import (
"sync"
"github.com/goreleaser/goreleaser/v2/internal/pipe"
"github.com/hashicorp/go-multierror"
"golang.org/x/sync/errgroup"
)
// Group is the Semaphore ErrorGroup itself.
type Group interface {
Go(func() error)
Wait() error
}
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,
}
}
// New returns a new Group of a given size.
func New(size int) Group {
var g errgroup.Group
g.SetLimit(size)
return &g
}
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 {
g Group
skipErr *multierror.Error
l sync.Mutex
}
// 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 {
err := fn()
// if the err is a skip, set it for later, but return nil for now so the
// group proceeds.
if pipe.IsSkip(err) {
s.l.Lock()
defer s.l.Unlock()
s.skipErr = multierror.Append(s.skipErr, err)
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
}
if s.skipErr == nil {
return nil
}
if s.skipErr.Len() == 1 {
return s.skipErr.Errors[0]
}
return s.skipErr
}