github.com/bazelbuild/remote-apis-sdks@v0.0.0-20240425170053-8a36686a6350/go/pkg/balancer/gcp_balancer.go (about) 1 // Package balancer is a forked version of https://github.com/GoogleCloudPlatform/grpc-gcp-go. 2 package balancer 3 4 import ( 5 "sync" 6 "sync/atomic" 7 8 "google.golang.org/grpc/balancer" 9 10 "google.golang.org/grpc/connectivity" 11 "google.golang.org/grpc/grpclog" 12 "google.golang.org/grpc/resolver" 13 ) 14 15 const ( 16 // Name is the name of grpc_gcp balancer. 17 Name = "grpc_gcp" 18 19 healthCheckEnabled = true 20 ) 21 22 var ( 23 // DefaultMinConnections is the default number of gRPC sub-connections the 24 // gRPC balancer should create during SDK initialization. 25 DefaultMinConnections = 5 26 27 // MinConnections is the minimum number of gRPC sub-connections the gRPC balancer 28 // should create during SDK initialization. 29 // It is initialized in flags package. 30 MinConnections = DefaultMinConnections 31 ) 32 33 func init() { 34 balancer.Register(newBuilder()) 35 } 36 37 type gcpBalancerBuilder struct { 38 name string 39 } 40 41 // Build returns a grpc balancer initialized with given build options. 42 func (bb *gcpBalancerBuilder) Build( 43 cc balancer.ClientConn, 44 opt balancer.BuildOptions, 45 ) balancer.Balancer { 46 return &gcpBalancer{ 47 cc: cc, 48 affinityMap: make(map[string]balancer.SubConn), 49 scRefs: make(map[balancer.SubConn]*subConnRef), 50 scStates: make(map[balancer.SubConn]connectivity.State), 51 csEvltr: &connectivityStateEvaluator{}, 52 // Initialize picker to a picker that always return 53 // ErrNoSubConnAvailable, because when state of a SubConn changes, we 54 // may call UpdateState with this picker. 55 picker: newErrPicker(balancer.ErrNoSubConnAvailable), 56 } 57 } 58 59 // Name returns the name of the balancer. 60 func (*gcpBalancerBuilder) Name() string { 61 return Name 62 } 63 64 // newBuilder creates a new grpcgcp balancer builder. 65 func newBuilder() balancer.Builder { 66 return &gcpBalancerBuilder{ 67 name: Name, 68 } 69 } 70 71 // connectivityStateEvaluator gets updated by addrConns when their 72 // states transition, based on which it evaluates the state of 73 // ClientConn. 74 type connectivityStateEvaluator struct { 75 numReady uint64 // Number of addrConns in ready state. 76 numConnecting uint64 // Number of addrConns in connecting state. 77 numTransientFailure uint64 // Number of addrConns in transientFailure. 78 } 79 80 // recordTransition records state change happening in every subConn and based on 81 // that it evaluates what aggregated state should be. 82 // It can only transition between Ready, Connecting and TransientFailure. Other states, 83 // Idle and Shutdown are transitioned into by ClientConn; in the beginning of the connection 84 // before any subConn is created ClientConn is in idle state. In the end when ClientConn 85 // closes it is in Shutdown state. 86 // 87 // recordTransition should only be called synchronously from the same goroutine. 88 func (cse *connectivityStateEvaluator) recordTransition( 89 oldState, 90 newState connectivity.State, 91 ) connectivity.State { 92 // Update counters. 93 for idx, state := range []connectivity.State{oldState, newState} { 94 updateVal := 2*uint64(idx) - 1 // -1 for oldState and +1 for new. 95 switch state { 96 case connectivity.Ready: 97 cse.numReady += updateVal 98 case connectivity.Connecting: 99 cse.numConnecting += updateVal 100 case connectivity.TransientFailure: 101 cse.numTransientFailure += updateVal 102 } 103 } 104 105 // Evaluate. 106 if cse.numReady > 0 { 107 return connectivity.Ready 108 } 109 if cse.numConnecting > 0 { 110 return connectivity.Connecting 111 } 112 return connectivity.TransientFailure 113 } 114 115 // subConnRef keeps reference to the real SubConn with its 116 // connectivity state, affinity count and streams count. 117 type subConnRef struct { 118 subConn balancer.SubConn 119 affinityCnt int32 // Keeps track of the number of keys bound to the subConn 120 streamsCnt int32 // Keeps track of the number of streams opened on the subConn 121 } 122 123 func (ref *subConnRef) getAffinityCnt() int32 { 124 return atomic.LoadInt32(&ref.affinityCnt) 125 } 126 127 func (ref *subConnRef) getStreamsCnt() int32 { 128 return atomic.LoadInt32(&ref.streamsCnt) 129 } 130 131 func (ref *subConnRef) affinityIncr() { 132 atomic.AddInt32(&ref.affinityCnt, 1) 133 } 134 135 func (ref *subConnRef) affinityDecr() { 136 atomic.AddInt32(&ref.affinityCnt, -1) 137 } 138 139 func (ref *subConnRef) streamsIncr() { 140 atomic.AddInt32(&ref.streamsCnt, 1) 141 } 142 143 func (ref *subConnRef) streamsDecr() { 144 atomic.AddInt32(&ref.streamsCnt, -1) 145 } 146 147 type gcpBalancer struct { 148 balancer.Balancer // Embed V1 Balancer so it compiles with Builder 149 addrs []resolver.Address 150 cc balancer.ClientConn 151 csEvltr *connectivityStateEvaluator 152 state connectivity.State 153 154 mu sync.RWMutex 155 affinityMap map[string]balancer.SubConn 156 scStates map[balancer.SubConn]connectivity.State 157 scRefs map[balancer.SubConn]*subConnRef 158 159 picker balancer.Picker 160 } 161 162 func (gb *gcpBalancer) UpdateClientConnState(ccs balancer.ClientConnState) error { 163 addrs := ccs.ResolverState.Addresses 164 gb.addrs = addrs 165 166 createdSubCons := false 167 for len(gb.scRefs) < MinConnections { 168 gb.newSubConn() 169 createdSubCons = true 170 } 171 if createdSubCons { 172 return nil 173 } 174 175 for _, scRef := range gb.scRefs { 176 scRef.subConn.UpdateAddresses(addrs) 177 scRef.subConn.Connect() 178 } 179 180 return nil 181 } 182 183 func (gb *gcpBalancer) ResolverError(err error) { 184 grpclog.Warningf( 185 "grpcgcp.gcpBalancer: ResolverError: %v", 186 err, 187 ) 188 } 189 190 // check current connection pool size 191 func (gb *gcpBalancer) getConnectionPoolSize() int { 192 gb.mu.Lock() 193 defer gb.mu.Unlock() 194 return len(gb.scRefs) 195 } 196 197 // newSubConn creates a new SubConn using cc.NewSubConn and initialize the subConnRef. 198 func (gb *gcpBalancer) newSubConn() { 199 gb.mu.Lock() 200 defer gb.mu.Unlock() 201 202 // there are chances the newly created subconns are still connecting, 203 // we can wait on those new subconns. 204 for _, scState := range gb.scStates { 205 if scState == connectivity.Connecting { 206 return 207 } 208 } 209 210 sc, err := gb.cc.NewSubConn( 211 gb.addrs, 212 balancer.NewSubConnOptions{HealthCheckEnabled: healthCheckEnabled}, 213 ) 214 if err != nil { 215 grpclog.Errorf("grpcgcp.gcpBalancer: failed to NewSubConn: %v", err) 216 return 217 } 218 gb.scRefs[sc] = &subConnRef{ 219 subConn: sc, 220 } 221 gb.scStates[sc] = connectivity.Idle 222 sc.Connect() 223 } 224 225 // getReadySubConnRef returns a subConnRef and a bool. The bool indicates whether 226 // the boundKey exists in the affinityMap. If returned subConnRef is a nil, it 227 // means the underlying subconn is not READY yet. 228 func (gb *gcpBalancer) getReadySubConnRef(boundKey string) (*subConnRef, bool) { 229 gb.mu.RLock() 230 defer gb.mu.RUnlock() 231 232 if sc, ok := gb.affinityMap[boundKey]; ok { 233 if gb.scStates[sc] != connectivity.Ready { 234 // It's possible that the bound subconn is not in the readySubConns list, 235 // If it's not ready yet, we throw ErrNoSubConnAvailable 236 return nil, true 237 } 238 return gb.scRefs[sc], true 239 } 240 return nil, false 241 } 242 243 // bindSubConn binds the given affinity key to an existing subConnRef. 244 func (gb *gcpBalancer) bindSubConn(bindKey string, sc balancer.SubConn) { 245 gb.mu.Lock() 246 defer gb.mu.Unlock() 247 _, ok := gb.affinityMap[bindKey] 248 if !ok { 249 gb.affinityMap[bindKey] = sc 250 } 251 gb.scRefs[sc].affinityIncr() 252 } 253 254 // unbindSubConn removes the existing binding associated with the key. 255 func (gb *gcpBalancer) unbindSubConn(boundKey string) { 256 gb.mu.Lock() 257 defer gb.mu.Unlock() 258 boundSC, ok := gb.affinityMap[boundKey] 259 if ok { 260 gb.scRefs[boundSC].affinityDecr() 261 if gb.scRefs[boundSC].getAffinityCnt() <= 0 { 262 delete(gb.affinityMap, boundKey) 263 } 264 } 265 } 266 267 // regeneratePicker takes a snapshot of the balancer, and generates a picker 268 // from it. The picker is 269 // - errPicker with ErrTransientFailure if the balancer is in TransientFailure, 270 // - built by the pickerBuilder with all READY SubConns otherwise. 271 func (gb *gcpBalancer) regeneratePicker() { 272 gb.mu.RLock() 273 defer gb.mu.RUnlock() 274 275 if gb.state == connectivity.TransientFailure { 276 gb.picker = newErrPicker(balancer.ErrTransientFailure) 277 return 278 } 279 readyRefs := []*subConnRef{} 280 281 // Select ready subConns from subConn map. 282 for sc, scState := range gb.scStates { 283 if scState == connectivity.Ready { 284 readyRefs = append(readyRefs, gb.scRefs[sc]) 285 } 286 } 287 gb.picker = newGCPPicker(readyRefs, gb) 288 } 289 290 func (gb *gcpBalancer) UpdateSubConnState(sc balancer.SubConn, scs balancer.SubConnState) { 291 s := scs.ConnectivityState 292 grpclog.Infof("grpcgcp.gcpBalancer: handle SubConn state change: %p, %v", sc, s) 293 294 gb.mu.Lock() 295 oldS, ok := gb.scStates[sc] 296 if !ok { 297 grpclog.Infof( 298 "grpcgcp.gcpBalancer: got state changes for an unknown SubConn: %p, %v", 299 sc, 300 s, 301 ) 302 gb.mu.Unlock() 303 return 304 } 305 gb.scStates[sc] = s 306 switch s { 307 case connectivity.Idle: 308 sc.Connect() 309 case connectivity.Shutdown: 310 delete(gb.scRefs, sc) 311 delete(gb.scStates, sc) 312 } 313 gb.mu.Unlock() 314 315 oldAggrState := gb.state 316 gb.state = gb.csEvltr.recordTransition(oldS, s) 317 318 // Regenerate picker when one of the following happens: 319 // - this sc became ready from not-ready 320 // - this sc became not-ready from ready 321 // - the aggregated state of balancer became TransientFailure from non-TransientFailure 322 // - the aggregated state of balancer became non-TransientFailure from TransientFailure 323 if (s == connectivity.Ready) != (oldS == connectivity.Ready) || 324 (gb.state == connectivity.TransientFailure) != (oldAggrState == connectivity.TransientFailure) { 325 gb.regeneratePicker() 326 gb.cc.UpdateState(balancer.State{ConnectivityState: gb.state, Picker: gb.picker}) 327 } 328 } 329 330 func (gb *gcpBalancer) Close() { 331 }