| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | /* | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright 2017 gRPC authors. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package grpc | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 	"sync/atomic" | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"google.golang.org/grpc/balancer" | 
					
						
							|  |  |  | 	"google.golang.org/grpc/codes" | 
					
						
							|  |  |  | 	"google.golang.org/grpc/internal/channelz" | 
					
						
							|  |  |  | 	istatus "google.golang.org/grpc/internal/status" | 
					
						
							|  |  |  | 	"google.golang.org/grpc/internal/transport" | 
					
						
							| 
									
										
										
										
											2023-09-18 13:47:28 +01:00
										 |  |  | 	"google.golang.org/grpc/stats" | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 	"google.golang.org/grpc/status" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | // pickerGeneration stores a picker and a channel used to signal that a picker | 
					
						
							|  |  |  | // newer than this one is available. | 
					
						
							|  |  |  | type pickerGeneration struct { | 
					
						
							|  |  |  | 	// picker is the picker produced by the LB policy.  May be nil if a picker | 
					
						
							|  |  |  | 	// has never been produced. | 
					
						
							|  |  |  | 	picker balancer.Picker | 
					
						
							|  |  |  | 	// blockingCh is closed when the picker has been invalidated because there | 
					
						
							|  |  |  | 	// is a new one available. | 
					
						
							|  |  |  | 	blockingCh chan struct{} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | // pickerWrapper is a wrapper of balancer.Picker. It blocks on certain pick | 
					
						
							|  |  |  | // actions and unblock when there's a picker update. | 
					
						
							|  |  |  | type pickerWrapper struct { | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 	// If pickerGen holds a nil pointer, the pickerWrapper is closed. | 
					
						
							|  |  |  | 	pickerGen     atomic.Pointer[pickerGeneration] | 
					
						
							| 
									
										
										
										
											2023-09-18 13:47:28 +01:00
										 |  |  | 	statsHandlers []stats.Handler // to record blocking picker calls | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-18 13:47:28 +01:00
										 |  |  | func newPickerWrapper(statsHandlers []stats.Handler) *pickerWrapper { | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 	pw := &pickerWrapper{ | 
					
						
							| 
									
										
										
										
											2023-09-18 13:47:28 +01:00
										 |  |  | 		statsHandlers: statsHandlers, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 	pw.pickerGen.Store(&pickerGeneration{ | 
					
						
							|  |  |  | 		blockingCh: make(chan struct{}), | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	return pw | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | // updatePicker is called by UpdateState calls from the LB policy. It | 
					
						
							|  |  |  | // unblocks all blocked pick. | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | func (pw *pickerWrapper) updatePicker(p balancer.Picker) { | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 	old := pw.pickerGen.Swap(&pickerGeneration{ | 
					
						
							|  |  |  | 		picker:     p, | 
					
						
							|  |  |  | 		blockingCh: make(chan struct{}), | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	close(old.blockingCh) | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // doneChannelzWrapper performs the following: | 
					
						
							|  |  |  | //   - increments the calls started channelz counter | 
					
						
							|  |  |  | //   - wraps the done function in the passed in result to increment the calls | 
					
						
							|  |  |  | //     failed or calls succeeded channelz counter before invoking the actual | 
					
						
							|  |  |  | //     done function. | 
					
						
							| 
									
										
										
										
											2023-09-07 13:20:37 +02:00
										 |  |  | func doneChannelzWrapper(acbw *acBalancerWrapper, result *balancer.PickResult) { | 
					
						
							|  |  |  | 	ac := acbw.ac | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 	ac.incrCallsStarted() | 
					
						
							|  |  |  | 	done := result.Done | 
					
						
							|  |  |  | 	result.Done = func(b balancer.DoneInfo) { | 
					
						
							|  |  |  | 		if b.Err != nil && b.Err != io.EOF { | 
					
						
							|  |  |  | 			ac.incrCallsFailed() | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			ac.incrCallsSucceeded() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if done != nil { | 
					
						
							|  |  |  | 			done(b) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // pick returns the transport that will be used for the RPC. | 
					
						
							|  |  |  | // It may block in the following cases: | 
					
						
							|  |  |  | // - there's no picker | 
					
						
							|  |  |  | // - the current picker returns ErrNoSubConnAvailable | 
					
						
							|  |  |  | // - the current picker returns other errors and failfast is false. | 
					
						
							|  |  |  | // - the subConn returned by the current picker is not READY | 
					
						
							|  |  |  | // When one of these situations happens, pick blocks until the picker gets updated. | 
					
						
							|  |  |  | func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.PickInfo) (transport.ClientTransport, balancer.PickResult, error) { | 
					
						
							|  |  |  | 	var ch chan struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var lastPickErr error | 
					
						
							| 
									
										
										
										
											2023-09-18 13:47:28 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 	for { | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 		pg := pw.pickerGen.Load() | 
					
						
							|  |  |  | 		if pg == nil { | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 			return nil, balancer.PickResult{}, ErrClientConnClosing | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 		if pg.picker == nil { | 
					
						
							|  |  |  | 			ch = pg.blockingCh | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 		if ch == pg.blockingCh { | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 			// This could happen when either: | 
					
						
							|  |  |  | 			// - pw.picker is nil (the previous if condition), or | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 			// - we have already called pick on the current picker. | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 			select { | 
					
						
							|  |  |  | 			case <-ctx.Done(): | 
					
						
							|  |  |  | 				var errStr string | 
					
						
							|  |  |  | 				if lastPickErr != nil { | 
					
						
							|  |  |  | 					errStr = "latest balancer error: " + lastPickErr.Error() | 
					
						
							|  |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2025-03-10 09:23:45 +00:00
										 |  |  | 					errStr = fmt.Sprintf("%v while waiting for connections to become ready", ctx.Err()) | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				switch ctx.Err() { | 
					
						
							|  |  |  | 				case context.DeadlineExceeded: | 
					
						
							|  |  |  | 					return nil, balancer.PickResult{}, status.Error(codes.DeadlineExceeded, errStr) | 
					
						
							|  |  |  | 				case context.Canceled: | 
					
						
							|  |  |  | 					return nil, balancer.PickResult{}, status.Error(codes.Canceled, errStr) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			case <-ch: | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-18 13:47:28 +01:00
										 |  |  | 		// If the channel is set, it means that the pick call had to wait for a | 
					
						
							|  |  |  | 		// new picker at some point. Either it's the first iteration and this | 
					
						
							|  |  |  | 		// function received the first picker, or a picker errored with | 
					
						
							|  |  |  | 		// ErrNoSubConnAvailable or errored with failfast set to false, which | 
					
						
							|  |  |  | 		// will trigger a continue to the next iteration. In the first case this | 
					
						
							|  |  |  | 		// conditional will hit if this call had to block (the channel is set). | 
					
						
							|  |  |  | 		// In the second case, the only way it will get to this conditional is | 
					
						
							|  |  |  | 		// if there is a new picker. | 
					
						
							|  |  |  | 		if ch != nil { | 
					
						
							|  |  |  | 			for _, sh := range pw.statsHandlers { | 
					
						
							|  |  |  | 				sh.HandleRPC(ctx, &stats.PickerUpdated{}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 		ch = pg.blockingCh | 
					
						
							|  |  |  | 		p := pg.picker | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		pickResult, err := p.Pick(info) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			if err == balancer.ErrNoSubConnAvailable { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if st, ok := status.FromError(err); ok { | 
					
						
							|  |  |  | 				// Status error: end the RPC unconditionally with this status. | 
					
						
							|  |  |  | 				// First restrict the code to the list allowed by gRFC A54. | 
					
						
							|  |  |  | 				if istatus.IsRestrictedControlPlaneCode(st) { | 
					
						
							|  |  |  | 					err = status.Errorf(codes.Internal, "received picker error with illegal status: %v", err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return nil, balancer.PickResult{}, dropError{error: err} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// For all other errors, wait for ready RPCs should block and other | 
					
						
							|  |  |  | 			// RPCs should fail with unavailable. | 
					
						
							|  |  |  | 			if !failfast { | 
					
						
							|  |  |  | 				lastPickErr = err | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return nil, balancer.PickResult{}, status.Error(codes.Unavailable, err.Error()) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-07 13:20:37 +02:00
										 |  |  | 		acbw, ok := pickResult.SubConn.(*acBalancerWrapper) | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 		if !ok { | 
					
						
							|  |  |  | 			logger.Errorf("subconn returned from pick is type %T, not *acBalancerWrapper", pickResult.SubConn) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-09-07 13:20:37 +02:00
										 |  |  | 		if t := acbw.ac.getReadyTransport(); t != nil { | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 			if channelz.IsOn() { | 
					
						
							| 
									
										
										
										
											2023-09-07 13:20:37 +02:00
										 |  |  | 				doneChannelzWrapper(acbw, &pickResult) | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 				return t, pickResult, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return t, pickResult, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if pickResult.Done != nil { | 
					
						
							|  |  |  | 			// Calling done with nil error, no bytes sent and no bytes received. | 
					
						
							|  |  |  | 			// DoneInfo with default value works. | 
					
						
							|  |  |  | 			pickResult.Done(balancer.DoneInfo{}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		logger.Infof("blockingPicker: the picked transport is not ready, loop back to repick") | 
					
						
							|  |  |  | 		// If ok == false, ac.state is not READY. | 
					
						
							|  |  |  | 		// A valid picker always returns READY subConn. This means the state of ac | 
					
						
							|  |  |  | 		// just changed, and picker will be updated shortly. | 
					
						
							|  |  |  | 		// continue back to the beginning of the for loop to repick. | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (pw *pickerWrapper) close() { | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 	old := pw.pickerGen.Swap(nil) | 
					
						
							|  |  |  | 	close(old.blockingCh) | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 15:34:34 +01:00
										 |  |  | // reset clears the pickerWrapper and prepares it for being used again when idle | 
					
						
							|  |  |  | // mode is exited. | 
					
						
							|  |  |  | func (pw *pickerWrapper) reset() { | 
					
						
							| 
									
										
										
										
											2024-08-26 18:05:54 +02:00
										 |  |  | 	old := pw.pickerGen.Swap(&pickerGeneration{blockingCh: make(chan struct{})}) | 
					
						
							|  |  |  | 	close(old.blockingCh) | 
					
						
							| 
									
										
										
										
											2023-09-07 13:20:37 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | // dropError is a wrapper error that indicates the LB policy wishes to drop the | 
					
						
							|  |  |  | // RPC and not retry it. | 
					
						
							|  |  |  | type dropError struct { | 
					
						
							|  |  |  | 	error | 
					
						
							|  |  |  | } |