google.golang.org/grpc@v1.62.1/pickfirst.go (about)

     1  /*
     2   *
     3   * Copyright 2017 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 grpc
    20  
    21  import (
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  
    26  	"google.golang.org/grpc/balancer"
    27  	"google.golang.org/grpc/connectivity"
    28  	internalgrpclog "google.golang.org/grpc/internal/grpclog"
    29  	"google.golang.org/grpc/internal/grpcrand"
    30  	"google.golang.org/grpc/internal/pretty"
    31  	"google.golang.org/grpc/resolver"
    32  	"google.golang.org/grpc/serviceconfig"
    33  )
    34  
    35  const (
    36  	// PickFirstBalancerName is the name of the pick_first balancer.
    37  	PickFirstBalancerName = "pick_first"
    38  	logPrefix             = "[pick-first-lb %p] "
    39  )
    40  
    41  func newPickfirstBuilder() balancer.Builder {
    42  	return &pickfirstBuilder{}
    43  }
    44  
    45  type pickfirstBuilder struct{}
    46  
    47  func (*pickfirstBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer {
    48  	b := &pickfirstBalancer{cc: cc}
    49  	b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b))
    50  	return b
    51  }
    52  
    53  func (*pickfirstBuilder) Name() string {
    54  	return PickFirstBalancerName
    55  }
    56  
    57  type pfConfig struct {
    58  	serviceconfig.LoadBalancingConfig `json:"-"`
    59  
    60  	// If set to true, instructs the LB policy to shuffle the order of the list
    61  	// of addresses received from the name resolver before attempting to
    62  	// connect to them.
    63  	ShuffleAddressList bool `json:"shuffleAddressList"`
    64  }
    65  
    66  func (*pickfirstBuilder) ParseConfig(js json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
    67  	var cfg pfConfig
    68  	if err := json.Unmarshal(js, &cfg); err != nil {
    69  		return nil, fmt.Errorf("pickfirst: unable to unmarshal LB policy config: %s, error: %v", string(js), err)
    70  	}
    71  	return cfg, nil
    72  }
    73  
    74  type pickfirstBalancer struct {
    75  	logger  *internalgrpclog.PrefixLogger
    76  	state   connectivity.State
    77  	cc      balancer.ClientConn
    78  	subConn balancer.SubConn
    79  }
    80  
    81  func (b *pickfirstBalancer) ResolverError(err error) {
    82  	if b.logger.V(2) {
    83  		b.logger.Infof("Received error from the name resolver: %v", err)
    84  	}
    85  	if b.subConn == nil {
    86  		b.state = connectivity.TransientFailure
    87  	}
    88  
    89  	if b.state != connectivity.TransientFailure {
    90  		// The picker will not change since the balancer does not currently
    91  		// report an error.
    92  		return
    93  	}
    94  	b.cc.UpdateState(balancer.State{
    95  		ConnectivityState: connectivity.TransientFailure,
    96  		Picker:            &picker{err: fmt.Errorf("name resolver error: %v", err)},
    97  	})
    98  }
    99  
   100  func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState) error {
   101  	addrs := state.ResolverState.Addresses
   102  	if len(addrs) == 0 {
   103  		// The resolver reported an empty address list. Treat it like an error by
   104  		// calling b.ResolverError.
   105  		if b.subConn != nil {
   106  			// Shut down the old subConn. All addresses were removed, so it is
   107  			// no longer valid.
   108  			b.subConn.Shutdown()
   109  			b.subConn = nil
   110  		}
   111  		b.ResolverError(errors.New("produced zero addresses"))
   112  		return balancer.ErrBadResolverState
   113  	}
   114  
   115  	// We don't have to guard this block with the env var because ParseConfig
   116  	// already does so.
   117  	cfg, ok := state.BalancerConfig.(pfConfig)
   118  	if state.BalancerConfig != nil && !ok {
   119  		return fmt.Errorf("pickfirst: received illegal BalancerConfig (type %T): %v", state.BalancerConfig, state.BalancerConfig)
   120  	}
   121  	if cfg.ShuffleAddressList {
   122  		addrs = append([]resolver.Address{}, addrs...)
   123  		grpcrand.Shuffle(len(addrs), func(i, j int) { addrs[i], addrs[j] = addrs[j], addrs[i] })
   124  	}
   125  
   126  	if b.logger.V(2) {
   127  		b.logger.Infof("Received new config %s, resolver state %s", pretty.ToJSON(cfg), pretty.ToJSON(state.ResolverState))
   128  	}
   129  
   130  	if b.subConn != nil {
   131  		b.cc.UpdateAddresses(b.subConn, addrs)
   132  		return nil
   133  	}
   134  
   135  	var subConn balancer.SubConn
   136  	subConn, err := b.cc.NewSubConn(addrs, balancer.NewSubConnOptions{
   137  		StateListener: func(state balancer.SubConnState) {
   138  			b.updateSubConnState(subConn, state)
   139  		},
   140  	})
   141  	if err != nil {
   142  		if b.logger.V(2) {
   143  			b.logger.Infof("Failed to create new SubConn: %v", err)
   144  		}
   145  		b.state = connectivity.TransientFailure
   146  		b.cc.UpdateState(balancer.State{
   147  			ConnectivityState: connectivity.TransientFailure,
   148  			Picker:            &picker{err: fmt.Errorf("error creating connection: %v", err)},
   149  		})
   150  		return balancer.ErrBadResolverState
   151  	}
   152  	b.subConn = subConn
   153  	b.state = connectivity.Idle
   154  	b.cc.UpdateState(balancer.State{
   155  		ConnectivityState: connectivity.Connecting,
   156  		Picker:            &picker{err: balancer.ErrNoSubConnAvailable},
   157  	})
   158  	b.subConn.Connect()
   159  	return nil
   160  }
   161  
   162  // UpdateSubConnState is unused as a StateListener is always registered when
   163  // creating SubConns.
   164  func (b *pickfirstBalancer) UpdateSubConnState(subConn balancer.SubConn, state balancer.SubConnState) {
   165  	b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", subConn, state)
   166  }
   167  
   168  func (b *pickfirstBalancer) updateSubConnState(subConn balancer.SubConn, state balancer.SubConnState) {
   169  	if b.logger.V(2) {
   170  		b.logger.Infof("Received SubConn state update: %p, %+v", subConn, state)
   171  	}
   172  	if b.subConn != subConn {
   173  		if b.logger.V(2) {
   174  			b.logger.Infof("Ignored state change because subConn is not recognized")
   175  		}
   176  		return
   177  	}
   178  	if state.ConnectivityState == connectivity.Shutdown {
   179  		b.subConn = nil
   180  		return
   181  	}
   182  
   183  	switch state.ConnectivityState {
   184  	case connectivity.Ready:
   185  		b.cc.UpdateState(balancer.State{
   186  			ConnectivityState: state.ConnectivityState,
   187  			Picker:            &picker{result: balancer.PickResult{SubConn: subConn}},
   188  		})
   189  	case connectivity.Connecting:
   190  		if b.state == connectivity.TransientFailure {
   191  			// We stay in TransientFailure until we are Ready. See A62.
   192  			return
   193  		}
   194  		b.cc.UpdateState(balancer.State{
   195  			ConnectivityState: state.ConnectivityState,
   196  			Picker:            &picker{err: balancer.ErrNoSubConnAvailable},
   197  		})
   198  	case connectivity.Idle:
   199  		if b.state == connectivity.TransientFailure {
   200  			// We stay in TransientFailure until we are Ready. Also kick the
   201  			// subConn out of Idle into Connecting. See A62.
   202  			b.subConn.Connect()
   203  			return
   204  		}
   205  		b.cc.UpdateState(balancer.State{
   206  			ConnectivityState: state.ConnectivityState,
   207  			Picker:            &idlePicker{subConn: subConn},
   208  		})
   209  	case connectivity.TransientFailure:
   210  		b.cc.UpdateState(balancer.State{
   211  			ConnectivityState: state.ConnectivityState,
   212  			Picker:            &picker{err: state.ConnectionError},
   213  		})
   214  	}
   215  	b.state = state.ConnectivityState
   216  }
   217  
   218  func (b *pickfirstBalancer) Close() {
   219  }
   220  
   221  func (b *pickfirstBalancer) ExitIdle() {
   222  	if b.subConn != nil && b.state == connectivity.Idle {
   223  		b.subConn.Connect()
   224  	}
   225  }
   226  
   227  type picker struct {
   228  	result balancer.PickResult
   229  	err    error
   230  }
   231  
   232  func (p *picker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
   233  	return p.result, p.err
   234  }
   235  
   236  // idlePicker is used when the SubConn is IDLE and kicks the SubConn into
   237  // CONNECTING when Pick is called.
   238  type idlePicker struct {
   239  	subConn balancer.SubConn
   240  }
   241  
   242  func (i *idlePicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
   243  	i.subConn.Connect()
   244  	return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
   245  }
   246  
   247  func init() {
   248  	balancer.Register(newPickfirstBuilder())
   249  }