google.golang.org/grpc@v1.72.2/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 "google.golang.org/grpc/balancer" 26 "google.golang.org/grpc/connectivity" 27 ) 28 29 var ( 30 // ErrAllPrioritiesRemoved is returned by the picker when there's no priority available. 31 ErrAllPrioritiesRemoved = errors.New("no priority is provided, all priorities are removed") 32 // DefaultPriorityInitTimeout is the timeout after which if a priority is 33 // not READY, the next will be started. It's exported to be overridden by 34 // tests. 35 DefaultPriorityInitTimeout = 10 * time.Second 36 ) 37 38 // syncPriority handles priority after a config update or a child balancer 39 // connectivity state update. It makes sure the balancer state (started or not) 40 // is in sync with the priorities (even in tricky cases where a child is moved 41 // from a priority to another). 42 // 43 // It's guaranteed that after this function returns: 44 // 45 // If some child is READY, it is childInUse, and all lower priorities are 46 // closed. 47 // 48 // If some child is newly started(in Connecting for the first time), it is 49 // childInUse, and all lower priorities are closed. 50 // 51 // Otherwise, the lowest priority is childInUse (none of the children is 52 // ready, and the overall state is not ready). 53 // 54 // Steps: 55 // 56 // If all priorities were deleted, unset childInUse (to an empty string), and 57 // set parent ClientConn to TransientFailure 58 // 59 // Otherwise, Scan all children from p0, and check balancer stats: 60 // 61 // For any of the following cases: 62 // 63 // If balancer is not started (not built), this is either a new child with 64 // high priority, or a new builder for an existing child. 65 // 66 // If balancer is Connecting and has non-nil initTimer (meaning it 67 // transitioned from Ready or Idle to connecting, not from TF, so we 68 // should give it init-time to connect). 69 // 70 // If balancer is READY or IDLE 71 // 72 // If this is the lowest priority 73 // 74 // do the following: 75 // 76 // if this is not the old childInUse, override picker so old picker is no 77 // longer used. 78 // 79 // switch to it (because all higher priorities are neither new or Ready) 80 // 81 // forward the new addresses and config 82 // 83 // Caller must hold b.mu. 84 func (b *priorityBalancer) syncPriority(childUpdating string) { 85 if b.inhibitPickerUpdates { 86 if b.logger.V(2) { 87 b.logger.Infof("Skipping update from child policy %q", childUpdating) 88 } 89 return 90 } 91 for p, name := range b.priorities { 92 child, ok := b.children[name] 93 if !ok { 94 b.logger.Warningf("Priority name %q is not found in list of child policies", name) 95 continue 96 } 97 98 if !child.started || 99 child.state.ConnectivityState == connectivity.Ready || 100 child.state.ConnectivityState == connectivity.Idle || 101 (child.state.ConnectivityState == connectivity.Connecting && child.initTimer != nil) || 102 p == len(b.priorities)-1 { 103 if b.childInUse != child.name || child.name == childUpdating { 104 if b.logger.V(2) { 105 b.logger.Infof("childInUse, childUpdating: %q, %q", b.childInUse, child.name) 106 } 107 // If we switch children or the child in use just updated its 108 // picker, push the child's picker to the parent. 109 b.cc.UpdateState(child.state) 110 } 111 if b.logger.V(2) { 112 b.logger.Infof("Switching to (%q, %v) in syncPriority", child.name, p) 113 } 114 b.switchToChild(child, p) 115 break 116 } 117 } 118 } 119 120 // Stop priorities [p+1, lowest]. 121 // 122 // Caller must hold b.mu. 123 func (b *priorityBalancer) stopSubBalancersLowerThanPriority(p int) { 124 for i := p + 1; i < len(b.priorities); i++ { 125 name := b.priorities[i] 126 child, ok := b.children[name] 127 if !ok { 128 b.logger.Warningf("Priority name %q is not found in list of child policies", name) 129 continue 130 } 131 child.stop() 132 } 133 } 134 135 // switchToChild does the following: 136 // - stop all child with lower priorities 137 // - if childInUse is not this child 138 // - set childInUse to this child 139 // - if this child is not started, start it 140 // 141 // Note that it does NOT send the current child state (picker) to the parent 142 // ClientConn. The caller needs to send it if necessary. 143 // 144 // this can be called when 145 // 1. first update, start p0 146 // 2. an update moves a READY child from a lower priority to higher 147 // 2. a different builder is updated for this child 148 // 3. a high priority goes Failure, start next 149 // 4. a high priority init timeout, start next 150 // 151 // Caller must hold b.mu. 152 func (b *priorityBalancer) switchToChild(child *childBalancer, priority int) { 153 // Stop lower priorities even if childInUse is same as this child. It's 154 // possible this child was moved from a priority to another. 155 b.stopSubBalancersLowerThanPriority(priority) 156 157 // If this child is already in use, do nothing. 158 // 159 // This can happen: 160 // - all priorities are not READY, an config update always triggers switch 161 // to the lowest. In this case, the lowest child could still be connecting, 162 // so we don't stop the init timer. 163 // - a high priority is READY, an config update always triggers switch to 164 // it. 165 if b.childInUse == child.name && child.started { 166 return 167 } 168 b.childInUse = child.name 169 170 if !child.started { 171 child.start() 172 } 173 } 174 175 // handleChildStateUpdate start/close priorities based on the connectivity 176 // state. 177 func (b *priorityBalancer) handleChildStateUpdate(childName string, s balancer.State) { 178 // Update state in child. The updated picker will be sent to parent later if 179 // necessary. 180 child, ok := b.children[childName] 181 if !ok { 182 b.logger.Warningf("Child policy not found for %q", childName) 183 return 184 } 185 if !child.started { 186 b.logger.Warningf("Ignoring update from child policy %q which is not in started state: %+v", childName, s) 187 return 188 } 189 child.state = s 190 191 // We start/stop the init timer of this child based on the new connectivity 192 // state. syncPriority() later will need the init timer (to check if it's 193 // nil or not) to decide which child to switch to. 194 switch s.ConnectivityState { 195 case connectivity.Ready, connectivity.Idle: 196 child.reportedTF = false 197 child.stopInitTimer() 198 case connectivity.TransientFailure: 199 child.reportedTF = true 200 child.stopInitTimer() 201 case connectivity.Connecting: 202 if !child.reportedTF { 203 child.startInitTimer() 204 } 205 default: 206 // New state is Shutdown, should never happen. Don't forward. 207 } 208 209 child.parent.syncPriority(childName) 210 }