snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
This commit is contained in:
223
internal/xds/balancer/priority/balancer_priority.go
Normal file
223
internal/xds/balancer/priority/balancer_priority.go
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package priority
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/balancer"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAllPrioritiesRemoved is returned by the picker when there's no priority available.
|
||||
ErrAllPrioritiesRemoved = errors.New("no priority is provided, all priorities are removed")
|
||||
// DefaultPriorityInitTimeout is the timeout after which if a priority is
|
||||
// not READY, the next will be started. It's exported to be overridden by
|
||||
// tests.
|
||||
DefaultPriorityInitTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
// syncPriority handles priority after a config update or a child balancer
|
||||
// connectivity state update. It makes sure the balancer state (started or not)
|
||||
// is in sync with the priorities (even in tricky cases where a child is moved
|
||||
// from a priority to another).
|
||||
//
|
||||
// It's guaranteed that after this function returns:
|
||||
//
|
||||
// If some child is READY, it is childInUse, and all lower priorities are
|
||||
// closed.
|
||||
//
|
||||
// If some child is newly started(in Connecting for the first time), it is
|
||||
// childInUse, and all lower priorities are closed.
|
||||
//
|
||||
// Otherwise, the lowest priority is childInUse (none of the children is
|
||||
// ready, and the overall state is not ready).
|
||||
//
|
||||
// Steps:
|
||||
//
|
||||
// If all priorities were deleted, unset childInUse (to an empty string), and
|
||||
// set parent ClientConn to TransientFailure
|
||||
//
|
||||
// Otherwise, Scan all children from p0, and check balancer stats:
|
||||
//
|
||||
// For any of the following cases:
|
||||
//
|
||||
// If balancer is not started (not built), this is either a new child with
|
||||
// high priority, or a new builder for an existing child.
|
||||
//
|
||||
// If balancer is Connecting and has non-nil initTimer (meaning it
|
||||
// transitioned from Ready or Idle to connecting, not from TF, so we
|
||||
// should give it init-time to connect).
|
||||
//
|
||||
// If balancer is READY or IDLE
|
||||
//
|
||||
// If this is the lowest priority
|
||||
//
|
||||
// do the following:
|
||||
//
|
||||
// if this is not the old childInUse, override picker so old picker is no
|
||||
// longer used.
|
||||
//
|
||||
// switch to it (because all higher priorities are neither new or Ready)
|
||||
//
|
||||
// forward the new addresses and config
|
||||
//
|
||||
// Caller must hold b.mu.
|
||||
func (b *priorityBalancer) syncPriority(childUpdating string) {
|
||||
if b.inhibitPickerUpdates {
|
||||
if b.logger.V(2) {
|
||||
b.logger.Infof("Skipping update from child policy %q", childUpdating)
|
||||
}
|
||||
return
|
||||
}
|
||||
for p, name := range b.priorities {
|
||||
child, ok := b.children[name]
|
||||
if !ok {
|
||||
b.logger.Warningf("Priority name %q is not found in list of child policies", name)
|
||||
continue
|
||||
}
|
||||
|
||||
if !child.started ||
|
||||
child.state.ConnectivityState == connectivity.Ready ||
|
||||
child.state.ConnectivityState == connectivity.Idle ||
|
||||
(child.state.ConnectivityState == connectivity.Connecting && child.initTimer != nil) ||
|
||||
p == len(b.priorities)-1 {
|
||||
if b.childInUse != child.name || child.name == childUpdating {
|
||||
if b.logger.V(2) {
|
||||
b.logger.Infof("childInUse, childUpdating: %q, %q", b.childInUse, child.name)
|
||||
}
|
||||
// If we switch children or the child in use just updated its
|
||||
// picker, push the child's picker to the parent.
|
||||
b.cc.UpdateState(child.state)
|
||||
}
|
||||
if b.logger.V(2) {
|
||||
b.logger.Infof("Switching to (%q, %v) in syncPriority", child.name, p)
|
||||
}
|
||||
b.switchToChild(child, p)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop priorities [p+1, lowest].
|
||||
//
|
||||
// Caller must hold b.mu.
|
||||
func (b *priorityBalancer) stopSubBalancersLowerThanPriority(p int) {
|
||||
for i := p + 1; i < len(b.priorities); i++ {
|
||||
name := b.priorities[i]
|
||||
child, ok := b.children[name]
|
||||
if !ok {
|
||||
b.logger.Warningf("Priority name %q is not found in list of child policies", name)
|
||||
continue
|
||||
}
|
||||
child.stop(false)
|
||||
}
|
||||
}
|
||||
|
||||
// switchToChild does the following:
|
||||
// - stop all child with lower priorities
|
||||
// - if childInUse is not this child
|
||||
// - set childInUse to this child
|
||||
// - if this child is not started, start it
|
||||
//
|
||||
// Note that it does NOT send the current child state (picker) to the parent
|
||||
// ClientConn. The caller needs to send it if necessary.
|
||||
//
|
||||
// this can be called when
|
||||
// 1. first update, start p0
|
||||
// 2. an update moves a READY child from a lower priority to higher
|
||||
// 2. a different builder is updated for this child
|
||||
// 3. a high priority goes Failure, start next
|
||||
// 4. a high priority init timeout, start next
|
||||
//
|
||||
// Caller must hold b.mu.
|
||||
func (b *priorityBalancer) switchToChild(child *childBalancer, priority int) {
|
||||
// Stop lower priorities even if childInUse is same as this child. It's
|
||||
// possible this child was moved from a priority to another.
|
||||
b.stopSubBalancersLowerThanPriority(priority)
|
||||
|
||||
// If this child is already in use, do nothing.
|
||||
//
|
||||
// This can happen:
|
||||
// - all priorities are not READY, an config update always triggers switch
|
||||
// to the lowest. In this case, the lowest child could still be connecting,
|
||||
// so we don't stop the init timer.
|
||||
// - a high priority is READY, an config update always triggers switch to
|
||||
// it.
|
||||
if b.childInUse == child.name && child.started {
|
||||
return
|
||||
}
|
||||
b.childInUse = child.name
|
||||
|
||||
if !child.started {
|
||||
child.start()
|
||||
}
|
||||
}
|
||||
|
||||
// handleChildStateUpdate start/close priorities based on the connectivity
|
||||
// state.
|
||||
func (b *priorityBalancer) handleChildStateUpdate(childName string, newState balancer.State) {
|
||||
// Update state in child. The updated picker will be sent to parent later if
|
||||
// necessary.
|
||||
child, ok := b.children[childName]
|
||||
if !ok {
|
||||
b.logger.Warningf("Child policy not found for %q", childName)
|
||||
return
|
||||
}
|
||||
if !child.started {
|
||||
b.logger.Warningf("Ignoring update from child policy %q which is not in started state: %+v", childName, newState)
|
||||
return
|
||||
}
|
||||
oldState := child.state
|
||||
child.state = newState
|
||||
|
||||
// We start/stop the init timer of this child based on the new connectivity
|
||||
// state. syncPriority() later will need the init timer (to check if it's
|
||||
// nil or not) to decide which child to switch to.
|
||||
switch newState.ConnectivityState {
|
||||
case connectivity.Ready, connectivity.Idle:
|
||||
child.reportedTF = false
|
||||
child.stopInitTimer()
|
||||
case connectivity.TransientFailure:
|
||||
child.reportedTF = true
|
||||
child.stopInitTimer()
|
||||
case connectivity.Connecting:
|
||||
// The init timer is created when the child is created and is reset when
|
||||
// it reports Ready or Idle. Most child policies start off in
|
||||
// Connecting, but ring_hash starts off in Idle and moves to Connecting
|
||||
// when a request comes in. To support such cases, we restart the init
|
||||
// timer when we see Connecting, but only if the child has not reported
|
||||
// TransientFailure more recently than it reported Ready or Idle. See
|
||||
// gRFC A42 for details on why ring_hash is special and what provisions
|
||||
// are required to make it work as a child of the priority LB policy.
|
||||
//
|
||||
// We don't want to restart the timer if the child was already in
|
||||
// Connecting, because we want failover to happen once the timer elapses
|
||||
// even when the child is still in Connecting.
|
||||
if !child.reportedTF && oldState.ConnectivityState != connectivity.Connecting {
|
||||
child.startInitTimer()
|
||||
}
|
||||
default:
|
||||
// New state is Shutdown, should never happen. Don't forward.
|
||||
}
|
||||
|
||||
child.parent.syncPriority(childName)
|
||||
}
|
||||
Reference in New Issue
Block a user