github.com/coreos/mantle@v0.13.0/lang/worker/group.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package worker 16 17 import ( 18 "sync" 19 20 "github.com/coreos/pkg/multierror" 21 "golang.org/x/net/context" 22 ) 23 24 // Worker is a function that WorkerGroup will run in a new goroutine. 25 type Worker func(context.Context) error 26 27 // WorkerGroup is similar in principle to sync.WaitGroup but manages the 28 // Workers itself. This allows it to provide a few helpful features: 29 // - Integration with the context library. 30 // - Limit the number of concurrent Workers. 31 // - Capture the errors returned by each Worker. 32 // - Abort everything after a single Worker reports an error. 33 type WorkerGroup struct { 34 ctx context.Context 35 cancel context.CancelFunc 36 limit chan struct{} 37 38 mu sync.Mutex 39 errors multierror.Error 40 } 41 42 // NewWorkerGroup creates a new group. 43 func NewWorkerGroup(ctx context.Context, workerLimit int) *WorkerGroup { 44 wg := WorkerGroup{limit: make(chan struct{}, workerLimit)} 45 wg.ctx, wg.cancel = context.WithCancel(ctx) 46 return &wg 47 } 48 49 func (wg *WorkerGroup) addErr(err error) { 50 wg.mu.Lock() 51 defer wg.mu.Unlock() 52 wg.errors = append(wg.errors, err) 53 wg.cancel() 54 } 55 56 func (wg *WorkerGroup) getErr() error { 57 wg.mu.Lock() 58 defer wg.mu.Unlock() 59 return wg.errors.AsError() 60 } 61 62 // Start launches a new worker, blocking if too many workers are 63 // already running. An error indicates the group's context is closed. 64 func (wg *WorkerGroup) Start(worker Worker) error { 65 // check for cancellation before waiting on a worker slot 66 select { 67 default: 68 case <-wg.ctx.Done(): 69 return wg.ctx.Err() 70 } 71 select { 72 case wg.limit <- struct{}{}: 73 go func() { 74 if err := worker(wg.ctx); err != nil { 75 wg.addErr(err) 76 } 77 <-wg.limit 78 }() 79 return nil 80 case <-wg.ctx.Done(): 81 return wg.ctx.Err() 82 } 83 } 84 85 // Wait blocks until all running workers have finished. An error 86 // indicates if at least one worker returned an error or was canceled. 87 func (wg *WorkerGroup) Wait() error { 88 // make sure cancel is called at least once, after all work is done. 89 defer wg.cancel() 90 for i := 0; i < cap(wg.limit); i++ { 91 wg.limit <- struct{}{} 92 } 93 return wg.getErr() 94 } 95 96 // Wait with a default error value that will be returned if no worker failed. 97 // 98 // if err := wg.Start(worker); err != nil { 99 // return wg.WaitError(err) 100 // } 101 // 102 func (wg *WorkerGroup) WaitError(err error) error { 103 if werr := wg.Wait(); werr != nil { 104 return werr 105 } 106 return err 107 }