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 }