github.com/Microsoft/azure-vhd-utils@v0.0.0-20230613175315-7c30a3748a1b/upload/concurrent/worker.go (about) 1 package concurrent 2 3 import "fmt" 4 5 // Worker represents a type which can listen for work from a channel and run them 6 // 7 type Worker struct { 8 RequestsToHandleChan chan *Request // The buffered channel of works this worker needs to handle 9 Pending int // The number of pending requests this worker needs to handle (i.e. worker load) 10 errorChan chan<- error // The channel to report failure in executing work 11 requestHandledChan chan<- *Worker // The channel to report that a work is done (irrespective of success or failure) 12 workerFinishedChan chan<- *Worker // The channel to signal that worker has finished (worker go-routine exited) 13 ID int // Unique Id for worker (Debugging purpose) 14 Index int // The index of the item in the heap. 15 pool *Pool // The parent pool holding all workers (used for work stealing) 16 } 17 18 // The maximum number of times a work needs to be retried before reporting failure on errorChan. 19 // 20 const maxRetryCount int = 5 21 22 // NewWorker creates a new instance of the worker with the given work channel size. 23 // errorChan is the channel to report the failure in addressing a work request after all 24 // retries, each time a work is completed (failure or success) doneChan will be signalled 25 // 26 func NewWorker(id int, workChannelSize int, pool *Pool, errorChan chan<- error, requestHandledChan chan<- *Worker, workerFinishedChan chan<- *Worker) *Worker { 27 return &Worker{ 28 ID: id, 29 RequestsToHandleChan: make(chan *Request, workChannelSize), 30 errorChan: errorChan, 31 requestHandledChan: requestHandledChan, 32 workerFinishedChan: workerFinishedChan, 33 pool: pool, 34 } 35 } 36 37 // Run starts a go-routine that read work from work-queue associated with the worker and executes one 38 // at a time. The go-routine returns/exit once one of the following condition is met: 39 // 1. The work-queue is closed and drained and there is no work to steal from peers worker's work-queue 40 // 2. A signal is received in the tearDownChan channel parameter 41 // 42 // After executing each work, this method sends report to Worker::requestHandledChan channel 43 // If a work fails after maximum retry, this method sends report to Worker::errorChan channel 44 // 45 func (w *Worker) Run(tearDownChan <-chan bool) { 46 go func() { 47 defer func() { 48 // Signal balancer that worker is finished 49 w.workerFinishedChan <- w 50 }() 51 52 var requestToHandle *Request 53 var ok bool 54 for { 55 select { 56 case requestToHandle, ok = <-w.RequestsToHandleChan: 57 if !ok { 58 // Request channel is closed and drained, worker can try to steal work from others. 59 // 60 // Note: load balancer does not play any role in stealing, load balancer closes send-end 61 // of all worker queue's at the same time, at this point we are sure that no more new job 62 // will be scheduled. Once we start stealing "Worker::Pending" won't reflect correct load. 63 requestToHandle = w.tryStealWork() 64 if requestToHandle == nil { 65 // Could not steal then return 66 return 67 } 68 } 69 case <-tearDownChan: 70 // immediate stop, no need to drain the request channel 71 return 72 } 73 74 var err error 75 // Do work, retry on failure. 76 Loop: 77 for count := 0; count < maxRetryCount+1; count++ { 78 select { 79 case <-tearDownChan: 80 return 81 default: 82 err = requestToHandle.Work() // Run work 83 if err == nil || !requestToHandle.ShouldRetry(err) { 84 break Loop 85 } 86 } 87 } 88 89 if err != nil { 90 select { 91 case w.errorChan <- fmt.Errorf("%s: %v", requestToHandle.ID, err): 92 case <-tearDownChan: 93 return 94 } 95 } 96 97 select { 98 case w.requestHandledChan <- w: // One work finished (successfully or unsuccessfully) 99 case <-tearDownChan: 100 return 101 } 102 } 103 }() 104 } 105 106 // tryStealWork will try to steal a work from peer worker if available. If all peer channels are 107 // empty then return nil 108 // 109 func (w *Worker) tryStealWork() *Request { 110 for _, w1 := range w.pool.Workers { 111 request, ok := <-w1.RequestsToHandleChan 112 if ok { 113 return request 114 } 115 } 116 return nil 117 }