github.com/Microsoft/azure-vhd-utils@v0.0.0-20230613175315-7c30a3748a1b/upload/concurrent/balancer.go (about) 1 package concurrent 2 3 import ( 4 "container/heap" 5 "time" 6 ) 7 8 // Balancer is a type that can balance load among a set of workers 9 // 10 type Balancer struct { 11 errorChan chan error // The channel used by all workers to report error 12 requestHandledChan chan *Worker // The channel used by a worker to signal balancer that a work has been executed 13 tearDownChan chan bool // The channel that all workers listening for force quit signal 14 workerFinishedChan chan *Worker // The channel that all worker used to signal balancer that it exiting 15 allWorkersFinishedChan chan bool // The channel this balancer signals once all worker signals it's exit on workerFinishedChan 16 pool Pool // Pool of workers that this load balancer balances 17 workerCount int // The number of workers 18 } 19 20 // The size of work channel associated with each worker this balancer manages. 21 // 22 const workerQueueSize int = 3 23 24 // NewBalancer creates a new instance of Balancer that needs to balance load between 'workerCount' workers 25 // 26 func NewBalancer(workerCount int) *Balancer { 27 balancer := &Balancer{ 28 workerCount: workerCount, 29 pool: Pool{ 30 Workers: make([]*Worker, workerCount), 31 }, 32 } 33 return balancer 34 } 35 36 // Init initializes all channels and start the workers. 37 // 38 func (b *Balancer) Init() { 39 b.errorChan = make(chan error, 0) 40 b.requestHandledChan = make(chan *Worker, 0) 41 b.workerFinishedChan = make(chan *Worker, 0) 42 b.allWorkersFinishedChan = make(chan bool, 0) 43 b.tearDownChan = make(chan bool, 0) 44 for i := 0; i < b.workerCount; i++ { 45 b.pool.Workers[i] = NewWorker(i, workerQueueSize, &(b.pool), b.errorChan, b.requestHandledChan, b.workerFinishedChan) 46 (b.pool.Workers[i]).Run(b.tearDownChan) 47 } 48 } 49 50 // TearDownWorkers sends a force quit signal to all workers, which case worker to quit as soon as possible, 51 // workers won't drain it's request channel in this case. 52 // 53 func (b *Balancer) TearDownWorkers() { 54 close(b.tearDownChan) 55 } 56 57 // Run read request from the request channel identified by the parameter requestChan and dispatch it the worker 58 // with least load. This method returns two channels, a channel to communicate error from any worker back to 59 // the consumer of balancer and second channel is used by the balancer to signal consumer that all workers has 60 // been finished executing. 61 // 62 func (b *Balancer) Run(requestChan <-chan *Request) (<-chan error, <-chan bool) { 63 // Request dispatcher 64 go func() { 65 for { 66 requestToHandle, ok := <-requestChan 67 if !ok { 68 b.closeWorkersRequestChannel() 69 return 70 } 71 b.dispatch(requestToHandle) 72 } 73 }() 74 75 // listener for worker status 76 go func() { 77 remainingWorkers := b.workerCount 78 for { 79 select { 80 case w := <-b.requestHandledChan: 81 b.completed(w) 82 case _ = <-b.workerFinishedChan: 83 remainingWorkers-- 84 if remainingWorkers == 0 { 85 b.allWorkersFinishedChan <- true // All workers has been exited 86 return 87 } 88 } 89 } 90 }() 91 92 return b.errorChan, b.allWorkersFinishedChan 93 } 94 95 // closeWorkersRequestChannel closes the Request channel of all workers, this indicates that no 96 // more work will not be send the channel so that the workers can gracefully exit after handling 97 // any pending work in the channel. 98 // 99 func (b *Balancer) closeWorkersRequestChannel() { 100 for i := 0; i < b.workerCount; i++ { 101 close((b.pool.Workers[i]).RequestsToHandleChan) 102 } 103 } 104 105 // dispatch dispatches the request to the worker with least load. If all workers are completely 106 // busy (i.e. there Pending request count is currently equal to the maximum load) then this 107 // method will poll until one worker is available. 108 // 109 func (b *Balancer) dispatch(request *Request) { 110 for { 111 if b.pool.Workers[0].Pending >= workerQueueSize { 112 // Wait for a worker to be available 113 time.Sleep(500 * time.Millisecond) 114 } else { 115 b.pool.Lock() 116 worker := b.pool.Workers[0] 117 worker.Pending++ 118 heap.Fix(&b.pool, worker.Index) 119 worker.RequestsToHandleChan <- request 120 b.pool.Unlock() 121 return 122 } 123 } 124 } 125 126 // completed is called when a worker finishes one work, it updates the load status of the given the 127 // worker. 128 // 129 func (b *Balancer) completed(worker *Worker) { 130 b.pool.Lock() 131 worker.Pending-- 132 heap.Fix(&b.pool, worker.Index) 133 b.pool.Unlock() 134 } 135 136 // WorkersCurrentLoad returns the load of the workers this balancer manages as comma separated string 137 // values where each value consists of worker id (Worker.Id property) and pending requests associated 138 // with the worker. 139 // 140 func (b *Balancer) WorkersCurrentLoad() string { 141 return b.pool.WorkersCurrentLoad() 142 }