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 b.logger.Debugf("Skipping update from child policy %q", childUpdating) 87 return 88 } 89 for p, name := range b.priorities { 90 child, ok := b.children[name] 91 if !ok { 92 b.logger.Warningf("Priority name %q is not found in list of child policies", name) 93 continue 94 } 95 96 if !child.started || 97 child.state.ConnectivityState == connectivity.Ready || 98 child.state.ConnectivityState == connectivity.Idle || 99 (child.state.ConnectivityState == connectivity.Connecting && child.initTimer != nil) || 100 p == len(b.priorities)-1 { 101 if b.childInUse != child.name || child.name == childUpdating { 102 b.logger.Debugf("childInUse, childUpdating: %q, %q", b.childInUse, child.name) 103 // If we switch children or the child in use just updated its 104 // picker, push the child's picker to the parent. 105 b.cc.UpdateState(child.state) 106 } 107 b.logger.Debugf("Switching to (%q, %v) in syncPriority", child.name, p) 108 b.switchToChild(child, p) 109 break 110 } 111 } 112 } 113 114 // Stop priorities [p+1, lowest]. 115 // 116 // Caller must hold b.mu. 117 func (b *priorityBalancer) stopSubBalancersLowerThanPriority(p int) { 118 for i := p + 1; i < len(b.priorities); i++ { 119 name := b.priorities[i] 120 child, ok := b.children[name] 121 if !ok { 122 b.logger.Warningf("Priority name %q is not found in list of child policies", name) 123 continue 124 } 125 child.stop() 126 } 127 } 128 129 // switchToChild does the following: 130 // - stop all child with lower priorities 131 // - if childInUse is not this child 132 // - set childInUse to this child 133 // - if this child is not started, start it 134 // 135 // Note that it does NOT send the current child state (picker) to the parent 136 // ClientConn. The caller needs to send it if necessary. 137 // 138 // this can be called when 139 // 1. first update, start p0 140 // 2. an update moves a READY child from a lower priority to higher 141 // 2. a different builder is updated for this child 142 // 3. a high priority goes Failure, start next 143 // 4. a high priority init timeout, start next 144 // 145 // Caller must hold b.mu. 146 func (b *priorityBalancer) switchToChild(child *childBalancer, priority int) { 147 // Stop lower priorities even if childInUse is same as this child. It's 148 // possible this child was moved from a priority to another. 149 b.stopSubBalancersLowerThanPriority(priority) 150 151 // If this child is already in use, do nothing. 152 // 153 // This can happen: 154 // - all priorities are not READY, an config update always triggers switch 155 // to the lowest. In this case, the lowest child could still be connecting, 156 // so we don't stop the init timer. 157 // - a high priority is READY, an config update always triggers switch to 158 // it. 159 if b.childInUse == child.name && child.started { 160 return 161 } 162 b.childInUse = child.name 163 164 if !child.started { 165 child.start() 166 } 167 } 168 169 // handleChildStateUpdate start/close priorities based on the connectivity 170 // state. 171 func (b *priorityBalancer) handleChildStateUpdate(childName string, s balancer.State) { 172 // Update state in child. The updated picker will be sent to parent later if 173 // necessary. 174 child, ok := b.children[childName] 175 if !ok { 176 b.logger.Warningf("Child policy not found for %q", childName) 177 return 178 } 179 if !child.started { 180 b.logger.Warningf("Ignoring update from child policy %q which is not in started state: %+v", childName, s) 181 return 182 } 183 child.state = s 184 185 // We start/stop the init timer of this child based on the new connectivity 186 // state. syncPriority() later will need the init timer (to check if it's 187 // nil or not) to decide which child to switch to. 188 switch s.ConnectivityState { 189 case connectivity.Ready, connectivity.Idle: 190 child.reportedTF = false 191 child.stopInitTimer() 192 case connectivity.TransientFailure: 193 child.reportedTF = true 194 child.stopInitTimer() 195 case connectivity.Connecting: 196 if !child.reportedTF { 197 child.startInitTimer() 198 } 199 default: 200 // New state is Shutdown, should never happen. Don't forward. 201 } 202 203 child.parent.syncPriority(childName) 204 } 205