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  }