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