gitee.com/zhaochuninhefei/gmgo@v0.0.31-0.20240209061119-069254a02979/grpc/xds/internal/balancer/priority/balancer_priority.go (about) 1 /* 2 * 3 * Copyright 2021 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package priority 20 21 import ( 22 "errors" 23 "time" 24 25 "gitee.com/zhaochuninhefei/gmgo/grpc/balancer" 26 "gitee.com/zhaochuninhefei/gmgo/grpc/balancer/base" 27 "gitee.com/zhaochuninhefei/gmgo/grpc/connectivity" 28 ) 29 30 var ( 31 // ErrAllPrioritiesRemoved is returned by the picker when there's no priority available. 32 ErrAllPrioritiesRemoved = errors.New("no priority is provided, all priorities are removed") 33 // DefaultPriorityInitTimeout is the timeout after which if a priority is 34 // not READY, the next will be started. It's exported to be overridden by 35 // tests. 36 DefaultPriorityInitTimeout = 10 * time.Second 37 ) 38 39 // syncPriority handles priority after a config update. It makes sure the 40 // balancer state (started or not) is in sync with the priorities (even in 41 // tricky cases where a child is moved from a priority to another). 42 // 43 // It's guaranteed that after this function returns: 44 // - If some child is READY, it is childInUse, and all lower priorities are 45 // closed. 46 // - If some child is newly started(in Connecting for the first time), it is 47 // childInUse, and all lower priorities are closed. 48 // - Otherwise, the lowest priority is childInUse (none of the children is 49 // ready, and the overall state is not ready). 50 // 51 // Steps: 52 // - If all priorities were deleted, unset childInUse (to an empty string), and 53 // set parent ClientConn to TransientFailure 54 // - Otherwise, Scan all children from p0, and check balancer stats: 55 // - For any of the following cases: 56 // - If balancer is not started (not built), this is either a new child 57 // with high priority, or a new builder for an existing child. 58 // - If balancer is READY 59 // - If this is the lowest priority 60 // - do the following: 61 // - if this is not the old childInUse, override picker so old picker is no 62 // longer used. 63 // - switch to it (because all higher priorities are neither new or Ready) 64 // - forward the new addresses and config 65 // 66 // Caller must hold b.mu. 67 func (b *priorityBalancer) syncPriority() { 68 // Everything was removed by the update. 69 if len(b.priorities) == 0 { 70 b.childInUse = "" 71 b.priorityInUse = 0 72 // Stop the init timer. This can happen if the only priority is removed 73 // shortly after it's added. 74 b.stopPriorityInitTimer() 75 b.cc.UpdateState(balancer.State{ 76 ConnectivityState: connectivity.TransientFailure, 77 Picker: base.NewErrPicker(ErrAllPrioritiesRemoved), 78 }) 79 return 80 } 81 82 for p, name := range b.priorities { 83 child, ok := b.children[name] 84 if !ok { 85 b.logger.Errorf("child with name %q is not found in children", name) 86 continue 87 } 88 89 if !child.started || 90 child.state.ConnectivityState == connectivity.Ready || 91 child.state.ConnectivityState == connectivity.Idle || 92 p == len(b.priorities)-1 { 93 if b.childInUse != "" && b.childInUse != child.name { 94 // childInUse was set and is different from this child, will 95 // change childInUse later. We need to update picker here 96 // immediately so parent stops using the old picker. 97 b.cc.UpdateState(child.state) 98 } 99 b.logger.Infof("switching to (%q, %v) in syncPriority", child.name, p) 100 b.switchToChild(child, p) 101 child.sendUpdate() 102 break 103 } 104 } 105 } 106 107 // Stop priorities [p+1, lowest]. 108 // 109 // Caller must hold b.mu. 110 func (b *priorityBalancer) stopSubBalancersLowerThanPriority(p int) { 111 for i := p + 1; i < len(b.priorities); i++ { 112 name := b.priorities[i] 113 child, ok := b.children[name] 114 if !ok { 115 b.logger.Errorf("child with name %q is not found in children", name) 116 continue 117 } 118 child.stop() 119 } 120 } 121 122 // switchToChild does the following: 123 // - stop all child with lower priorities 124 // - if childInUse is not this child 125 // - set childInUse to this child 126 // - stops init timer 127 // - if this child is not started, start it, and start a init timer 128 // 129 // Note that it does NOT send the current child state (picker) to the parent 130 // ClientConn. The caller needs to send it if necessary. 131 // 132 // this can be called when 133 // 1. first update, start p0 134 // 2. an update moves a READY child from a lower priority to higher 135 // 2. a different builder is updated for this child 136 // 3. a high priority goes Failure, start next 137 // 4. a high priority init timeout, start next 138 // 139 // Caller must hold b.mu. 140 func (b *priorityBalancer) switchToChild(child *childBalancer, priority int) { 141 // Stop lower priorities even if childInUse is same as this child. It's 142 // possible this child was moved from a priority to another. 143 b.stopSubBalancersLowerThanPriority(priority) 144 145 // If this child is already in use, do nothing. 146 // 147 // This can happen: 148 // - all priorities are not READY, an config update always triggers switch 149 // to the lowest. In this case, the lowest child could still be connecting, 150 // so we don't stop the init timer. 151 // - a high priority is READY, an config update always triggers switch to 152 // it. 153 if b.childInUse == child.name && child.started { 154 return 155 } 156 b.childInUse = child.name 157 b.priorityInUse = priority 158 159 // Init timer is always for childInUse. Since we are switching to a 160 // different child, we will stop the init timer no matter what. If this 161 // child is not started, we will start the init timer later. 162 b.stopPriorityInitTimer() 163 164 if !child.started { 165 child.start() 166 // Need this local variable to capture timerW in the AfterFunc closure 167 // to check the stopped boolean. 168 timerW := &timerWrapper{} 169 b.priorityInitTimer = timerW 170 timerW.timer = time.AfterFunc(DefaultPriorityInitTimeout, func() { 171 b.mu.Lock() 172 defer b.mu.Unlock() 173 if timerW.stopped { 174 return 175 } 176 b.priorityInitTimer = nil 177 // Switch to the next priority if there's any. 178 if pNext := priority + 1; pNext < len(b.priorities) { 179 nameNext := b.priorities[pNext] 180 if childNext, ok := b.children[nameNext]; ok { 181 b.switchToChild(childNext, pNext) 182 childNext.sendUpdate() 183 } 184 } 185 }) 186 } 187 } 188 189 // handleChildStateUpdate start/close priorities based on the connectivity 190 // state. 191 func (b *priorityBalancer) handleChildStateUpdate(childName string, s balancer.State) { 192 b.mu.Lock() 193 defer b.mu.Unlock() 194 if b.done.HasFired() { 195 return 196 } 197 198 priority, ok := b.childToPriority[childName] 199 if !ok { 200 b.logger.Errorf("priority: received picker update with unknown child %v", childName) 201 return 202 } 203 204 if b.childInUse == "" { 205 b.logger.Errorf("priority: no child is in use when picker update is received") 206 return 207 } 208 209 // priorityInUse is higher than this priority. 210 if b.priorityInUse < priority { 211 // Lower priorities should all be closed, this is an unexpected update. 212 // Can happen if the child policy sends an update after we tell it to 213 // close. 214 b.logger.Warningf("priority: received picker update from priority %v, lower than priority in use %v", priority, b.priorityInUse) 215 return 216 } 217 218 // Update state in child. The updated picker will be sent to parent later if 219 // necessary. 220 child, ok := b.children[childName] 221 if !ok { 222 b.logger.Errorf("priority: child balancer not found for child %v, priority %v", childName, priority) 223 return 224 } 225 oldState := child.state.ConnectivityState 226 child.state = s 227 228 switch s.ConnectivityState { 229 case connectivity.Ready, connectivity.Idle: 230 // Note that idle is also handled as if it's Ready. It will close the 231 // lower priorities (which will be kept in a cache, not deleted), and 232 // new picks will use the Idle picker. 233 b.handlePriorityWithNewStateReady(child, priority) 234 case connectivity.TransientFailure: 235 b.handlePriorityWithNewStateTransientFailure(child, priority) 236 case connectivity.Connecting: 237 b.handlePriorityWithNewStateConnecting(child, priority, oldState) 238 default: 239 // New state is Shutdown, should never happen. Don't forward. 240 } 241 } 242 243 // handlePriorityWithNewStateReady handles state Ready from a higher or equal 244 // priority. 245 // 246 // An update with state Ready: 247 // - If it's from higher priority: 248 // - Switch to this priority 249 // - Forward the update 250 // - If it's from priorityInUse: 251 // - Forward only 252 // 253 // Caller must make sure priorityInUse is not higher than priority. 254 // 255 // Caller must hold mu. 256 func (b *priorityBalancer) handlePriorityWithNewStateReady(child *childBalancer, priority int) { 257 // If one priority higher or equal to priorityInUse goes Ready, stop the 258 // init timer. If update is from higher than priorityInUse, priorityInUse 259 // will be closed, and the init timer will become useless. 260 b.stopPriorityInitTimer() 261 262 // priorityInUse is lower than this priority, switch to this. 263 if b.priorityInUse > priority { 264 b.logger.Infof("Switching priority from %v to %v, because latter became Ready", b.priorityInUse, priority) 265 b.switchToChild(child, priority) 266 } 267 // Forward the update since it's READY. 268 b.cc.UpdateState(child.state) 269 } 270 271 // handlePriorityWithNewStateTransientFailure handles state TransientFailure 272 // from a higher or equal priority. 273 // 274 // An update with state TransientFailure: 275 // - If it's from a higher priority: 276 // - Do not forward, and do nothing 277 // - If it's from priorityInUse: 278 // - If there's no lower: 279 // - Forward and do nothing else 280 // - If there's a lower priority: 281 // - Switch to the lower 282 // - Forward the lower child's state 283 // - Do NOT forward this update 284 // 285 // Caller must make sure priorityInUse is not higher than priority. 286 // 287 // Caller must hold mu. 288 func (b *priorityBalancer) handlePriorityWithNewStateTransientFailure(child *childBalancer, priority int) { 289 // priorityInUse is lower than this priority, do nothing. 290 if b.priorityInUse > priority { 291 return 292 } 293 // priorityInUse sends a failure. Stop its init timer. 294 b.stopPriorityInitTimer() 295 priorityNext := priority + 1 296 if priorityNext >= len(b.priorities) { 297 // Forward this update. 298 b.cc.UpdateState(child.state) 299 return 300 } 301 b.logger.Infof("Switching priority from %v to %v, because former became TransientFailure", priority, priorityNext) 302 nameNext := b.priorities[priorityNext] 303 childNext := b.children[nameNext] 304 b.switchToChild(childNext, priorityNext) 305 b.cc.UpdateState(childNext.state) 306 childNext.sendUpdate() 307 } 308 309 // handlePriorityWithNewStateConnecting handles state Connecting from a higher 310 // than or equal priority. 311 // 312 // An update with state Connecting: 313 // - If it's from a higher priority 314 // - Do nothing 315 // - If it's from priorityInUse, the behavior depends on previous state. 316 // 317 // When new state is Connecting, the behavior depends on previous state. If the 318 // previous state was Ready, this is a transition out from Ready to Connecting. 319 // Assuming there are multiple backends in the same priority, this mean we are 320 // in a bad situation and we should failover to the next priority (Side note: 321 // the current connectivity state aggregating algorithm (e.g. round-robin) is 322 // not handling this right, because if many backends all go from Ready to 323 // Connecting, the overall situation is more like TransientFailure, not 324 // Connecting). 325 // 326 // If the previous state was Idle, we don't do anything special with failure, 327 // and simply forward the update. The init timer should be in process, will 328 // handle failover if it timeouts. If the previous state was TransientFailure, 329 // we do not forward, because the lower priority is in use. 330 // 331 // Caller must make sure priorityInUse is not higher than priority. 332 // 333 // Caller must hold mu. 334 func (b *priorityBalancer) handlePriorityWithNewStateConnecting(child *childBalancer, priority int, oldState connectivity.State) { 335 // priorityInUse is lower than this priority, do nothing. 336 if b.priorityInUse > priority { 337 return 338 } 339 340 switch oldState { 341 case connectivity.Ready: 342 // Handling transition from Ready to Connecting, is same as handling 343 // TransientFailure. There's no need to stop the init timer, because it 344 // should have been stopped when state turned Ready. 345 priorityNext := priority + 1 346 if priorityNext >= len(b.priorities) { 347 // Forward this update. 348 b.cc.UpdateState(child.state) 349 return 350 } 351 b.logger.Infof("Switching priority from %v to %v, because former became TransientFailure", priority, priorityNext) 352 nameNext := b.priorities[priorityNext] 353 childNext := b.children[nameNext] 354 b.switchToChild(childNext, priorityNext) 355 b.cc.UpdateState(childNext.state) 356 childNext.sendUpdate() 357 case connectivity.Idle: 358 b.cc.UpdateState(child.state) 359 default: 360 // Old state is Connecting, TransientFailure or Shutdown. Don't forward. 361 } 362 }