google.golang.org/grpc@v1.72.2/xds/internal/balancer/priority/balancer_priority.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
    20  
    21  import (
    22  	"errors"
    23  	"time"
    24  
    25  	"google.golang.org/grpc/balancer"
    26  	"google.golang.org/grpc/connectivity"
    27  )
    28  
    29  var (
    30  	// ErrAllPrioritiesRemoved is returned by the picker when there's no priority available.
    31  	ErrAllPrioritiesRemoved = errors.New("no priority is provided, all priorities are removed")
    32  	// DefaultPriorityInitTimeout is the timeout after which if a priority is
    33  	// not READY, the next will be started. It's exported to be overridden by
    34  	// tests.
    35  	DefaultPriorityInitTimeout = 10 * time.Second
    36  )
    37  
    38  // syncPriority handles priority after a config update or a child balancer
    39  // connectivity state update. It makes sure the balancer state (started or not)
    40  // is in sync with the priorities (even in tricky cases where a child is moved
    41  // from a priority to another).
    42  //
    43  // It's guaranteed that after this function returns:
    44  //
    45  //	If some child is READY, it is childInUse, and all lower priorities are
    46  //	closed.
    47  //
    48  //	If some child is newly started(in Connecting for the first time), it is
    49  //	childInUse, and all lower priorities are closed.
    50  //
    51  //	Otherwise, the lowest priority is childInUse (none of the children is
    52  //	ready, and the overall state is not ready).
    53  //
    54  // Steps:
    55  //
    56  //	If all priorities were deleted, unset childInUse (to an empty string), and
    57  //	set parent ClientConn to TransientFailure
    58  //
    59  //	Otherwise, Scan all children from p0, and check balancer stats:
    60  //
    61  //	  For any of the following cases:
    62  //
    63  //	    If balancer is not started (not built), this is either a new child with
    64  //	    high priority, or a new builder for an existing child.
    65  //
    66  //	    If balancer is Connecting and has non-nil initTimer (meaning it
    67  //	    transitioned from Ready or Idle to connecting, not from TF, so we
    68  //	    should give it init-time to connect).
    69  //
    70  //	    If balancer is READY or IDLE
    71  //
    72  //	    If this is the lowest priority
    73  //
    74  //	 do the following:
    75  //
    76  //	    if this is not the old childInUse, override picker so old picker is no
    77  //	    longer used.
    78  //
    79  //	    switch to it (because all higher priorities are neither new or Ready)
    80  //
    81  //	    forward the new addresses and config
    82  //
    83  // Caller must hold b.mu.
    84  func (b *priorityBalancer) syncPriority(childUpdating string) {
    85  	if b.inhibitPickerUpdates {
    86  		if b.logger.V(2) {
    87  			b.logger.Infof("Skipping update from child policy %q", childUpdating)
    88  		}
    89  		return
    90  	}
    91  	for p, name := range b.priorities {
    92  		child, ok := b.children[name]
    93  		if !ok {
    94  			b.logger.Warningf("Priority name %q is not found in list of child policies", name)
    95  			continue
    96  		}
    97  
    98  		if !child.started ||
    99  			child.state.ConnectivityState == connectivity.Ready ||
   100  			child.state.ConnectivityState == connectivity.Idle ||
   101  			(child.state.ConnectivityState == connectivity.Connecting && child.initTimer != nil) ||
   102  			p == len(b.priorities)-1 {
   103  			if b.childInUse != child.name || child.name == childUpdating {
   104  				if b.logger.V(2) {
   105  					b.logger.Infof("childInUse, childUpdating: %q, %q", b.childInUse, child.name)
   106  				}
   107  				// If we switch children or the child in use just updated its
   108  				// picker, push the child's picker to the parent.
   109  				b.cc.UpdateState(child.state)
   110  			}
   111  			if b.logger.V(2) {
   112  				b.logger.Infof("Switching to (%q, %v) in syncPriority", child.name, p)
   113  			}
   114  			b.switchToChild(child, p)
   115  			break
   116  		}
   117  	}
   118  }
   119  
   120  // Stop priorities [p+1, lowest].
   121  //
   122  // Caller must hold b.mu.
   123  func (b *priorityBalancer) stopSubBalancersLowerThanPriority(p int) {
   124  	for i := p + 1; i < len(b.priorities); i++ {
   125  		name := b.priorities[i]
   126  		child, ok := b.children[name]
   127  		if !ok {
   128  			b.logger.Warningf("Priority name %q is not found in list of child policies", name)
   129  			continue
   130  		}
   131  		child.stop()
   132  	}
   133  }
   134  
   135  // switchToChild does the following:
   136  // - stop all child with lower priorities
   137  // - if childInUse is not this child
   138  //   - set childInUse to this child
   139  //   - if this child is not started, start it
   140  //
   141  // Note that it does NOT send the current child state (picker) to the parent
   142  // ClientConn. The caller needs to send it if necessary.
   143  //
   144  // this can be called when
   145  // 1. first update, start p0
   146  // 2. an update moves a READY child from a lower priority to higher
   147  // 2. a different builder is updated for this child
   148  // 3. a high priority goes Failure, start next
   149  // 4. a high priority init timeout, start next
   150  //
   151  // Caller must hold b.mu.
   152  func (b *priorityBalancer) switchToChild(child *childBalancer, priority int) {
   153  	// Stop lower priorities even if childInUse is same as this child. It's
   154  	// possible this child was moved from a priority to another.
   155  	b.stopSubBalancersLowerThanPriority(priority)
   156  
   157  	// If this child is already in use, do nothing.
   158  	//
   159  	// This can happen:
   160  	// - all priorities are not READY, an config update always triggers switch
   161  	// to the lowest. In this case, the lowest child could still be connecting,
   162  	// so we don't stop the init timer.
   163  	// - a high priority is READY, an config update always triggers switch to
   164  	// it.
   165  	if b.childInUse == child.name && child.started {
   166  		return
   167  	}
   168  	b.childInUse = child.name
   169  
   170  	if !child.started {
   171  		child.start()
   172  	}
   173  }
   174  
   175  // handleChildStateUpdate start/close priorities based on the connectivity
   176  // state.
   177  func (b *priorityBalancer) handleChildStateUpdate(childName string, s balancer.State) {
   178  	// Update state in child. The updated picker will be sent to parent later if
   179  	// necessary.
   180  	child, ok := b.children[childName]
   181  	if !ok {
   182  		b.logger.Warningf("Child policy not found for %q", childName)
   183  		return
   184  	}
   185  	if !child.started {
   186  		b.logger.Warningf("Ignoring update from child policy %q which is not in started state: %+v", childName, s)
   187  		return
   188  	}
   189  	child.state = s
   190  
   191  	// We start/stop the init timer of this child based on the new connectivity
   192  	// state. syncPriority() later will need the init timer (to check if it's
   193  	// nil or not) to decide which child to switch to.
   194  	switch s.ConnectivityState {
   195  	case connectivity.Ready, connectivity.Idle:
   196  		child.reportedTF = false
   197  		child.stopInitTimer()
   198  	case connectivity.TransientFailure:
   199  		child.reportedTF = true
   200  		child.stopInitTimer()
   201  	case connectivity.Connecting:
   202  		if !child.reportedTF {
   203  			child.startInitTimer()
   204  		}
   205  	default:
   206  		// New state is Shutdown, should never happen. Don't forward.
   207  	}
   208  
   209  	child.parent.syncPriority(childName)
   210  }