google.golang.org/grpc@v1.62.1/xds/internal/balancer/priority/balancer_child.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 "time" 23 24 "google.golang.org/grpc/balancer" 25 "google.golang.org/grpc/balancer/base" 26 "google.golang.org/grpc/connectivity" 27 "google.golang.org/grpc/resolver" 28 "google.golang.org/grpc/serviceconfig" 29 ) 30 31 type childBalancer struct { 32 name string 33 parent *priorityBalancer 34 parentCC balancer.ClientConn 35 balancerName string 36 cc *ignoreResolveNowClientConn 37 38 ignoreReresolutionRequests bool 39 config serviceconfig.LoadBalancingConfig 40 rState resolver.State 41 42 started bool 43 // This is set when the child reports TransientFailure, and unset when it 44 // reports Ready or Idle. It is used to decide whether the failover timer 45 // should start when the child is transitioning into Connecting. The timer 46 // will be restarted if the child has not reported TF more recently than it 47 // reported Ready or Idle. 48 reportedTF bool 49 // The latest state the child balancer provided. 50 state balancer.State 51 // The timer to give a priority some time to connect. And if the priority 52 // doesn't go into Ready/Failure, the next priority will be started. 53 initTimer *timerWrapper 54 } 55 56 // newChildBalancer creates a child balancer place holder, but doesn't 57 // build/start the child balancer. 58 func newChildBalancer(name string, parent *priorityBalancer, balancerName string, cc balancer.ClientConn) *childBalancer { 59 return &childBalancer{ 60 name: name, 61 parent: parent, 62 parentCC: cc, 63 balancerName: balancerName, 64 cc: newIgnoreResolveNowClientConn(cc, false), 65 started: false, 66 // Start with the connecting state and picker with re-pick error, so 67 // that when a priority switch causes this child picked before it's 68 // balancing policy is created, a re-pick will happen. 69 state: balancer.State{ 70 ConnectivityState: connectivity.Connecting, 71 Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable), 72 }, 73 } 74 } 75 76 // updateBalancerName updates balancer name for the child, but doesn't build a 77 // new one. The parent priority LB always closes the child policy before 78 // updating the balancer name, and the new balancer is built when it gets added 79 // to the balancergroup as part of start(). 80 func (cb *childBalancer) updateBalancerName(balancerName string) { 81 cb.balancerName = balancerName 82 cb.cc = newIgnoreResolveNowClientConn(cb.parentCC, cb.ignoreReresolutionRequests) 83 } 84 85 // updateConfig sets childBalancer's config and state, but doesn't send update to 86 // the child balancer unless it is started. 87 func (cb *childBalancer) updateConfig(child *Child, rState resolver.State) { 88 cb.ignoreReresolutionRequests = child.IgnoreReresolutionRequests 89 cb.config = child.Config.Config 90 cb.rState = rState 91 if cb.started { 92 cb.sendUpdate() 93 } 94 } 95 96 // start builds the child balancer if it's not already started. 97 // 98 // It doesn't do it directly. It asks the balancer group to build it. 99 func (cb *childBalancer) start() { 100 if cb.started { 101 return 102 } 103 cb.started = true 104 cb.parent.bg.AddWithClientConn(cb.name, cb.balancerName, cb.cc) 105 cb.startInitTimer() 106 cb.sendUpdate() 107 } 108 109 // sendUpdate sends the addresses and config to the child balancer. 110 func (cb *childBalancer) sendUpdate() { 111 cb.cc.updateIgnoreResolveNow(cb.ignoreReresolutionRequests) 112 // TODO: return and aggregate the returned error in the parent. 113 err := cb.parent.bg.UpdateClientConnState(cb.name, balancer.ClientConnState{ 114 ResolverState: cb.rState, 115 BalancerConfig: cb.config, 116 }) 117 if err != nil { 118 cb.parent.logger.Warningf("Failed to update state for child policy %q: %v", cb.name, err) 119 } 120 } 121 122 // stop stops the child balancer and resets the state. 123 // 124 // It doesn't do it directly. It asks the balancer group to remove it. 125 // 126 // Note that the underlying balancer group could keep the child in a cache. 127 func (cb *childBalancer) stop() { 128 if !cb.started { 129 return 130 } 131 cb.stopInitTimer() 132 cb.parent.bg.Remove(cb.name) 133 cb.started = false 134 cb.state = balancer.State{ 135 ConnectivityState: connectivity.Connecting, 136 Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable), 137 } 138 // Clear child.reportedTF, so that if this child is started later, it will 139 // be given time to connect. 140 cb.reportedTF = false 141 } 142 143 func (cb *childBalancer) startInitTimer() { 144 if cb.initTimer != nil { 145 return 146 } 147 // Need this local variable to capture timerW in the AfterFunc closure 148 // to check the stopped boolean. 149 timerW := &timerWrapper{} 150 cb.initTimer = timerW 151 timerW.timer = time.AfterFunc(DefaultPriorityInitTimeout, func() { 152 cb.parent.mu.Lock() 153 defer cb.parent.mu.Unlock() 154 if timerW.stopped { 155 return 156 } 157 cb.initTimer = nil 158 // Re-sync the priority. This will switch to the next priority if 159 // there's any. Note that it's important sync() is called after setting 160 // initTimer to nil. 161 cb.parent.syncPriority("") 162 }) 163 } 164 165 func (cb *childBalancer) stopInitTimer() { 166 timerW := cb.initTimer 167 if timerW == nil { 168 return 169 } 170 cb.initTimer = nil 171 timerW.stopped = true 172 timerW.timer.Stop() 173 }