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 }