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  }