...

Source file src/google.golang.org/grpc/xds/internal/balancer/priority/balancer_priority.go

Documentation: google.golang.org/grpc/xds/internal/balancer/priority

     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  		b.logger.Debugf("Skipping update from child policy %q", childUpdating)
    87  		return
    88  	}
    89  	for p, name := range b.priorities {
    90  		child, ok := b.children[name]
    91  		if !ok {
    92  			b.logger.Warningf("Priority name %q is not found in list of child policies", name)
    93  			continue
    94  		}
    95  
    96  		if !child.started ||
    97  			child.state.ConnectivityState == connectivity.Ready ||
    98  			child.state.ConnectivityState == connectivity.Idle ||
    99  			(child.state.ConnectivityState == connectivity.Connecting && child.initTimer != nil) ||
   100  			p == len(b.priorities)-1 {
   101  			if b.childInUse != child.name || child.name == childUpdating {
   102  				b.logger.Debugf("childInUse, childUpdating: %q, %q", b.childInUse, child.name)
   103  				// If we switch children or the child in use just updated its
   104  				// picker, push the child's picker to the parent.
   105  				b.cc.UpdateState(child.state)
   106  			}
   107  			b.logger.Debugf("Switching to (%q, %v) in syncPriority", child.name, p)
   108  			b.switchToChild(child, p)
   109  			break
   110  		}
   111  	}
   112  }
   113  
   114  // Stop priorities [p+1, lowest].
   115  //
   116  // Caller must hold b.mu.
   117  func (b *priorityBalancer) stopSubBalancersLowerThanPriority(p int) {
   118  	for i := p + 1; i < len(b.priorities); i++ {
   119  		name := b.priorities[i]
   120  		child, ok := b.children[name]
   121  		if !ok {
   122  			b.logger.Warningf("Priority name %q is not found in list of child policies", name)
   123  			continue
   124  		}
   125  		child.stop()
   126  	}
   127  }
   128  
   129  // switchToChild does the following:
   130  // - stop all child with lower priorities
   131  // - if childInUse is not this child
   132  //   - set childInUse to this child
   133  //   - if this child is not started, start it
   134  //
   135  // Note that it does NOT send the current child state (picker) to the parent
   136  // ClientConn. The caller needs to send it if necessary.
   137  //
   138  // this can be called when
   139  // 1. first update, start p0
   140  // 2. an update moves a READY child from a lower priority to higher
   141  // 2. a different builder is updated for this child
   142  // 3. a high priority goes Failure, start next
   143  // 4. a high priority init timeout, start next
   144  //
   145  // Caller must hold b.mu.
   146  func (b *priorityBalancer) switchToChild(child *childBalancer, priority int) {
   147  	// Stop lower priorities even if childInUse is same as this child. It's
   148  	// possible this child was moved from a priority to another.
   149  	b.stopSubBalancersLowerThanPriority(priority)
   150  
   151  	// If this child is already in use, do nothing.
   152  	//
   153  	// This can happen:
   154  	// - all priorities are not READY, an config update always triggers switch
   155  	// to the lowest. In this case, the lowest child could still be connecting,
   156  	// so we don't stop the init timer.
   157  	// - a high priority is READY, an config update always triggers switch to
   158  	// it.
   159  	if b.childInUse == child.name && child.started {
   160  		return
   161  	}
   162  	b.childInUse = child.name
   163  
   164  	if !child.started {
   165  		child.start()
   166  	}
   167  }
   168  
   169  // handleChildStateUpdate start/close priorities based on the connectivity
   170  // state.
   171  func (b *priorityBalancer) handleChildStateUpdate(childName string, s balancer.State) {
   172  	// Update state in child. The updated picker will be sent to parent later if
   173  	// necessary.
   174  	child, ok := b.children[childName]
   175  	if !ok {
   176  		b.logger.Warningf("Child policy not found for %q", childName)
   177  		return
   178  	}
   179  	if !child.started {
   180  		b.logger.Warningf("Ignoring update from child policy %q which is not in started state: %+v", childName, s)
   181  		return
   182  	}
   183  	child.state = s
   184  
   185  	// We start/stop the init timer of this child based on the new connectivity
   186  	// state. syncPriority() later will need the init timer (to check if it's
   187  	// nil or not) to decide which child to switch to.
   188  	switch s.ConnectivityState {
   189  	case connectivity.Ready, connectivity.Idle:
   190  		child.reportedTF = false
   191  		child.stopInitTimer()
   192  	case connectivity.TransientFailure:
   193  		child.reportedTF = true
   194  		child.stopInitTimer()
   195  	case connectivity.Connecting:
   196  		if !child.reportedTF {
   197  			child.startInitTimer()
   198  		}
   199  	default:
   200  		// New state is Shutdown, should never happen. Don't forward.
   201  	}
   202  
   203  	child.parent.syncPriority(childName)
   204  }
   205  

View as plain text