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  }