dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/balancer/ringhash/picker.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  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   *
    20   * Copyright 2021 gRPC authors.
    21   *
    22   */
    23  
    24  package ringhash
    25  
    26  import (
    27  	"fmt"
    28  )
    29  
    30  import (
    31  	dubbogoLogger "github.com/dubbogo/gost/log/logger"
    32  
    33  	"google.golang.org/grpc/balancer"
    34  
    35  	"google.golang.org/grpc/codes"
    36  
    37  	"google.golang.org/grpc/connectivity"
    38  
    39  	"google.golang.org/grpc/status"
    40  )
    41  
    42  type picker struct {
    43  	ring   *ring
    44  	logger dubbogoLogger.Logger
    45  }
    46  
    47  func newPicker(ring *ring, logger dubbogoLogger.Logger) *picker {
    48  	return &picker{ring: ring, logger: logger}
    49  }
    50  
    51  // handleRICSResult is the return type of handleRICS. It's needed to wrap the
    52  // returned error from Pick() in a struct. With this, if the return values are
    53  // `balancer.PickResult, error, bool`, linter complains because error is not the
    54  // last return value.
    55  type handleRICSResult struct {
    56  	pr  balancer.PickResult
    57  	err error
    58  }
    59  
    60  // handleRICS generates pick result if the entry is in Ready, Idle, Connecting
    61  // or Shutdown. TransientFailure will be handled specifically after this
    62  // function returns.
    63  //
    64  // The first return value indicates if the state is in Ready, Idle, Connecting
    65  // or Shutdown. If it's true, the PickResult and error should be returned from
    66  // Pick() as is.
    67  func (p *picker) handleRICS(e *ringEntry) (handleRICSResult, bool) {
    68  	switch state := e.sc.effectiveState(); state {
    69  	case connectivity.Ready:
    70  		return handleRICSResult{pr: balancer.PickResult{SubConn: e.sc.sc}}, true
    71  	case connectivity.Idle:
    72  		// Trigger Connect() and queue the pick.
    73  		e.sc.queueConnect()
    74  		return handleRICSResult{err: balancer.ErrNoSubConnAvailable}, true
    75  	case connectivity.Connecting:
    76  		return handleRICSResult{err: balancer.ErrNoSubConnAvailable}, true
    77  	case connectivity.TransientFailure:
    78  		// Return ok==false, so TransientFailure will be handled afterwards.
    79  		return handleRICSResult{}, false
    80  	case connectivity.Shutdown:
    81  		// Shutdown can happen in a race where the old picker is called. A new
    82  		// picker should already be sent.
    83  		return handleRICSResult{err: balancer.ErrNoSubConnAvailable}, true
    84  	default:
    85  		// Should never reach this. All the connectivity states are already
    86  		// handled in the cases.
    87  		p.logger.Errorf("SubConn has undefined connectivity state: %v", state)
    88  		return handleRICSResult{err: status.Errorf(codes.Unavailable, "SubConn has undefined connectivity state: %v", state)}, true
    89  	}
    90  }
    91  
    92  func (p *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
    93  	e := p.ring.pick(getRequestHash(info.Ctx))
    94  	if hr, ok := p.handleRICS(e); ok {
    95  		return hr.pr, hr.err
    96  	}
    97  	// ok was false, the entry is in transient failure.
    98  	return p.handleTransientFailure(e)
    99  }
   100  
   101  func (p *picker) handleTransientFailure(e *ringEntry) (balancer.PickResult, error) {
   102  	// Queue a connect on the first picked SubConn.
   103  	e.sc.queueConnect()
   104  
   105  	// Find next entry in the ring, skipping duplicate SubConns.
   106  	e2 := nextSkippingDuplicates(p.ring, e)
   107  	if e2 == nil {
   108  		// There's no next entry available, fail the pick.
   109  		return balancer.PickResult{}, fmt.Errorf("the only SubConn is in Transient Failure")
   110  	}
   111  
   112  	// For the second SubConn, also check Ready/Idle/Connecting as if it's the
   113  	// first entry.
   114  	if hr, ok := p.handleRICS(e2); ok {
   115  		return hr.pr, hr.err
   116  	}
   117  
   118  	// The second SubConn is also in TransientFailure. Queue a connect on it.
   119  	e2.sc.queueConnect()
   120  
   121  	// If it gets here, this is after the second SubConn, and the second SubConn
   122  	// was in TransientFailure.
   123  	//
   124  	// Loop over all other SubConns:
   125  	// - If all SubConns so far are all TransientFailure, trigger Connect() on
   126  	// the TransientFailure SubConns, and keep going.
   127  	// - If there's one SubConn that's not in TransientFailure, keep checking
   128  	// the remaining SubConns (in case there's a Ready, which will be returned),
   129  	// but don't not trigger Connect() on the other SubConns.
   130  	var firstNonFailedFound bool
   131  	for ee := nextSkippingDuplicates(p.ring, e2); ee != e; ee = nextSkippingDuplicates(p.ring, ee) {
   132  		scState := ee.sc.effectiveState()
   133  		if scState == connectivity.Ready {
   134  			return balancer.PickResult{SubConn: ee.sc.sc}, nil
   135  		}
   136  		if firstNonFailedFound {
   137  			continue
   138  		}
   139  		if scState == connectivity.TransientFailure {
   140  			// This will queue a connect.
   141  			ee.sc.queueConnect()
   142  			continue
   143  		}
   144  		// This is a SubConn in a non-failure state. We continue to check the
   145  		// other SubConns, but remember that there was a non-failed SubConn
   146  		// seen. After this, Pick() will never trigger any SubConn to Connect().
   147  		firstNonFailedFound = true
   148  		if scState == connectivity.Idle {
   149  			// This is the first non-failed SubConn, and it is in a real Idle
   150  			// state. Trigger it to Connect().
   151  			ee.sc.queueConnect()
   152  		}
   153  	}
   154  	return balancer.PickResult{}, fmt.Errorf("no connection is Ready")
   155  }
   156  
   157  func nextSkippingDuplicates(ring *ring, entry *ringEntry) *ringEntry {
   158  	for next := ring.next(entry); next != entry; next = ring.next(next) {
   159  		if next.sc != entry.sc {
   160  			return next
   161  		}
   162  	}
   163  	// There's no qualifying next entry.
   164  	return nil
   165  }