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  }