google.golang.org/grpc@v1.72.2/balancer/lazy/lazy.go (about)

     1  /*
     2   *
     3   * Copyright 2025 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 lazy contains a load balancer that starts in IDLE instead of
    20  // CONNECTING. Once it starts connecting, it instantiates its delegate.
    21  //
    22  // # Experimental
    23  //
    24  // Notice: This package is EXPERIMENTAL and may be changed or removed in a
    25  // later release.
    26  package lazy
    27  
    28  import (
    29  	"fmt"
    30  	"sync"
    31  
    32  	"google.golang.org/grpc/balancer"
    33  	"google.golang.org/grpc/connectivity"
    34  	"google.golang.org/grpc/grpclog"
    35  	"google.golang.org/grpc/resolver"
    36  
    37  	internalgrpclog "google.golang.org/grpc/internal/grpclog"
    38  )
    39  
    40  var (
    41  	logger = grpclog.Component("lazy-lb")
    42  )
    43  
    44  const (
    45  	logPrefix = "[lazy-lb %p] "
    46  )
    47  
    48  // ChildBuilderFunc creates a new balancer with the ClientConn. It has the same
    49  // type as the balancer.Builder.Build method.
    50  type ChildBuilderFunc func(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer
    51  
    52  // NewBalancer is the constructor for the lazy balancer.
    53  func NewBalancer(cc balancer.ClientConn, bOpts balancer.BuildOptions, childBuilder ChildBuilderFunc) balancer.Balancer {
    54  	b := &lazyBalancer{
    55  		cc:           cc,
    56  		buildOptions: bOpts,
    57  		childBuilder: childBuilder,
    58  	}
    59  	b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b))
    60  	cc.UpdateState(balancer.State{
    61  		ConnectivityState: connectivity.Idle,
    62  		Picker: &idlePicker{exitIdle: sync.OnceFunc(func() {
    63  			// Call ExitIdle in a new goroutine to avoid deadlocks while calling
    64  			// back into the channel synchronously.
    65  			go b.ExitIdle()
    66  		})},
    67  	})
    68  	return b
    69  }
    70  
    71  type lazyBalancer struct {
    72  	// The following fields are initialized at build time and read-only after
    73  	// that and therefore do not need to be guarded by a mutex.
    74  	cc           balancer.ClientConn
    75  	buildOptions balancer.BuildOptions
    76  	logger       *internalgrpclog.PrefixLogger
    77  	childBuilder ChildBuilderFunc
    78  
    79  	// The following fields are accessed while handling calls to the idlePicker
    80  	// and when handling ClientConn state updates. They are guarded by a mutex.
    81  
    82  	mu                    sync.Mutex
    83  	delegate              balancer.Balancer
    84  	latestClientConnState *balancer.ClientConnState
    85  	latestResolverError   error
    86  }
    87  
    88  func (lb *lazyBalancer) Close() {
    89  	lb.mu.Lock()
    90  	defer lb.mu.Unlock()
    91  	if lb.delegate != nil {
    92  		lb.delegate.Close()
    93  		lb.delegate = nil
    94  	}
    95  }
    96  
    97  func (lb *lazyBalancer) ResolverError(err error) {
    98  	lb.mu.Lock()
    99  	defer lb.mu.Unlock()
   100  	if lb.delegate != nil {
   101  		lb.delegate.ResolverError(err)
   102  		return
   103  	}
   104  	lb.latestResolverError = err
   105  }
   106  
   107  func (lb *lazyBalancer) UpdateClientConnState(ccs balancer.ClientConnState) error {
   108  	lb.mu.Lock()
   109  	defer lb.mu.Unlock()
   110  	if lb.delegate != nil {
   111  		return lb.delegate.UpdateClientConnState(ccs)
   112  	}
   113  
   114  	lb.latestClientConnState = &ccs
   115  	lb.latestResolverError = nil
   116  	return nil
   117  }
   118  
   119  // UpdateSubConnState implements balancer.Balancer.
   120  func (lb *lazyBalancer) UpdateSubConnState(balancer.SubConn, balancer.SubConnState) {
   121  	// UpdateSubConnState is deprecated.
   122  }
   123  
   124  func (lb *lazyBalancer) ExitIdle() {
   125  	lb.mu.Lock()
   126  	defer lb.mu.Unlock()
   127  	if lb.delegate != nil {
   128  		if d, ok := lb.delegate.(balancer.ExitIdler); ok {
   129  			d.ExitIdle()
   130  		}
   131  		return
   132  	}
   133  	lb.delegate = lb.childBuilder(lb.cc, lb.buildOptions)
   134  	if lb.latestClientConnState != nil {
   135  		if err := lb.delegate.UpdateClientConnState(*lb.latestClientConnState); err != nil {
   136  			if err == balancer.ErrBadResolverState {
   137  				lb.cc.ResolveNow(resolver.ResolveNowOptions{})
   138  			} else {
   139  				lb.logger.Warningf("Error from child policy on receiving initial state: %v", err)
   140  			}
   141  		}
   142  		lb.latestClientConnState = nil
   143  	}
   144  	if lb.latestResolverError != nil {
   145  		lb.delegate.ResolverError(lb.latestResolverError)
   146  		lb.latestResolverError = nil
   147  	}
   148  }
   149  
   150  // idlePicker is used when the SubConn is IDLE and kicks the SubConn into
   151  // CONNECTING when Pick is called.
   152  type idlePicker struct {
   153  	exitIdle func()
   154  }
   155  
   156  func (i *idlePicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
   157  	i.exitIdle()
   158  	return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
   159  }