github.com/amane3/goreleaser@v0.182.0/internal/semerrgroup/sem.go (about)

     1  // Package semerrgroup wraps a error group with a semaphore with configurable
     2  // size, so you can control the number of tasks being executed simultaneously.
     3  package semerrgroup
     4  
     5  import (
     6  	"sync"
     7  
     8  	"github.com/amane3/goreleaser/internal/pipe"
     9  	"golang.org/x/sync/errgroup"
    10  )
    11  
    12  // Group is the Semphore ErrorGroup itself.
    13  type Group interface {
    14  	Go(func() error)
    15  	Wait() error
    16  }
    17  
    18  // New returns a new Group of a given size.
    19  func New(size int) Group {
    20  	if size == 1 {
    21  		return &serialGroup{}
    22  	}
    23  	return &parallelGroup{
    24  		ch: make(chan bool, size),
    25  		g:  errgroup.Group{},
    26  	}
    27  }
    28  
    29  var _ Group = &parallelGroup{}
    30  
    31  type parallelGroup struct {
    32  	ch chan bool
    33  	g  errgroup.Group
    34  }
    35  
    36  // Go execs one function respecting the group and semaphore.
    37  func (s *parallelGroup) Go(fn func() error) {
    38  	s.g.Go(func() error {
    39  		s.ch <- true
    40  		defer func() {
    41  			<-s.ch
    42  		}()
    43  		return fn()
    44  	})
    45  }
    46  
    47  // Wait waits for the group to complete and return an error if any.
    48  func (s *parallelGroup) Wait() error {
    49  	return s.g.Wait()
    50  }
    51  
    52  var _ Group = &serialGroup{}
    53  
    54  type serialGroup struct {
    55  	err     error
    56  	errOnce sync.Once
    57  }
    58  
    59  // Go execs runs `fn` and saves the result if no error has been encountered.
    60  func (s *serialGroup) Go(fn func() error) {
    61  	if s.err != nil {
    62  		return
    63  	}
    64  	if err := fn(); err != nil {
    65  		s.errOnce.Do(func() {
    66  			s.err = err
    67  		})
    68  	}
    69  }
    70  
    71  // Wait waits for Go to complete and returns the first error encountered.
    72  func (s *serialGroup) Wait() error {
    73  	return s.err
    74  }
    75  
    76  var _ Group = &skipAwareGroup{}
    77  
    78  // NewSkipAware returns a new Group of a given size and aware of pipe skips.
    79  func NewSkipAware(g Group) Group {
    80  	return &skipAwareGroup{g: g}
    81  }
    82  
    83  type skipAwareGroup struct {
    84  	g        Group
    85  	skipErr  error
    86  	skipOnce sync.Once
    87  }
    88  
    89  // Go execs runs `fn` and saves the result if no error has been encountered.
    90  func (s *skipAwareGroup) Go(fn func() error) {
    91  	s.g.Go(func() error {
    92  		var err = fn()
    93  		// if the err is a skip, set it for later, but return nil for now so the
    94  		// the group proceeds.
    95  		if pipe.IsSkip(err) {
    96  			s.skipOnce.Do(func() {
    97  				s.skipErr = err
    98  			})
    99  			return nil
   100  		}
   101  		return err
   102  	})
   103  }
   104  
   105  // Wait waits for Go to complete and returns the first error encountered.
   106  func (s *skipAwareGroup) Wait() error {
   107  	// if we got a "real error", return it, otherwise return skipErr or nil.
   108  	if err := s.g.Wait(); err != nil {
   109  		return err
   110  	}
   111  	return s.skipErr
   112  }