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  }