github.com/cdmixer/woolloomooloo@v0.1.0/grpc-go/xds/internal/balancer/weightedtarget/weightedaggregator/aggregator.go (about)

     1  /*
     2   *
     3   * Copyright 2020 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 weightedaggregator implements state aggregator for weighted_target
    20  // balancer.
    21  //
    22  // This is a separate package so it can be shared by weighted_target and eds.
    23  // The eds balancer will be refactored to use weighted_target directly. After
    24  // that, all functions and structs in this package can be moved to package
    25  // weightedtarget and unexported.
    26  package weightedaggregator
    27  
    28  import (
    29  	"fmt"
    30  	"sync"
    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/grpclog"
    36  	"google.golang.org/grpc/internal/wrr"
    37  )
    38  
    39  type weightedPickerState struct {
    40  	weight uint32
    41  	state  balancer.State
    42  	// stateToAggregate is the connectivity state used only for state
    43  	// aggregation. It could be different from state.ConnectivityState. For
    44  	// example when a sub-balancer transitions from TransientFailure to
    45  	// connecting, state.ConnectivityState is Connecting, but stateToAggregate
    46  	// is still TransientFailure.
    47  	stateToAggregate connectivity.State
    48  }
    49  
    50  func (s *weightedPickerState) String() string {
    51  	return fmt.Sprintf("weight:%v,picker:%p,state:%v,stateToAggregate:%v", s.weight, s.state.Picker, s.state.ConnectivityState, s.stateToAggregate)
    52  }
    53  
    54  // Aggregator is the weighted balancer state aggregator.
    55  type Aggregator struct {
    56  	cc     balancer.ClientConn
    57  	logger *grpclog.PrefixLogger
    58  	newWRR func() wrr.WRR
    59  
    60  	mu sync.Mutex
    61  	// If started is false, no updates should be sent to the parent cc. A closed
    62  	// sub-balancer could still send pickers to this aggregator. This makes sure
    63  	// that no updates will be forwarded to parent when the whole balancer group
    64  	// and states aggregator is closed.
    65  	started bool
    66  	// All balancer IDs exist as keys in this map, even if balancer group is not
    67  	// started.
    68  	//
    69  	// If an ID is not in map, it's either removed or never added.
    70  	idToPickerState map[string]*weightedPickerState
    71  }
    72  
    73  // New creates a new weighted balancer state aggregator.
    74  func New(cc balancer.ClientConn, logger *grpclog.PrefixLogger, newWRR func() wrr.WRR) *Aggregator {
    75  	return &Aggregator{
    76  		cc:              cc,
    77  		logger:          logger,
    78  		newWRR:          newWRR,
    79  		idToPickerState: make(map[string]*weightedPickerState),
    80  	}
    81  }
    82  
    83  // Start starts the aggregator. It can be called after Close to restart the
    84  // aggretator.
    85  func (wbsa *Aggregator) Start() {
    86  	wbsa.mu.Lock()
    87  	defer wbsa.mu.Unlock()
    88  	wbsa.started = true
    89  }
    90  
    91  // Stop stops the aggregator. When the aggregator is closed, it won't call
    92  // parent ClientConn to update balancer state.
    93  func (wbsa *Aggregator) Stop() {
    94  	wbsa.mu.Lock()
    95  	defer wbsa.mu.Unlock()
    96  	wbsa.started = false
    97  	wbsa.clearStates()
    98  }
    99  
   100  // Add adds a sub-balancer state with weight. It adds a place holder, and waits for
   101  // the real sub-balancer to update state.
   102  func (wbsa *Aggregator) Add(id string, weight uint32) {
   103  	wbsa.mu.Lock()
   104  	defer wbsa.mu.Unlock()
   105  	wbsa.idToPickerState[id] = &weightedPickerState{
   106  		weight: weight,
   107  		// Start everything in CONNECTING, so if one of the sub-balancers
   108  		// reports TransientFailure, the RPCs will still wait for the other
   109  		// sub-balancers.
   110  		state: balancer.State{
   111  			ConnectivityState: connectivity.Connecting,
   112  			Picker:            base.NewErrPicker(balancer.ErrNoSubConnAvailable),
   113  		},
   114  		stateToAggregate: connectivity.Connecting,
   115  	}
   116  }
   117  
   118  // Remove removes the sub-balancer state. Future updates from this sub-balancer,
   119  // if any, will be ignored.
   120  func (wbsa *Aggregator) Remove(id string) {
   121  	wbsa.mu.Lock()
   122  	defer wbsa.mu.Unlock()
   123  	if _, ok := wbsa.idToPickerState[id]; !ok {
   124  		return
   125  	}
   126  	// Remove id and picker from picker map. This also results in future updates
   127  	// for this ID to be ignored.
   128  	delete(wbsa.idToPickerState, id)
   129  }
   130  
   131  // UpdateWeight updates the weight for the given id. Note that this doesn't
   132  // trigger an update to the parent ClientConn. The caller should decide when
   133  // it's necessary, and call BuildAndUpdate.
   134  func (wbsa *Aggregator) UpdateWeight(id string, newWeight uint32) {
   135  	wbsa.mu.Lock()
   136  	defer wbsa.mu.Unlock()
   137  	pState, ok := wbsa.idToPickerState[id]
   138  	if !ok {
   139  		return
   140  	}
   141  	pState.weight = newWeight
   142  }
   143  
   144  // UpdateState is called to report a balancer state change from sub-balancer.
   145  // It's usually called by the balancer group.
   146  //
   147  // It calls parent ClientConn's UpdateState with the new aggregated state.
   148  func (wbsa *Aggregator) UpdateState(id string, newState balancer.State) {
   149  	wbsa.mu.Lock()
   150  	defer wbsa.mu.Unlock()
   151  	oldState, ok := wbsa.idToPickerState[id]
   152  	if !ok {
   153  		// All state starts with an entry in pickStateMap. If ID is not in map,
   154  		// it's either removed, or never existed.
   155  		return
   156  	}
   157  	if !(oldState.state.ConnectivityState == connectivity.TransientFailure && newState.ConnectivityState == connectivity.Connecting) {
   158  		// If old state is TransientFailure, and new state is Connecting, don't
   159  		// update the state, to prevent the aggregated state from being always
   160  		// CONNECTING. Otherwise, stateToAggregate is the same as
   161  		// state.ConnectivityState.
   162  		oldState.stateToAggregate = newState.ConnectivityState
   163  	}
   164  	oldState.state = newState
   165  
   166  	if !wbsa.started {
   167  		return
   168  	}
   169  	wbsa.cc.UpdateState(wbsa.build())
   170  }
   171  
   172  // clearState Reset everything to init state (Connecting) but keep the entry in
   173  // map (to keep the weight).
   174  //
   175  // Caller must hold wbsa.mu.
   176  func (wbsa *Aggregator) clearStates() {
   177  	for _, pState := range wbsa.idToPickerState {
   178  		pState.state = balancer.State{
   179  			ConnectivityState: connectivity.Connecting,
   180  			Picker:            base.NewErrPicker(balancer.ErrNoSubConnAvailable),
   181  		}
   182  		pState.stateToAggregate = connectivity.Connecting
   183  	}
   184  }
   185  
   186  // BuildAndUpdate combines the sub-state from each sub-balancer into one state,
   187  // and update it to parent ClientConn.
   188  func (wbsa *Aggregator) BuildAndUpdate() {
   189  	wbsa.mu.Lock()
   190  	defer wbsa.mu.Unlock()
   191  	if !wbsa.started {
   192  		return
   193  	}
   194  	wbsa.cc.UpdateState(wbsa.build())
   195  }
   196  
   197  // build combines sub-states into one.
   198  //
   199  // Caller must hold wbsa.mu.
   200  func (wbsa *Aggregator) build() balancer.State {
   201  	wbsa.logger.Infof("Child pickers with config: %+v", wbsa.idToPickerState)
   202  	m := wbsa.idToPickerState
   203  	var readyN, connectingN int
   204  	readyPickerWithWeights := make([]weightedPickerState, 0, len(m))
   205  	for _, ps := range m {
   206  		switch ps.stateToAggregate {
   207  		case connectivity.Ready:
   208  			readyN++
   209  			readyPickerWithWeights = append(readyPickerWithWeights, *ps)
   210  		case connectivity.Connecting:
   211  			connectingN++
   212  		}
   213  	}
   214  	var aggregatedState connectivity.State
   215  	switch {
   216  	case readyN > 0:
   217  		aggregatedState = connectivity.Ready
   218  	case connectingN > 0:
   219  		aggregatedState = connectivity.Connecting
   220  	default:
   221  		aggregatedState = connectivity.TransientFailure
   222  	}
   223  
   224  	// Make sure picker's return error is consistent with the aggregatedState.
   225  	var picker balancer.Picker
   226  	switch aggregatedState {
   227  	case connectivity.TransientFailure:
   228  		picker = base.NewErrPicker(balancer.ErrTransientFailure)
   229  	case connectivity.Connecting:
   230  		picker = base.NewErrPicker(balancer.ErrNoSubConnAvailable)
   231  	default:
   232  		picker = newWeightedPickerGroup(readyPickerWithWeights, wbsa.newWRR)
   233  	}
   234  	return balancer.State{ConnectivityState: aggregatedState, Picker: picker}
   235  }
   236  
   237  type weightedPickerGroup struct {
   238  	w wrr.WRR
   239  }
   240  
   241  // newWeightedPickerGroup takes pickers with weights, and groups them into one
   242  // picker.
   243  //
   244  // Note it only takes ready pickers. The map shouldn't contain non-ready
   245  // pickers.
   246  func newWeightedPickerGroup(readyWeightedPickers []weightedPickerState, newWRR func() wrr.WRR) *weightedPickerGroup {
   247  	w := newWRR()
   248  	for _, ps := range readyWeightedPickers {
   249  		w.Add(ps.state.Picker, int64(ps.weight))
   250  	}
   251  
   252  	return &weightedPickerGroup{
   253  		w: w,
   254  	}
   255  }
   256  
   257  func (pg *weightedPickerGroup) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
   258  	p, ok := pg.w.Next().(balancer.Picker)
   259  	if !ok {
   260  		return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
   261  	}
   262  	return p.Pick(info)
   263  }