gitee.com/zhaochuninhefei/gmgo@v0.0.31-0.20240209061119-069254a02979/grpc/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  	"gitee.com/zhaochuninhefei/gmgo/grpc/balancer"
    26  	"gitee.com/zhaochuninhefei/gmgo/grpc/balancer/base"
    27  	"gitee.com/zhaochuninhefei/gmgo/grpc/connectivity"
    28  )
    29  
    30  var (
    31  	// ErrAllPrioritiesRemoved is returned by the picker when there's no priority available.
    32  	ErrAllPrioritiesRemoved = errors.New("no priority is provided, all priorities are removed")
    33  	// DefaultPriorityInitTimeout is the timeout after which if a priority is
    34  	// not READY, the next will be started. It's exported to be overridden by
    35  	// tests.
    36  	DefaultPriorityInitTimeout = 10 * time.Second
    37  )
    38  
    39  // syncPriority handles priority after a config update. It makes sure the
    40  // balancer state (started or not) is in sync with the priorities (even in
    41  // tricky cases where a child is moved from a priority to another).
    42  //
    43  // It's guaranteed that after this function returns:
    44  // - If some child is READY, it is childInUse, and all lower priorities are
    45  // closed.
    46  // - If some child is newly started(in Connecting for the first time), it is
    47  // childInUse, and all lower priorities are closed.
    48  // - Otherwise, the lowest priority is childInUse (none of the children is
    49  // ready, and the overall state is not ready).
    50  //
    51  // Steps:
    52  // - If all priorities were deleted, unset childInUse (to an empty string), and
    53  // set parent ClientConn to TransientFailure
    54  // - Otherwise, Scan all children from p0, and check balancer stats:
    55  //   - For any of the following cases:
    56  // 	   - If balancer is not started (not built), this is either a new child
    57  //       with high priority, or a new builder for an existing child.
    58  // 	   - If balancer is READY
    59  // 	   - If this is the lowest priority
    60  //   - do the following:
    61  //     - if this is not the old childInUse, override picker so old picker is no
    62  //       longer used.
    63  //     - switch to it (because all higher priorities are neither new or Ready)
    64  //     - forward the new addresses and config
    65  //
    66  // Caller must hold b.mu.
    67  func (b *priorityBalancer) syncPriority() {
    68  	// Everything was removed by the update.
    69  	if len(b.priorities) == 0 {
    70  		b.childInUse = ""
    71  		b.priorityInUse = 0
    72  		// Stop the init timer. This can happen if the only priority is removed
    73  		// shortly after it's added.
    74  		b.stopPriorityInitTimer()
    75  		b.cc.UpdateState(balancer.State{
    76  			ConnectivityState: connectivity.TransientFailure,
    77  			Picker:            base.NewErrPicker(ErrAllPrioritiesRemoved),
    78  		})
    79  		return
    80  	}
    81  
    82  	for p, name := range b.priorities {
    83  		child, ok := b.children[name]
    84  		if !ok {
    85  			b.logger.Errorf("child with name %q is not found in children", name)
    86  			continue
    87  		}
    88  
    89  		if !child.started ||
    90  			child.state.ConnectivityState == connectivity.Ready ||
    91  			child.state.ConnectivityState == connectivity.Idle ||
    92  			p == len(b.priorities)-1 {
    93  			if b.childInUse != "" && b.childInUse != child.name {
    94  				// childInUse was set and is different from this child, will
    95  				// change childInUse later. We need to update picker here
    96  				// immediately so parent stops using the old picker.
    97  				b.cc.UpdateState(child.state)
    98  			}
    99  			b.logger.Infof("switching to (%q, %v) in syncPriority", child.name, p)
   100  			b.switchToChild(child, p)
   101  			child.sendUpdate()
   102  			break
   103  		}
   104  	}
   105  }
   106  
   107  // Stop priorities [p+1, lowest].
   108  //
   109  // Caller must hold b.mu.
   110  func (b *priorityBalancer) stopSubBalancersLowerThanPriority(p int) {
   111  	for i := p + 1; i < len(b.priorities); i++ {
   112  		name := b.priorities[i]
   113  		child, ok := b.children[name]
   114  		if !ok {
   115  			b.logger.Errorf("child with name %q is not found in children", name)
   116  			continue
   117  		}
   118  		child.stop()
   119  	}
   120  }
   121  
   122  // switchToChild does the following:
   123  // - stop all child with lower priorities
   124  // - if childInUse is not this child
   125  //   - set childInUse to this child
   126  //   - stops init timer
   127  //   - if this child is not started, start it, and start a init timer
   128  //
   129  // Note that it does NOT send the current child state (picker) to the parent
   130  // ClientConn. The caller needs to send it if necessary.
   131  //
   132  // this can be called when
   133  // 1. first update, start p0
   134  // 2. an update moves a READY child from a lower priority to higher
   135  // 2. a different builder is updated for this child
   136  // 3. a high priority goes Failure, start next
   137  // 4. a high priority init timeout, start next
   138  //
   139  // Caller must hold b.mu.
   140  func (b *priorityBalancer) switchToChild(child *childBalancer, priority int) {
   141  	// Stop lower priorities even if childInUse is same as this child. It's
   142  	// possible this child was moved from a priority to another.
   143  	b.stopSubBalancersLowerThanPriority(priority)
   144  
   145  	// If this child is already in use, do nothing.
   146  	//
   147  	// This can happen:
   148  	// - all priorities are not READY, an config update always triggers switch
   149  	// to the lowest. In this case, the lowest child could still be connecting,
   150  	// so we don't stop the init timer.
   151  	// - a high priority is READY, an config update always triggers switch to
   152  	// it.
   153  	if b.childInUse == child.name && child.started {
   154  		return
   155  	}
   156  	b.childInUse = child.name
   157  	b.priorityInUse = priority
   158  
   159  	// Init timer is always for childInUse. Since we are switching to a
   160  	// different child, we will stop the init timer no matter what. If this
   161  	// child is not started, we will start the init timer later.
   162  	b.stopPriorityInitTimer()
   163  
   164  	if !child.started {
   165  		child.start()
   166  		// Need this local variable to capture timerW in the AfterFunc closure
   167  		// to check the stopped boolean.
   168  		timerW := &timerWrapper{}
   169  		b.priorityInitTimer = timerW
   170  		timerW.timer = time.AfterFunc(DefaultPriorityInitTimeout, func() {
   171  			b.mu.Lock()
   172  			defer b.mu.Unlock()
   173  			if timerW.stopped {
   174  				return
   175  			}
   176  			b.priorityInitTimer = nil
   177  			// Switch to the next priority if there's any.
   178  			if pNext := priority + 1; pNext < len(b.priorities) {
   179  				nameNext := b.priorities[pNext]
   180  				if childNext, ok := b.children[nameNext]; ok {
   181  					b.switchToChild(childNext, pNext)
   182  					childNext.sendUpdate()
   183  				}
   184  			}
   185  		})
   186  	}
   187  }
   188  
   189  // handleChildStateUpdate start/close priorities based on the connectivity
   190  // state.
   191  func (b *priorityBalancer) handleChildStateUpdate(childName string, s balancer.State) {
   192  	b.mu.Lock()
   193  	defer b.mu.Unlock()
   194  	if b.done.HasFired() {
   195  		return
   196  	}
   197  
   198  	priority, ok := b.childToPriority[childName]
   199  	if !ok {
   200  		b.logger.Errorf("priority: received picker update with unknown child %v", childName)
   201  		return
   202  	}
   203  
   204  	if b.childInUse == "" {
   205  		b.logger.Errorf("priority: no child is in use when picker update is received")
   206  		return
   207  	}
   208  
   209  	// priorityInUse is higher than this priority.
   210  	if b.priorityInUse < priority {
   211  		// Lower priorities should all be closed, this is an unexpected update.
   212  		// Can happen if the child policy sends an update after we tell it to
   213  		// close.
   214  		b.logger.Warningf("priority: received picker update from priority %v,  lower than priority in use %v", priority, b.priorityInUse)
   215  		return
   216  	}
   217  
   218  	// Update state in child. The updated picker will be sent to parent later if
   219  	// necessary.
   220  	child, ok := b.children[childName]
   221  	if !ok {
   222  		b.logger.Errorf("priority: child balancer not found for child %v, priority %v", childName, priority)
   223  		return
   224  	}
   225  	oldState := child.state.ConnectivityState
   226  	child.state = s
   227  
   228  	switch s.ConnectivityState {
   229  	case connectivity.Ready, connectivity.Idle:
   230  		// Note that idle is also handled as if it's Ready. It will close the
   231  		// lower priorities (which will be kept in a cache, not deleted), and
   232  		// new picks will use the Idle picker.
   233  		b.handlePriorityWithNewStateReady(child, priority)
   234  	case connectivity.TransientFailure:
   235  		b.handlePriorityWithNewStateTransientFailure(child, priority)
   236  	case connectivity.Connecting:
   237  		b.handlePriorityWithNewStateConnecting(child, priority, oldState)
   238  	default:
   239  		// New state is Shutdown, should never happen. Don't forward.
   240  	}
   241  }
   242  
   243  // handlePriorityWithNewStateReady handles state Ready from a higher or equal
   244  // priority.
   245  //
   246  // An update with state Ready:
   247  // - If it's from higher priority:
   248  //   - Switch to this priority
   249  //   - Forward the update
   250  // - If it's from priorityInUse:
   251  //   - Forward only
   252  //
   253  // Caller must make sure priorityInUse is not higher than priority.
   254  //
   255  // Caller must hold mu.
   256  func (b *priorityBalancer) handlePriorityWithNewStateReady(child *childBalancer, priority int) {
   257  	// If one priority higher or equal to priorityInUse goes Ready, stop the
   258  	// init timer. If update is from higher than priorityInUse, priorityInUse
   259  	// will be closed, and the init timer will become useless.
   260  	b.stopPriorityInitTimer()
   261  
   262  	// priorityInUse is lower than this priority, switch to this.
   263  	if b.priorityInUse > priority {
   264  		b.logger.Infof("Switching priority from %v to %v, because latter became Ready", b.priorityInUse, priority)
   265  		b.switchToChild(child, priority)
   266  	}
   267  	// Forward the update since it's READY.
   268  	b.cc.UpdateState(child.state)
   269  }
   270  
   271  // handlePriorityWithNewStateTransientFailure handles state TransientFailure
   272  // from a higher or equal priority.
   273  //
   274  // An update with state TransientFailure:
   275  // - If it's from a higher priority:
   276  //   - Do not forward, and do nothing
   277  // - If it's from priorityInUse:
   278  //   - If there's no lower:
   279  //     - Forward and do nothing else
   280  //   - If there's a lower priority:
   281  //     - Switch to the lower
   282  //     - Forward the lower child's state
   283  //     - Do NOT forward this update
   284  //
   285  // Caller must make sure priorityInUse is not higher than priority.
   286  //
   287  // Caller must hold mu.
   288  func (b *priorityBalancer) handlePriorityWithNewStateTransientFailure(child *childBalancer, priority int) {
   289  	// priorityInUse is lower than this priority, do nothing.
   290  	if b.priorityInUse > priority {
   291  		return
   292  	}
   293  	// priorityInUse sends a failure. Stop its init timer.
   294  	b.stopPriorityInitTimer()
   295  	priorityNext := priority + 1
   296  	if priorityNext >= len(b.priorities) {
   297  		// Forward this update.
   298  		b.cc.UpdateState(child.state)
   299  		return
   300  	}
   301  	b.logger.Infof("Switching priority from %v to %v, because former became TransientFailure", priority, priorityNext)
   302  	nameNext := b.priorities[priorityNext]
   303  	childNext := b.children[nameNext]
   304  	b.switchToChild(childNext, priorityNext)
   305  	b.cc.UpdateState(childNext.state)
   306  	childNext.sendUpdate()
   307  }
   308  
   309  // handlePriorityWithNewStateConnecting handles state Connecting from a higher
   310  // than or equal priority.
   311  //
   312  // An update with state Connecting:
   313  // - If it's from a higher priority
   314  //   - Do nothing
   315  // - If it's from priorityInUse, the behavior depends on previous state.
   316  //
   317  // When new state is Connecting, the behavior depends on previous state. If the
   318  // previous state was Ready, this is a transition out from Ready to Connecting.
   319  // Assuming there are multiple backends in the same priority, this mean we are
   320  // in a bad situation and we should failover to the next priority (Side note:
   321  // the current connectivity state aggregating algorithm (e.g. round-robin) is
   322  // not handling this right, because if many backends all go from Ready to
   323  // Connecting, the overall situation is more like TransientFailure, not
   324  // Connecting).
   325  //
   326  // If the previous state was Idle, we don't do anything special with failure,
   327  // and simply forward the update. The init timer should be in process, will
   328  // handle failover if it timeouts. If the previous state was TransientFailure,
   329  // we do not forward, because the lower priority is in use.
   330  //
   331  // Caller must make sure priorityInUse is not higher than priority.
   332  //
   333  // Caller must hold mu.
   334  func (b *priorityBalancer) handlePriorityWithNewStateConnecting(child *childBalancer, priority int, oldState connectivity.State) {
   335  	// priorityInUse is lower than this priority, do nothing.
   336  	if b.priorityInUse > priority {
   337  		return
   338  	}
   339  
   340  	switch oldState {
   341  	case connectivity.Ready:
   342  		// Handling transition from Ready to Connecting, is same as handling
   343  		// TransientFailure. There's no need to stop the init timer, because it
   344  		// should have been stopped when state turned Ready.
   345  		priorityNext := priority + 1
   346  		if priorityNext >= len(b.priorities) {
   347  			// Forward this update.
   348  			b.cc.UpdateState(child.state)
   349  			return
   350  		}
   351  		b.logger.Infof("Switching priority from %v to %v, because former became TransientFailure", priority, priorityNext)
   352  		nameNext := b.priorities[priorityNext]
   353  		childNext := b.children[nameNext]
   354  		b.switchToChild(childNext, priorityNext)
   355  		b.cc.UpdateState(childNext.state)
   356  		childNext.sendUpdate()
   357  	case connectivity.Idle:
   358  		b.cc.UpdateState(child.state)
   359  	default:
   360  		// Old state is Connecting, TransientFailure or Shutdown. Don't forward.
   361  	}
   362  }