github.com/mailru/activerecord@v1.12.2/pkg/iproto/syncutil/multitask.go (about)

     1  package syncutil
     2  
     3  import (
     4  	"sync"
     5  
     6  	"golang.org/x/net/context"
     7  )
     8  
     9  // Multitask helps to run N tasks in parallel.
    10  type Multitask struct {
    11  	// ContinueOnError disables cancellation of sub context passed to
    12  	// action callbackwhen it is not possible to start all N goroutines to
    13  	// prepare an action.
    14  	ContinueOnError bool
    15  
    16  	// Goer starts a goroutine which executes a given task.
    17  	// It is useful when client using some pool of goroutines.
    18  	//
    19  	// If nil, then default `go` is used and context is ignored.
    20  	//
    21  	// Non-nil error from Goer means that some resources are temporarily
    22  	// unavailable and given task will not be executed.
    23  	Goer GoerFn
    24  }
    25  
    26  // Do executes actor function N times probably in parallel.
    27  // It blocks until all actions are done or become canceled.
    28  func (m Multitask) Do(ctx context.Context, n int, actor func(context.Context, int) bool) (err error) {
    29  	// Prepare sub context to get the ability of cancelation remaining actions
    30  	// when user decide to stop.
    31  	subctx, cancel := context.WithCancel(ctx)
    32  	defer cancel()
    33  
    34  	// Prapre wait group counter.
    35  	var wg sync.WaitGroup
    36  
    37  	wg.Add(n)
    38  
    39  	for i := 0; i < n; i++ {
    40  		// Remember index of i.
    41  		index := i
    42  		// NOTE: We must spawn a goroutine with exactly root context, and call
    43  		// actor() with exactly sub context to prevent goer() falsy errors.
    44  		err = goer(ctx, m.Goer, func() {
    45  			if !actor(subctx, index) && subctx.Err() == nil {
    46  				cancel()
    47  			}
    48  			wg.Done()
    49  		})
    50  		if err != nil {
    51  			// Reduce wait group counter to zero because we do not want to
    52  			// proceed.
    53  			for j := i; j < n; j++ {
    54  				wg.Done()
    55  			}
    56  
    57  			if !m.ContinueOnError {
    58  				cancel()
    59  			}
    60  
    61  			// We are pessimistic here. If Goer could not prepare our request,
    62  			// we assume that other requests will fail too.
    63  			//
    64  			// It is also works on case when Goer is relies only on context –
    65  			// if context is canceled no more requests can be processed.
    66  			break
    67  		}
    68  	}
    69  
    70  	// Wait for the sent requests.
    71  	wg.Wait()
    72  
    73  	return err
    74  }
    75  
    76  // Every starts n goroutines and runs actor inside each. If some actor returns
    77  // error it stops processing and cancel other actions by canceling their
    78  // context argument. It returns first error occured.
    79  func Every(ctx context.Context, n int, actor func(context.Context, int) error) error {
    80  	m := Multitask{
    81  		ContinueOnError: false,
    82  	}
    83  
    84  	var (
    85  		mu   sync.Mutex
    86  		fail error
    87  	)
    88  
    89  	_ = m.Do(ctx, n, func(ctx context.Context, i int) bool {
    90  		if err := actor(ctx, i); err != nil {
    91  			mu.Lock()
    92  			if fail == nil {
    93  				fail = err
    94  			}
    95  			mu.Unlock()
    96  
    97  			return false
    98  		}
    99  
   100  		return true
   101  	})
   102  
   103  	return fail
   104  }
   105  
   106  // Each starts n goroutines and runs actor inside it. It returns when all
   107  // actors return.
   108  func Each(ctx context.Context, n int, actor func(context.Context, int)) {
   109  	m := Multitask{
   110  		ContinueOnError: true,
   111  	}
   112  
   113  	_ = m.Do(ctx, n, func(ctx context.Context, i int) bool {
   114  		actor(ctx, i)
   115  		return true
   116  	})
   117  }
   118  
   119  // GoerFn represents function that starts a goroutine which executes a given
   120  // task.
   121  type GoerFn func(context.Context, func()) error
   122  
   123  func goer(ctx context.Context, g GoerFn, task func()) error {
   124  	if g != nil {
   125  		return g(ctx, task)
   126  	}
   127  
   128  	go task()
   129  
   130  	return nil
   131  }