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 }