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 }