github.com/grailbio/base@v0.0.11/sync/multierror/multierror.go (about)

     1  package multierror
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"sync"
     7  )
     8  
     9  type multiError struct {
    10  	errs    []error
    11  	dropped int
    12  }
    13  
    14  // Error returns a string describing the multiple errors represented by e.
    15  func (e multiError) Error() string {
    16  	switch len(e.errs) {
    17  	case 0, 1:
    18  		panic("invalid multiError")
    19  	default:
    20  		var b strings.Builder
    21  		b.WriteString("[")
    22  		for i, err := range e.errs {
    23  			if i > 0 {
    24  				b.WriteString("\n")
    25  			}
    26  			b.WriteString(err.Error())
    27  		}
    28  		b.WriteString("]")
    29  		if e.dropped > 0 {
    30  			fmt.Fprintf(&b, " [plus %d other error(s)]", e.dropped)
    31  		}
    32  		return b.String()
    33  	}
    34  }
    35  
    36  // Builder captures errors from parallel goroutines.
    37  //
    38  // Example usage:
    39  // 	var (
    40  // 		errs = multierror.NewBuilder(3)
    41  // 		wg   sync.WaitGroup
    42  // 	)
    43  // 	for _, foo := range foos {
    44  // 		wg.Add(1)
    45  // 		go func() {
    46  // 			defer wg.Done()
    47  // 			errs.Add(someWork(foo))
    48  // 		}()
    49  // 	}
    50  // 	wg.Wait()
    51  // 	if err := errs.Err(); err != nil {
    52  // 		// handle err
    53  // 	}
    54  type Builder struct {
    55  	mu      sync.Mutex // mu guards all fields
    56  	errs    []error
    57  	dropped int
    58  }
    59  
    60  func NewBuilder(max int) *Builder {
    61  	return &Builder{errs: make([]error, 0, max)}
    62  }
    63  
    64  // Add adds an error to b. b must be non-nil.
    65  func (b *Builder) Add(err error) {
    66  	if err == nil {
    67  		return
    68  	}
    69  
    70  	b.mu.Lock()
    71  	defer b.mu.Unlock()
    72  
    73  	if len(b.errs) == cap(b.errs) {
    74  		b.dropped++
    75  		return
    76  	}
    77  	b.errs = append(b.errs, err)
    78  }
    79  
    80  // Err returns an error combining all the errors that were already Add-ed. Otherwise returns nil.
    81  // b may be nil.
    82  func (b *Builder) Err() error {
    83  	if b == nil {
    84  		return nil
    85  	}
    86  	b.mu.Lock()
    87  	defer b.mu.Unlock()
    88  	switch len(b.errs) {
    89  	case 0:
    90  		return nil
    91  	case 1:
    92  		// TODO: This silently ignores b.dropped which is bad because it may be non-zero.
    93  		// Maybe we should make multiError{*, 1} legal. Or, maybe forbid max < 2.
    94  		return b.errs[0]
    95  	default:
    96  		return multiError{
    97  			// Sharing b.errs is ok because multiError doesn't mutate or append and Builder
    98  			// only appends.
    99  			errs:    b.errs,
   100  			dropped: b.dropped,
   101  		}
   102  	}
   103  }