google.golang.org/grpc@v1.72.2/xds/internal/balancer/priority/balancer.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 implements the priority balancer. 20 // 21 // This balancer will be kept in internal until we use it in the xds balancers, 22 // and are confident its functionalities are stable. It will then be exported 23 // for more users. 24 package priority 25 26 import ( 27 "encoding/json" 28 "fmt" 29 "sync" 30 "time" 31 32 "google.golang.org/grpc/balancer" 33 "google.golang.org/grpc/balancer/base" 34 "google.golang.org/grpc/connectivity" 35 "google.golang.org/grpc/internal/balancergroup" 36 "google.golang.org/grpc/internal/buffer" 37 "google.golang.org/grpc/internal/grpclog" 38 "google.golang.org/grpc/internal/grpcsync" 39 "google.golang.org/grpc/internal/hierarchy" 40 "google.golang.org/grpc/internal/pretty" 41 "google.golang.org/grpc/resolver" 42 "google.golang.org/grpc/serviceconfig" 43 ) 44 45 // Name is the name of the priority balancer. 46 const Name = "priority_experimental" 47 48 // DefaultSubBalancerCloseTimeout is defined as a variable instead of const for 49 // testing. 50 var DefaultSubBalancerCloseTimeout = 15 * time.Minute 51 52 func init() { 53 balancer.Register(bb{}) 54 } 55 56 type bb struct{} 57 58 func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer { 59 b := &priorityBalancer{ 60 cc: cc, 61 done: grpcsync.NewEvent(), 62 children: make(map[string]*childBalancer), 63 childBalancerStateUpdate: buffer.NewUnbounded(), 64 } 65 66 b.logger = prefixLogger(b) 67 b.bg = balancergroup.New(balancergroup.Options{ 68 CC: cc, 69 BuildOpts: bOpts, 70 StateAggregator: b, 71 Logger: b.logger, 72 SubBalancerCloseTimeout: DefaultSubBalancerCloseTimeout, 73 }) 74 go b.run() 75 b.logger.Infof("Created") 76 return b 77 } 78 79 func (b bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 80 return parseConfig(s) 81 } 82 83 func (bb) Name() string { 84 return Name 85 } 86 87 // timerWrapper wraps a timer with a boolean. So that when a race happens 88 // between AfterFunc and Stop, the func is guaranteed to not execute. 89 type timerWrapper struct { 90 stopped bool 91 timer *time.Timer 92 } 93 94 type priorityBalancer struct { 95 logger *grpclog.PrefixLogger 96 cc balancer.ClientConn 97 bg *balancergroup.BalancerGroup 98 done *grpcsync.Event 99 childBalancerStateUpdate *buffer.Unbounded 100 101 mu sync.Mutex 102 childInUse string 103 // priorities is a list of child names from higher to lower priority. 104 priorities []string 105 // children is a map from child name to sub-balancers. 106 children map[string]*childBalancer 107 108 // Set during UpdateClientConnState when calling into sub-balancers. 109 // Prevents child updates from recomputing the active priority or sending 110 // an update of the aggregated picker to the parent. Cleared after all 111 // sub-balancers have finished UpdateClientConnState, after which 112 // syncPriority is called manually. 113 inhibitPickerUpdates bool 114 } 115 116 func (b *priorityBalancer) UpdateClientConnState(s balancer.ClientConnState) error { 117 if b.logger.V(2) { 118 b.logger.Infof("Received an update with balancer config: %+v", pretty.ToJSON(s.BalancerConfig)) 119 } 120 newConfig, ok := s.BalancerConfig.(*LBConfig) 121 if !ok { 122 return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig) 123 } 124 addressesSplit := hierarchy.Group(s.ResolverState.Addresses) 125 endpointsSplit := hierarchy.GroupEndpoints(s.ResolverState.Endpoints) 126 127 b.mu.Lock() 128 // Create and remove children, since we know all children from the config 129 // are used by some priority. 130 for name, newSubConfig := range newConfig.Children { 131 bb := balancer.Get(newSubConfig.Config.Name) 132 if bb == nil { 133 b.logger.Errorf("balancer name %v from config is not registered", newSubConfig.Config.Name) 134 continue 135 } 136 137 currentChild, ok := b.children[name] 138 if !ok { 139 // This is a new child, add it to the children list. But note that 140 // the balancer isn't built, because this child can be a low 141 // priority. If necessary, it will be built when syncing priorities. 142 cb := newChildBalancer(name, b, bb.Name(), b.cc) 143 cb.updateConfig(newSubConfig, resolver.State{ 144 Addresses: addressesSplit[name], 145 Endpoints: endpointsSplit[name], 146 ServiceConfig: s.ResolverState.ServiceConfig, 147 Attributes: s.ResolverState.Attributes, 148 }) 149 b.children[name] = cb 150 continue 151 } 152 153 // This is not a new child. But the config/addresses could change. 154 155 // The balancing policy name is changed, close the old child. But don't 156 // rebuild, rebuild will happen when syncing priorities. 157 if currentChild.balancerName != bb.Name() { 158 currentChild.stop() 159 currentChild.updateBalancerName(bb.Name()) 160 } 161 162 // Update config and address, but note that this doesn't send the 163 // updates to non-started child balancers (the child balancer might not 164 // be built, if it's a low priority). 165 currentChild.updateConfig(newSubConfig, resolver.State{ 166 Addresses: addressesSplit[name], 167 Endpoints: endpointsSplit[name], 168 ServiceConfig: s.ResolverState.ServiceConfig, 169 Attributes: s.ResolverState.Attributes, 170 }) 171 } 172 // Cleanup resources used by children removed from the config. 173 for name, oldChild := range b.children { 174 if _, ok := newConfig.Children[name]; !ok { 175 oldChild.stop() 176 delete(b.children, name) 177 } 178 } 179 180 // Update priorities and handle priority changes. 181 b.priorities = newConfig.Priorities 182 183 // Everything was removed by the update. 184 if len(b.priorities) == 0 { 185 b.childInUse = "" 186 b.cc.UpdateState(balancer.State{ 187 ConnectivityState: connectivity.TransientFailure, 188 Picker: base.NewErrPicker(ErrAllPrioritiesRemoved), 189 }) 190 b.mu.Unlock() 191 return nil 192 } 193 194 // This will sync the states of all children to the new updated 195 // priorities. Includes starting/stopping child balancers when necessary. 196 // Block picker updates until all children have had a chance to call 197 // UpdateState to prevent races where, e.g., the active priority reports 198 // transient failure but a higher priority may have reported something that 199 // made it active, and if the transient failure update is handled first, 200 // RPCs could fail. 201 b.inhibitPickerUpdates = true 202 // Add an item to queue to notify us when the current items in the queue 203 // are done and syncPriority has been called. 204 done := make(chan struct{}) 205 b.childBalancerStateUpdate.Put(resumePickerUpdates{done: done}) 206 b.mu.Unlock() 207 <-done 208 209 return nil 210 } 211 212 func (b *priorityBalancer) ResolverError(err error) { 213 if b.logger.V(2) { 214 b.logger.Infof("Received error from the resolver: %v", err) 215 } 216 b.bg.ResolverError(err) 217 } 218 219 func (b *priorityBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 220 b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state) 221 } 222 223 func (b *priorityBalancer) Close() { 224 b.bg.Close() 225 b.childBalancerStateUpdate.Close() 226 227 b.mu.Lock() 228 defer b.mu.Unlock() 229 b.done.Fire() 230 // Clear states of the current child in use, so if there's a race in picker 231 // update, it will be dropped. 232 b.childInUse = "" 233 // Stop the child policies, this is necessary to stop the init timers in the 234 // children. 235 for _, child := range b.children { 236 child.stop() 237 } 238 } 239 240 func (b *priorityBalancer) ExitIdle() { 241 b.bg.ExitIdle() 242 } 243 244 // UpdateState implements balancergroup.BalancerStateAggregator interface. The 245 // balancer group sends new connectivity state and picker here. 246 func (b *priorityBalancer) UpdateState(childName string, state balancer.State) { 247 b.childBalancerStateUpdate.Put(childBalancerState{ 248 name: childName, 249 s: state, 250 }) 251 } 252 253 type childBalancerState struct { 254 name string 255 s balancer.State 256 } 257 258 type resumePickerUpdates struct { 259 done chan struct{} 260 } 261 262 // run handles child update in a separate goroutine, so if the child sends 263 // updates inline (when called by parent), it won't cause deadlocks (by trying 264 // to hold the same mutex). 265 func (b *priorityBalancer) run() { 266 for { 267 select { 268 case u, ok := <-b.childBalancerStateUpdate.Get(): 269 if !ok { 270 return 271 } 272 b.childBalancerStateUpdate.Load() 273 // Needs to handle state update in a goroutine, because each state 274 // update needs to start/close child policy, could result in 275 // deadlock. 276 b.mu.Lock() 277 if b.done.HasFired() { 278 b.mu.Unlock() 279 return 280 } 281 switch s := u.(type) { 282 case childBalancerState: 283 b.handleChildStateUpdate(s.name, s.s) 284 case resumePickerUpdates: 285 b.inhibitPickerUpdates = false 286 b.syncPriority(b.childInUse) 287 close(s.done) 288 } 289 b.mu.Unlock() 290 case <-b.done.Done(): 291 return 292 } 293 } 294 }