github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/balancer/ringhash/picker.go (about)

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