...

Source file src/google.golang.org/grpc/xds/internal/balancer/priority/balancer_child.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  	"time"
    23  
    24  	"google.golang.org/grpc/balancer"
    25  	"google.golang.org/grpc/balancer/base"
    26  	"google.golang.org/grpc/connectivity"
    27  	"google.golang.org/grpc/resolver"
    28  	"google.golang.org/grpc/serviceconfig"
    29  )
    30  
    31  type childBalancer struct {
    32  	name         string
    33  	parent       *priorityBalancer
    34  	parentCC     balancer.ClientConn
    35  	balancerName string
    36  	cc           *ignoreResolveNowClientConn
    37  
    38  	ignoreReresolutionRequests bool
    39  	config                     serviceconfig.LoadBalancingConfig
    40  	rState                     resolver.State
    41  
    42  	started bool
    43  	// This is set when the child reports TransientFailure, and unset when it
    44  	// reports Ready or Idle. It is used to decide whether the failover timer
    45  	// should start when the child is transitioning into Connecting. The timer
    46  	// will be restarted if the child has not reported TF more recently than it
    47  	// reported Ready or Idle.
    48  	reportedTF bool
    49  	// The latest state the child balancer provided.
    50  	state balancer.State
    51  	// The timer to give a priority some time to connect. And if the priority
    52  	// doesn't go into Ready/Failure, the next priority will be started.
    53  	initTimer *timerWrapper
    54  }
    55  
    56  // newChildBalancer creates a child balancer place holder, but doesn't
    57  // build/start the child balancer.
    58  func newChildBalancer(name string, parent *priorityBalancer, balancerName string, cc balancer.ClientConn) *childBalancer {
    59  	return &childBalancer{
    60  		name:         name,
    61  		parent:       parent,
    62  		parentCC:     cc,
    63  		balancerName: balancerName,
    64  		cc:           newIgnoreResolveNowClientConn(cc, false),
    65  		started:      false,
    66  		// Start with the connecting state and picker with re-pick error, so
    67  		// that when a priority switch causes this child picked before it's
    68  		// balancing policy is created, a re-pick will happen.
    69  		state: balancer.State{
    70  			ConnectivityState: connectivity.Connecting,
    71  			Picker:            base.NewErrPicker(balancer.ErrNoSubConnAvailable),
    72  		},
    73  	}
    74  }
    75  
    76  // updateBalancerName updates balancer name for the child, but doesn't build a
    77  // new one. The parent priority LB always closes the child policy before
    78  // updating the balancer name, and the new balancer is built when it gets added
    79  // to the balancergroup as part of start().
    80  func (cb *childBalancer) updateBalancerName(balancerName string) {
    81  	cb.balancerName = balancerName
    82  	cb.cc = newIgnoreResolveNowClientConn(cb.parentCC, cb.ignoreReresolutionRequests)
    83  }
    84  
    85  // updateConfig sets childBalancer's config and state, but doesn't send update to
    86  // the child balancer unless it is started.
    87  func (cb *childBalancer) updateConfig(child *Child, rState resolver.State) {
    88  	cb.ignoreReresolutionRequests = child.IgnoreReresolutionRequests
    89  	cb.config = child.Config.Config
    90  	cb.rState = rState
    91  	if cb.started {
    92  		cb.sendUpdate()
    93  	}
    94  }
    95  
    96  // start builds the child balancer if it's not already started.
    97  //
    98  // It doesn't do it directly. It asks the balancer group to build it.
    99  func (cb *childBalancer) start() {
   100  	if cb.started {
   101  		return
   102  	}
   103  	cb.started = true
   104  	cb.parent.bg.AddWithClientConn(cb.name, cb.balancerName, cb.cc)
   105  	cb.startInitTimer()
   106  	cb.sendUpdate()
   107  }
   108  
   109  // sendUpdate sends the addresses and config to the child balancer.
   110  func (cb *childBalancer) sendUpdate() {
   111  	cb.cc.updateIgnoreResolveNow(cb.ignoreReresolutionRequests)
   112  	// TODO: return and aggregate the returned error in the parent.
   113  	err := cb.parent.bg.UpdateClientConnState(cb.name, balancer.ClientConnState{
   114  		ResolverState:  cb.rState,
   115  		BalancerConfig: cb.config,
   116  	})
   117  	if err != nil {
   118  		cb.parent.logger.Warningf("Failed to update state for child policy %q: %v", cb.name, err)
   119  	}
   120  }
   121  
   122  // stop stops the child balancer and resets the state.
   123  //
   124  // It doesn't do it directly. It asks the balancer group to remove it.
   125  //
   126  // Note that the underlying balancer group could keep the child in a cache.
   127  func (cb *childBalancer) stop() {
   128  	if !cb.started {
   129  		return
   130  	}
   131  	cb.stopInitTimer()
   132  	cb.parent.bg.Remove(cb.name)
   133  	cb.started = false
   134  	cb.state = balancer.State{
   135  		ConnectivityState: connectivity.Connecting,
   136  		Picker:            base.NewErrPicker(balancer.ErrNoSubConnAvailable),
   137  	}
   138  	// Clear child.reportedTF, so that if this child is started later, it will
   139  	// be given time to connect.
   140  	cb.reportedTF = false
   141  }
   142  
   143  func (cb *childBalancer) startInitTimer() {
   144  	if cb.initTimer != nil {
   145  		return
   146  	}
   147  	// Need this local variable to capture timerW in the AfterFunc closure
   148  	// to check the stopped boolean.
   149  	timerW := &timerWrapper{}
   150  	cb.initTimer = timerW
   151  	timerW.timer = time.AfterFunc(DefaultPriorityInitTimeout, func() {
   152  		cb.parent.mu.Lock()
   153  		defer cb.parent.mu.Unlock()
   154  		if timerW.stopped {
   155  			return
   156  		}
   157  		cb.initTimer = nil
   158  		// Re-sync the priority. This will switch to the next priority if
   159  		// there's any. Note that it's important sync() is called after setting
   160  		// initTimer to nil.
   161  		cb.parent.syncPriority("")
   162  	})
   163  }
   164  
   165  func (cb *childBalancer) stopInitTimer() {
   166  	timerW := cb.initTimer
   167  	if timerW == nil {
   168  		return
   169  	}
   170  	cb.initTimer = nil
   171  	timerW.stopped = true
   172  	timerW.timer.Stop()
   173  }
   174  

View as plain text