storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/sync/errgroup/errgroup.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2017 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package errgroup 18 19 import ( 20 "context" 21 "sync" 22 "sync/atomic" 23 ) 24 25 // A Group is a collection of goroutines working on subtasks that are part of 26 // the same overall task. 27 // 28 // A zero Group can be used if errors should not be tracked. 29 type Group struct { 30 firstErr int64 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG 31 wg sync.WaitGroup 32 bucket chan struct{} 33 errs []error 34 cancel context.CancelFunc 35 ctxCancel <-chan struct{} // nil if no context. 36 ctxErr func() error 37 } 38 39 // WithNErrs returns a new Group with length of errs slice upto nerrs, 40 // upon Wait() errors are returned collected from all tasks. 41 func WithNErrs(nerrs int) *Group { 42 return &Group{errs: make([]error, nerrs), firstErr: -1} 43 } 44 45 // Wait blocks until all function calls from the Go method have returned, then 46 // returns the slice of errors from all function calls. 47 func (g *Group) Wait() []error { 48 g.wg.Wait() 49 if g.cancel != nil { 50 g.cancel() 51 } 52 return g.errs 53 } 54 55 // WaitErr blocks until all function calls from the Go method have returned, then 56 // returns the first error returned. 57 func (g *Group) WaitErr() error { 58 g.wg.Wait() 59 if g.cancel != nil { 60 g.cancel() 61 } 62 if g.firstErr >= 0 && len(g.errs) > int(g.firstErr) { 63 // len(g.errs) > int(g.firstErr) is for then used uninitialized. 64 return g.errs[g.firstErr] 65 } 66 return nil 67 } 68 69 // WithConcurrency allows to limit the concurrency of the group. 70 // This must be called before starting any async processes. 71 // There is no order to which functions are allowed to run. 72 // If n <= 0 no concurrency limits are enforced. 73 // g is modified and returned as well. 74 func (g *Group) WithConcurrency(n int) *Group { 75 if n <= 0 { 76 g.bucket = nil 77 return g 78 } 79 80 // Fill bucket with tokens 81 g.bucket = make(chan struct{}, n) 82 for i := 0; i < n; i++ { 83 g.bucket <- struct{}{} 84 } 85 return g 86 } 87 88 // WithCancelOnError will return a context that is canceled 89 // as soon as an error occurs. 90 // The returned CancelFunc must always be called similar to context.WithCancel. 91 // If the supplied context is canceled any goroutines waiting for execution are also canceled. 92 func (g *Group) WithCancelOnError(ctx context.Context) (context.Context, context.CancelFunc) { 93 ctx, g.cancel = context.WithCancel(ctx) 94 g.ctxCancel = ctx.Done() 95 g.ctxErr = ctx.Err 96 return ctx, g.cancel 97 } 98 99 // Go calls the given function in a new goroutine. 100 // 101 // The errors will be collected in errs slice and returned by Wait(). 102 func (g *Group) Go(f func() error, index int) { 103 g.wg.Add(1) 104 go func() { 105 defer g.wg.Done() 106 if g.bucket != nil { 107 // Wait for token 108 select { 109 case <-g.bucket: 110 defer func() { 111 // Put back token.. 112 g.bucket <- struct{}{} 113 }() 114 case <-g.ctxCancel: 115 if len(g.errs) > index { 116 atomic.CompareAndSwapInt64(&g.firstErr, -1, int64(index)) 117 g.errs[index] = g.ctxErr() 118 } 119 return 120 } 121 } 122 if err := f(); err != nil { 123 if len(g.errs) > index { 124 atomic.CompareAndSwapInt64(&g.firstErr, -1, int64(index)) 125 g.errs[index] = err 126 } 127 if g.cancel != nil { 128 g.cancel() 129 } 130 } 131 }() 132 }