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 }