gitee.com/zhaochuninhefei/gmgo@v0.0.31-0.20240209061119-069254a02979/grpc/picker_wrapper.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 "context" 23 "errors" 24 "io" 25 "sync" 26 27 "gitee.com/zhaochuninhefei/gmgo/grpc/balancer" 28 "gitee.com/zhaochuninhefei/gmgo/grpc/codes" 29 "gitee.com/zhaochuninhefei/gmgo/grpc/internal/channelz" 30 "gitee.com/zhaochuninhefei/gmgo/grpc/internal/transport" 31 "gitee.com/zhaochuninhefei/gmgo/grpc/status" 32 ) 33 34 // pickerWrapper is a wrapper of balancer.Picker. It blocks on certain pick 35 // actions and unblock when there's a picker update. 36 type pickerWrapper struct { 37 mu sync.Mutex 38 done bool 39 blockingCh chan struct{} 40 picker balancer.Picker 41 } 42 43 func newPickerWrapper() *pickerWrapper { 44 return &pickerWrapper{blockingCh: make(chan struct{})} 45 } 46 47 // updatePicker is called by UpdateBalancerState. It unblocks all blocked pick. 48 func (pw *pickerWrapper) updatePicker(p balancer.Picker) { 49 pw.mu.Lock() 50 if pw.done { 51 pw.mu.Unlock() 52 return 53 } 54 pw.picker = p 55 // pw.blockingCh should never be nil. 56 close(pw.blockingCh) 57 pw.blockingCh = make(chan struct{}) 58 pw.mu.Unlock() 59 } 60 61 func doneChannelzWrapper(acw *acBalancerWrapper, done func(balancer.DoneInfo)) func(balancer.DoneInfo) { 62 acw.mu.Lock() 63 ac := acw.ac 64 acw.mu.Unlock() 65 ac.incrCallsStarted() 66 return func(b balancer.DoneInfo) { 67 if b.Err != nil && b.Err != io.EOF { 68 ac.incrCallsFailed() 69 } else { 70 ac.incrCallsSucceeded() 71 } 72 if done != nil { 73 done(b) 74 } 75 } 76 } 77 78 // pick returns the transport that will be used for the RPC. 79 // It may block in the following cases: 80 // - there's no picker 81 // - the current picker returns ErrNoSubConnAvailable 82 // - the current picker returns other errors and failfast is false. 83 // - the subConn returned by the current picker is not READY 84 // When one of these situations happens, pick blocks until the picker gets updated. 85 func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.PickInfo) (transport.ClientTransport, func(balancer.DoneInfo), error) { 86 var ch chan struct{} 87 88 var lastPickErr error 89 for { 90 pw.mu.Lock() 91 if pw.done { 92 pw.mu.Unlock() 93 return nil, nil, ErrClientConnClosing 94 } 95 96 if pw.picker == nil { 97 ch = pw.blockingCh 98 } 99 if ch == pw.blockingCh { 100 // This could happen when either: 101 // - pw.picker is nil (the previous if condition), or 102 // - has called pick on the current picker. 103 pw.mu.Unlock() 104 select { 105 case <-ctx.Done(): 106 var errStr string 107 if lastPickErr != nil { 108 errStr = "latest balancer error: " + lastPickErr.Error() 109 } else { 110 errStr = ctx.Err().Error() 111 } 112 switch err := ctx.Err(); { 113 case errors.Is(err, context.DeadlineExceeded): 114 return nil, nil, status.Error(codes.DeadlineExceeded, errStr) 115 case errors.Is(err, context.Canceled): 116 return nil, nil, status.Error(codes.Canceled, errStr) 117 } 118 case <-ch: 119 } 120 continue 121 } 122 123 ch = pw.blockingCh 124 p := pw.picker 125 pw.mu.Unlock() 126 127 pickResult, err := p.Pick(info) 128 129 if err != nil { 130 if errors.Is(err, balancer.ErrNoSubConnAvailable) { 131 continue 132 } 133 if _, ok := status.FromError(err); ok { 134 // Status error: end the RPC unconditionally with this status. 135 return nil, nil, err 136 } 137 // For all other errors, wait for ready RPCs should block and other 138 // RPCs should fail with unavailable. 139 if !failfast { 140 lastPickErr = err 141 continue 142 } 143 return nil, nil, status.Error(codes.Unavailable, err.Error()) 144 } 145 146 acw, ok := pickResult.SubConn.(*acBalancerWrapper) 147 if !ok { 148 logger.Errorf("subconn returned from pick is type %T, not *acBalancerWrapper", pickResult.SubConn) 149 continue 150 } 151 if t := acw.getAddrConn().getReadyTransport(); t != nil { 152 if channelz.IsOn() { 153 return t, doneChannelzWrapper(acw, pickResult.Done), nil 154 } 155 return t, pickResult.Done, nil 156 } 157 if pickResult.Done != nil { 158 // Calling done with nil error, no bytes sent and no bytes received. 159 // DoneInfo with default value works. 160 pickResult.Done(balancer.DoneInfo{}) 161 } 162 logger.Infof("blockingPicker: the picked transport is not ready, loop back to repick") 163 // If ok == false, ac.state is not READY. 164 // A valid picker always returns READY subConn. This means the state of ac 165 // just changed, and picker will be updated shortly. 166 // continue back to the beginning of the for loop to repick. 167 } 168 } 169 170 func (pw *pickerWrapper) close() { 171 pw.mu.Lock() 172 defer pw.mu.Unlock() 173 if pw.done { 174 return 175 } 176 pw.done = true 177 close(pw.blockingCh) 178 }