mirror of https://github.com/grpc/grpc-go.git
166 lines
5.0 KiB
Go
166 lines
5.0 KiB
Go
/*
|
|
*
|
|
* 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 (
|
|
"time"
|
|
|
|
"google.golang.org/grpc/balancer"
|
|
"google.golang.org/grpc/balancer/base"
|
|
"google.golang.org/grpc/connectivity"
|
|
"google.golang.org/grpc/resolver"
|
|
"google.golang.org/grpc/serviceconfig"
|
|
)
|
|
|
|
type childBalancer struct {
|
|
name string
|
|
parent *priorityBalancer
|
|
bb *ignoreResolveNowBalancerBuilder
|
|
|
|
ignoreReresolutionRequests bool
|
|
config serviceconfig.LoadBalancingConfig
|
|
rState resolver.State
|
|
|
|
started bool
|
|
// This is set when the child reports TransientFailure, and unset when it
|
|
// reports Ready or Idle. It is used to decide whether the failover timer
|
|
// should start when the child is transitioning into Connecting. The timer
|
|
// will be restarted if the child has not reported TF more recently than it
|
|
// reported Ready or Idle.
|
|
reportedTF bool
|
|
// The latest state the child balancer provided.
|
|
state balancer.State
|
|
// The timer to give a priority some time to connect. And if the priority
|
|
// doesn't go into Ready/Failure, the next priority will be started.
|
|
initTimer *timerWrapper
|
|
}
|
|
|
|
// newChildBalancer creates a child balancer place holder, but doesn't
|
|
// build/start the child balancer.
|
|
func newChildBalancer(name string, parent *priorityBalancer, bb balancer.Builder) *childBalancer {
|
|
return &childBalancer{
|
|
name: name,
|
|
parent: parent,
|
|
bb: newIgnoreResolveNowBalancerBuilder(bb, false),
|
|
started: false,
|
|
// Start with the connecting state and picker with re-pick error, so
|
|
// that when a priority switch causes this child picked before it's
|
|
// balancing policy is created, a re-pick will happen.
|
|
state: balancer.State{
|
|
ConnectivityState: connectivity.Connecting,
|
|
Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable),
|
|
},
|
|
}
|
|
}
|
|
|
|
// updateBuilder updates builder for the child, but doesn't build.
|
|
func (cb *childBalancer) updateBuilder(bb balancer.Builder) {
|
|
cb.bb = newIgnoreResolveNowBalancerBuilder(bb, cb.ignoreReresolutionRequests)
|
|
}
|
|
|
|
// updateConfig sets childBalancer's config and state, but doesn't send update to
|
|
// the child balancer unless it is started.
|
|
func (cb *childBalancer) updateConfig(child *Child, rState resolver.State) {
|
|
cb.ignoreReresolutionRequests = child.IgnoreReresolutionRequests
|
|
cb.config = child.Config.Config
|
|
cb.rState = rState
|
|
if cb.started {
|
|
cb.sendUpdate()
|
|
}
|
|
}
|
|
|
|
// start builds the child balancer if it's not already started.
|
|
//
|
|
// It doesn't do it directly. It asks the balancer group to build it.
|
|
func (cb *childBalancer) start() {
|
|
if cb.started {
|
|
return
|
|
}
|
|
cb.started = true
|
|
cb.parent.bg.Add(cb.name, cb.bb)
|
|
cb.startInitTimer()
|
|
cb.sendUpdate()
|
|
}
|
|
|
|
// sendUpdate sends the addresses and config to the child balancer.
|
|
func (cb *childBalancer) sendUpdate() {
|
|
cb.bb.updateIgnoreResolveNow(cb.ignoreReresolutionRequests)
|
|
// TODO: return and aggregate the returned error in the parent.
|
|
err := cb.parent.bg.UpdateClientConnState(cb.name, balancer.ClientConnState{
|
|
ResolverState: cb.rState,
|
|
BalancerConfig: cb.config,
|
|
})
|
|
if err != nil {
|
|
cb.parent.logger.Warningf("failed to update ClientConn state for child %v: %v", cb.name, err)
|
|
}
|
|
}
|
|
|
|
// stop stops the child balancer and resets the state.
|
|
//
|
|
// It doesn't do it directly. It asks the balancer group to remove it.
|
|
//
|
|
// Note that the underlying balancer group could keep the child in a cache.
|
|
func (cb *childBalancer) stop() {
|
|
if !cb.started {
|
|
return
|
|
}
|
|
cb.stopInitTimer()
|
|
cb.parent.bg.Remove(cb.name)
|
|
cb.started = false
|
|
cb.state = balancer.State{
|
|
ConnectivityState: connectivity.Connecting,
|
|
Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable),
|
|
}
|
|
// Clear child.reportedTF, so that if this child is started later, it will
|
|
// be given time to connect.
|
|
cb.reportedTF = false
|
|
}
|
|
|
|
func (cb *childBalancer) startInitTimer() {
|
|
if cb.initTimer != nil {
|
|
return
|
|
}
|
|
// Need this local variable to capture timerW in the AfterFunc closure
|
|
// to check the stopped boolean.
|
|
timerW := &timerWrapper{}
|
|
cb.initTimer = timerW
|
|
timerW.timer = time.AfterFunc(DefaultPriorityInitTimeout, func() {
|
|
cb.parent.mu.Lock()
|
|
defer cb.parent.mu.Unlock()
|
|
if timerW.stopped {
|
|
return
|
|
}
|
|
cb.initTimer = nil
|
|
// Re-sync the priority. This will switch to the next priority if
|
|
// there's any. Note that it's important sync() is called after setting
|
|
// initTimer to nil.
|
|
cb.parent.syncPriority("")
|
|
})
|
|
}
|
|
|
|
func (cb *childBalancer) stopInitTimer() {
|
|
timerW := cb.initTimer
|
|
if timerW == nil {
|
|
return
|
|
}
|
|
cb.initTimer = nil
|
|
timerW.stopped = true
|
|
timerW.timer.Stop()
|
|
}
|