picker_wrapper.go (5633B)
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 "io" 24 "sync" 25 26 "google.golang.org/grpc/balancer" 27 "google.golang.org/grpc/codes" 28 "google.golang.org/grpc/internal/channelz" 29 istatus "google.golang.org/grpc/internal/status" 30 "google.golang.org/grpc/internal/transport" 31 "google.golang.org/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 // doneChannelzWrapper performs the following: 62 // - increments the calls started channelz counter 63 // - wraps the done function in the passed in result to increment the calls 64 // failed or calls succeeded channelz counter before invoking the actual 65 // done function. 66 func doneChannelzWrapper(acw *acBalancerWrapper, result *balancer.PickResult) { 67 acw.mu.Lock() 68 ac := acw.ac 69 acw.mu.Unlock() 70 ac.incrCallsStarted() 71 done := result.Done 72 result.Done = func(b balancer.DoneInfo) { 73 if b.Err != nil && b.Err != io.EOF { 74 ac.incrCallsFailed() 75 } else { 76 ac.incrCallsSucceeded() 77 } 78 if done != nil { 79 done(b) 80 } 81 } 82 } 83 84 // pick returns the transport that will be used for the RPC. 85 // It may block in the following cases: 86 // - there's no picker 87 // - the current picker returns ErrNoSubConnAvailable 88 // - the current picker returns other errors and failfast is false. 89 // - the subConn returned by the current picker is not READY 90 // When one of these situations happens, pick blocks until the picker gets updated. 91 func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.PickInfo) (transport.ClientTransport, balancer.PickResult, error) { 92 var ch chan struct{} 93 94 var lastPickErr error 95 for { 96 pw.mu.Lock() 97 if pw.done { 98 pw.mu.Unlock() 99 return nil, balancer.PickResult{}, ErrClientConnClosing 100 } 101 102 if pw.picker == nil { 103 ch = pw.blockingCh 104 } 105 if ch == pw.blockingCh { 106 // This could happen when either: 107 // - pw.picker is nil (the previous if condition), or 108 // - has called pick on the current picker. 109 pw.mu.Unlock() 110 select { 111 case <-ctx.Done(): 112 var errStr string 113 if lastPickErr != nil { 114 errStr = "latest balancer error: " + lastPickErr.Error() 115 } else { 116 errStr = ctx.Err().Error() 117 } 118 switch ctx.Err() { 119 case context.DeadlineExceeded: 120 return nil, balancer.PickResult{}, status.Error(codes.DeadlineExceeded, errStr) 121 case context.Canceled: 122 return nil, balancer.PickResult{}, status.Error(codes.Canceled, errStr) 123 } 124 case <-ch: 125 } 126 continue 127 } 128 129 ch = pw.blockingCh 130 p := pw.picker 131 pw.mu.Unlock() 132 133 pickResult, err := p.Pick(info) 134 if err != nil { 135 if err == balancer.ErrNoSubConnAvailable { 136 continue 137 } 138 if st, ok := status.FromError(err); ok { 139 // Status error: end the RPC unconditionally with this status. 140 // First restrict the code to the list allowed by gRFC A54. 141 if istatus.IsRestrictedControlPlaneCode(st) { 142 err = status.Errorf(codes.Internal, "received picker error with illegal status: %v", err) 143 } 144 return nil, balancer.PickResult{}, dropError{error: err} 145 } 146 // For all other errors, wait for ready RPCs should block and other 147 // RPCs should fail with unavailable. 148 if !failfast { 149 lastPickErr = err 150 continue 151 } 152 return nil, balancer.PickResult{}, status.Error(codes.Unavailable, err.Error()) 153 } 154 155 acw, ok := pickResult.SubConn.(*acBalancerWrapper) 156 if !ok { 157 logger.Errorf("subconn returned from pick is type %T, not *acBalancerWrapper", pickResult.SubConn) 158 continue 159 } 160 if t := acw.getAddrConn().getReadyTransport(); t != nil { 161 if channelz.IsOn() { 162 doneChannelzWrapper(acw, &pickResult) 163 return t, pickResult, nil 164 } 165 return t, pickResult, nil 166 } 167 if pickResult.Done != nil { 168 // Calling done with nil error, no bytes sent and no bytes received. 169 // DoneInfo with default value works. 170 pickResult.Done(balancer.DoneInfo{}) 171 } 172 logger.Infof("blockingPicker: the picked transport is not ready, loop back to repick") 173 // If ok == false, ac.state is not READY. 174 // A valid picker always returns READY subConn. This means the state of ac 175 // just changed, and picker will be updated shortly. 176 // continue back to the beginning of the for loop to repick. 177 } 178 } 179 180 func (pw *pickerWrapper) close() { 181 pw.mu.Lock() 182 defer pw.mu.Unlock() 183 if pw.done { 184 return 185 } 186 pw.done = true 187 close(pw.blockingCh) 188 } 189 190 // dropError is a wrapper error that indicates the LB policy wishes to drop the 191 // RPC and not retry it. 192 type dropError struct { 193 error 194 }