dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/balancer/priority/balancer_priority.go (about)

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