github.com/blend/go-sdk@v1.20220411.3/async/batch.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package async
     9  
    10  import (
    11  	"context"
    12  	"runtime"
    13  )
    14  
    15  // NewBatch creates a new batch processor.
    16  // Batch processes are a known quantity of work that needs to be processed in parallel.
    17  func NewBatch(work chan interface{}, action WorkAction, options ...BatchOption) *Batch {
    18  	b := Batch{
    19  		Action:      action,
    20  		Work:        work,
    21  		Parallelism: runtime.NumCPU(),
    22  	}
    23  	for _, option := range options {
    24  		option(&b)
    25  	}
    26  	return &b
    27  }
    28  
    29  // BatchOption is an option for the batch worker.
    30  type BatchOption func(*Batch)
    31  
    32  // OptBatchErrors sets the batch worker error return channel.
    33  func OptBatchErrors(errors chan error) BatchOption {
    34  	return func(i *Batch) {
    35  		i.Errors = errors
    36  	}
    37  }
    38  
    39  // OptBatchSkipRecoverPanics sets the batch worker to throw (or to recover) panics.
    40  func OptBatchSkipRecoverPanics(skipRecoverPanics bool) BatchOption {
    41  	return func(i *Batch) {
    42  		i.SkipRecoverPanics = skipRecoverPanics
    43  	}
    44  }
    45  
    46  // OptBatchParallelism sets the batch worker parallelism, or the number of workers to create.
    47  func OptBatchParallelism(parallelism int) BatchOption {
    48  	return func(i *Batch) {
    49  		i.Parallelism = parallelism
    50  	}
    51  }
    52  
    53  // Batch is a batch of work executed by a fixed count of workers.
    54  type Batch struct {
    55  	Action            WorkAction
    56  	SkipRecoverPanics bool
    57  	Parallelism       int
    58  	Work              chan interface{}
    59  	Errors            chan error
    60  }
    61  
    62  // Process executes the action for all the work items.
    63  func (b *Batch) Process(ctx context.Context) {
    64  	if len(b.Work) == 0 {
    65  		return
    66  	}
    67  
    68  	effectiveParallelism := b.Parallelism
    69  	if effectiveParallelism == 0 {
    70  		effectiveParallelism = runtime.NumCPU()
    71  	}
    72  	if effectiveParallelism > len(b.Work) {
    73  		effectiveParallelism = len(b.Work)
    74  	}
    75  
    76  	allWorkers := make([]*Worker, effectiveParallelism)
    77  	availableWorkers := make(chan *Worker, effectiveParallelism)
    78  
    79  	// return worker is a local finalizer
    80  	// that grabs a reference to the workers set.
    81  	returnWorker := func(ctx context.Context, worker *Worker) error {
    82  		availableWorkers <- worker
    83  		return nil
    84  	}
    85  
    86  	// create and start workers.
    87  	for x := 0; x < effectiveParallelism; x++ {
    88  		worker := NewWorker(b.Action)
    89  		worker.Context = ctx
    90  		worker.Errors = b.Errors
    91  		worker.Finalizer = returnWorker
    92  		worker.SkipRecoverPanics = b.SkipRecoverPanics
    93  
    94  		workerStarted := worker.NotifyStarted()
    95  		go func() { _ = worker.Start() }()
    96  		<-workerStarted
    97  
    98  		allWorkers[x] = worker
    99  		availableWorkers <- worker
   100  	}
   101  	defer func() {
   102  		for x := 0; x < len(allWorkers); x++ {
   103  			_ = allWorkers[x].Stop()
   104  		}
   105  	}()
   106  
   107  	numWorkItems := len(b.Work)
   108  	var worker *Worker
   109  	var workItem interface{}
   110  	for x := 0; x < numWorkItems; x++ {
   111  		select {
   112  		case <-ctx.Done():
   113  			return
   114  		default:
   115  		}
   116  
   117  		select {
   118  		case workItem = <-b.Work:
   119  			select {
   120  			case worker = <-availableWorkers:
   121  				select {
   122  				case worker.Work <- workItem:
   123  				case <-ctx.Done():
   124  					return
   125  				}
   126  			case <-ctx.Done():
   127  				return
   128  			}
   129  		case <-ctx.Done():
   130  			return
   131  		}
   132  	}
   133  }