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