github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/xerrors/transport.go (about)

     1  package xerrors
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  
     8  	grpcCodes "google.golang.org/grpc/codes"
     9  	grpcStatus "google.golang.org/grpc/status"
    10  
    11  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/backoff"
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    13  )
    14  
    15  type transportError struct {
    16  	status  *grpcStatus.Status
    17  	err     error
    18  	address string
    19  	traceID string
    20  }
    21  
    22  func (e *transportError) GRPCStatus() *grpcStatus.Status {
    23  	return e.status
    24  }
    25  
    26  func (e *transportError) isYdbError() {}
    27  
    28  func (e *transportError) Code() int32 {
    29  	return int32(e.status.Code())
    30  }
    31  
    32  func (e *transportError) Name() string {
    33  	return "transport/" + e.status.Code().String()
    34  }
    35  
    36  type teOpt interface {
    37  	applyToTransportError(te *transportError)
    38  }
    39  
    40  type addressOption string
    41  
    42  func (address addressOption) applyToTransportError(te *transportError) {
    43  	te.address = string(address)
    44  }
    45  
    46  func WithAddress(address string) addressOption {
    47  	return addressOption(address)
    48  }
    49  
    50  func (e *transportError) Error() string {
    51  	var b bytes.Buffer
    52  	b.WriteString(e.Name())
    53  	b.WriteString(fmt.Sprintf(" (code = %d, source error = %q", e.status.Code(), e.err.Error()))
    54  	if len(e.address) > 0 {
    55  		b.WriteString(fmt.Sprintf(", address: %q", e.address))
    56  	}
    57  	if len(e.traceID) > 0 {
    58  		b.WriteString(fmt.Sprintf(", traceID: %q", e.traceID))
    59  	}
    60  	b.WriteString(")")
    61  
    62  	return b.String()
    63  }
    64  
    65  func (e *transportError) Unwrap() error {
    66  	return e.err
    67  }
    68  
    69  func (e *transportError) Type() Type {
    70  	switch e.status.Code() {
    71  	case
    72  		grpcCodes.Aborted,
    73  		grpcCodes.ResourceExhausted:
    74  		return TypeRetryable
    75  	case
    76  		grpcCodes.Internal,
    77  		grpcCodes.Canceled,
    78  		grpcCodes.DeadlineExceeded,
    79  		grpcCodes.Unavailable:
    80  		return TypeConditionallyRetryable
    81  	default:
    82  		return TypeUndefined
    83  	}
    84  }
    85  
    86  func (e *transportError) BackoffType() backoff.Type {
    87  	switch e.status.Code() {
    88  	case
    89  		grpcCodes.Internal,
    90  		grpcCodes.Canceled,
    91  		grpcCodes.DeadlineExceeded,
    92  		grpcCodes.Unavailable:
    93  		return backoff.TypeFast
    94  	case grpcCodes.ResourceExhausted:
    95  		return backoff.TypeSlow
    96  	default:
    97  		return backoff.TypeNoBackoff
    98  	}
    99  }
   100  
   101  func (e *transportError) MustDeleteSession() bool {
   102  	switch e.status.Code() {
   103  	case
   104  		grpcCodes.ResourceExhausted,
   105  		grpcCodes.OutOfRange:
   106  		return false
   107  	default:
   108  		return true
   109  	}
   110  }
   111  
   112  // IsTransportError reports whether err is transportError with given grpc codes
   113  func IsTransportError(err error, codes ...grpcCodes.Code) bool {
   114  	if err == nil {
   115  		return false
   116  	}
   117  	var status *grpcStatus.Status
   118  	if t := (*transportError)(nil); errors.As(err, &t) {
   119  		status = t.status
   120  	} else if t, has := grpcStatus.FromError(err); has {
   121  		status = t
   122  	}
   123  	if status != nil {
   124  		if len(codes) == 0 {
   125  			return true
   126  		}
   127  		for _, code := range codes {
   128  			if status.Code() == code {
   129  				return true
   130  			}
   131  		}
   132  	}
   133  
   134  	return false
   135  }
   136  
   137  // Transport returns a new transport error with given options
   138  func Transport(err error, opts ...teOpt) error {
   139  	if err == nil {
   140  		return nil
   141  	}
   142  	var te *transportError
   143  	if errors.As(err, &te) {
   144  		return err
   145  	}
   146  	if s, ok := grpcStatus.FromError(err); ok {
   147  		te = &transportError{
   148  			status: s,
   149  			err:    err,
   150  		}
   151  	} else {
   152  		te = &transportError{
   153  			status: grpcStatus.New(grpcCodes.Unknown, stack.Record(1)),
   154  			err:    err,
   155  		}
   156  	}
   157  	for _, opt := range opts {
   158  		if opt != nil {
   159  			opt.applyToTransportError(te)
   160  		}
   161  	}
   162  
   163  	return te
   164  }
   165  
   166  func MustPessimizeEndpoint(err error, codes ...grpcCodes.Code) bool {
   167  	switch {
   168  	case err == nil:
   169  		return false
   170  
   171  	// all transport errors except selected codes
   172  	case IsTransportError(err) && !IsTransportError(
   173  		err,
   174  		append(
   175  			codes,
   176  			grpcCodes.ResourceExhausted,
   177  			grpcCodes.OutOfRange,
   178  		)...,
   179  	):
   180  		return true
   181  
   182  	default:
   183  		return false
   184  	}
   185  }
   186  
   187  func TransportError(err error) Error {
   188  	if err == nil {
   189  		return nil
   190  	}
   191  	var t *transportError
   192  	if errors.As(err, &t) {
   193  		return t
   194  	}
   195  	if s, ok := grpcStatus.FromError(err); ok {
   196  		return &transportError{
   197  			status: s,
   198  			err:    err,
   199  		}
   200  	}
   201  
   202  	return nil
   203  }