src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

retryable_error.go (6552B)


      1 package retry
      2 
      3 import (
      4 	"errors"
      5 	"fmt"
      6 	"net"
      7 	"net/url"
      8 	"strings"
      9 
     10 	"github.com/aws/aws-sdk-go-v2/aws"
     11 )
     12 
     13 // IsErrorRetryable provides the interface of an implementation to determine if
     14 // a error as the result of an operation is retryable.
     15 type IsErrorRetryable interface {
     16 	IsErrorRetryable(error) aws.Ternary
     17 }
     18 
     19 // IsErrorRetryables is a collection of checks to determine of the error is
     20 // retryable.  Iterates through the checks and returns the state of retryable
     21 // if any check returns something other than unknown.
     22 type IsErrorRetryables []IsErrorRetryable
     23 
     24 // IsErrorRetryable returns if the error is retryable if any of the checks in
     25 // the list return a value other than unknown.
     26 func (r IsErrorRetryables) IsErrorRetryable(err error) aws.Ternary {
     27 	for _, re := range r {
     28 		if v := re.IsErrorRetryable(err); v != aws.UnknownTernary {
     29 			return v
     30 		}
     31 	}
     32 	return aws.UnknownTernary
     33 }
     34 
     35 // IsErrorRetryableFunc wraps a function with the IsErrorRetryable interface.
     36 type IsErrorRetryableFunc func(error) aws.Ternary
     37 
     38 // IsErrorRetryable returns if the error is retryable.
     39 func (fn IsErrorRetryableFunc) IsErrorRetryable(err error) aws.Ternary {
     40 	return fn(err)
     41 }
     42 
     43 // RetryableError is an IsErrorRetryable implementation which uses the
     44 // optional interface Retryable on the error value to determine if the error is
     45 // retryable.
     46 type RetryableError struct{}
     47 
     48 // IsErrorRetryable returns if the error is retryable if it satisfies the
     49 // Retryable interface, and returns if the attempt should be retried.
     50 func (RetryableError) IsErrorRetryable(err error) aws.Ternary {
     51 	var v interface{ RetryableError() bool }
     52 
     53 	if !errors.As(err, &v) {
     54 		return aws.UnknownTernary
     55 	}
     56 
     57 	return aws.BoolTernary(v.RetryableError())
     58 }
     59 
     60 // NoRetryCanceledError detects if the error was an request canceled error and
     61 // returns if so.
     62 type NoRetryCanceledError struct{}
     63 
     64 // IsErrorRetryable returns the error is not retryable if the request was
     65 // canceled.
     66 func (NoRetryCanceledError) IsErrorRetryable(err error) aws.Ternary {
     67 	var v interface{ CanceledError() bool }
     68 
     69 	if !errors.As(err, &v) {
     70 		return aws.UnknownTernary
     71 	}
     72 
     73 	if v.CanceledError() {
     74 		return aws.FalseTernary
     75 	}
     76 	return aws.UnknownTernary
     77 }
     78 
     79 // RetryableConnectionError determines if the underlying error is an HTTP
     80 // connection and returns if it should be retried.
     81 //
     82 // Includes errors such as connection reset, connection refused, net dial,
     83 // temporary, and timeout errors.
     84 type RetryableConnectionError struct{}
     85 
     86 // IsErrorRetryable returns if the error is caused by and HTTP connection
     87 // error, and should be retried.
     88 func (r RetryableConnectionError) IsErrorRetryable(err error) aws.Ternary {
     89 	if err == nil {
     90 		return aws.UnknownTernary
     91 	}
     92 	var retryable bool
     93 
     94 	var conErr interface{ ConnectionError() bool }
     95 	var tempErr interface{ Temporary() bool }
     96 	var timeoutErr interface{ Timeout() bool }
     97 	var urlErr *url.Error
     98 	var netOpErr *net.OpError
     99 	var dnsError *net.DNSError
    100 
    101 	if errors.As(err, &dnsError) {
    102 		// NXDOMAIN errors should not be retried
    103 		if dnsError.IsNotFound {
    104 			return aws.BoolTernary(false)
    105 		}
    106 
    107 		// if !dnsError.Temporary(), error may or may not be temporary,
    108 		// (i.e. !Temporary() =/=> !retryable) so we should fall through to
    109 		// remaining checks
    110 		if dnsError.Temporary() {
    111 			return aws.BoolTernary(true)
    112 		}
    113 	}
    114 
    115 	switch {
    116 	case errors.As(err, &conErr) && conErr.ConnectionError():
    117 		retryable = true
    118 
    119 	case strings.Contains(err.Error(), "use of closed network connection"):
    120 		fallthrough
    121 	case strings.Contains(err.Error(), "connection reset"):
    122 		// The errors "connection reset" and "use of closed network connection"
    123 		// are effectively the same. It appears to be the difference between
    124 		// sync and async read of TCP RST in the stdlib's net.Conn read loop.
    125 		// see #2737
    126 		retryable = true
    127 
    128 	case errors.As(err, &urlErr):
    129 		// Refused connections should be retried as the service may not yet be
    130 		// running on the port. Go TCP dial considers refused connections as
    131 		// not temporary.
    132 		if strings.Contains(urlErr.Error(), "connection refused") {
    133 			retryable = true
    134 		} else {
    135 			return r.IsErrorRetryable(errors.Unwrap(urlErr))
    136 		}
    137 
    138 	case errors.As(err, &netOpErr):
    139 		// Network dial, or temporary network errors are always retryable.
    140 		if strings.EqualFold(netOpErr.Op, "dial") || netOpErr.Temporary() {
    141 			retryable = true
    142 		} else {
    143 			return r.IsErrorRetryable(errors.Unwrap(netOpErr))
    144 		}
    145 
    146 	case errors.As(err, &tempErr) && tempErr.Temporary():
    147 		// Fallback to the generic temporary check, with temporary errors
    148 		// retryable.
    149 		retryable = true
    150 
    151 	case errors.As(err, &timeoutErr) && timeoutErr.Timeout():
    152 		// Fallback to the generic timeout check, with timeout errors
    153 		// retryable.
    154 		retryable = true
    155 
    156 	default:
    157 		return aws.UnknownTernary
    158 	}
    159 
    160 	return aws.BoolTernary(retryable)
    161 
    162 }
    163 
    164 // RetryableHTTPStatusCode provides a IsErrorRetryable based on HTTP status
    165 // codes.
    166 type RetryableHTTPStatusCode struct {
    167 	Codes map[int]struct{}
    168 }
    169 
    170 // IsErrorRetryable return if the passed in error is retryable based on the
    171 // HTTP status code.
    172 func (r RetryableHTTPStatusCode) IsErrorRetryable(err error) aws.Ternary {
    173 	var v interface{ HTTPStatusCode() int }
    174 
    175 	if !errors.As(err, &v) {
    176 		return aws.UnknownTernary
    177 	}
    178 
    179 	_, ok := r.Codes[v.HTTPStatusCode()]
    180 	if !ok {
    181 		return aws.UnknownTernary
    182 	}
    183 
    184 	return aws.TrueTernary
    185 }
    186 
    187 // RetryableErrorCode determines if an attempt should be retried based on the
    188 // API error code.
    189 type RetryableErrorCode struct {
    190 	Codes map[string]struct{}
    191 }
    192 
    193 // IsErrorRetryable return if the error is retryable based on the error codes.
    194 // Returns unknown if the error doesn't have a code or it is unknown.
    195 func (r RetryableErrorCode) IsErrorRetryable(err error) aws.Ternary {
    196 	var v interface{ ErrorCode() string }
    197 
    198 	if !errors.As(err, &v) {
    199 		return aws.UnknownTernary
    200 	}
    201 
    202 	_, ok := r.Codes[v.ErrorCode()]
    203 	if !ok {
    204 		return aws.UnknownTernary
    205 	}
    206 
    207 	return aws.TrueTernary
    208 }
    209 
    210 // retryableClockSkewError marks errors that can be caused by clock skew
    211 // (difference between server time and client time).
    212 // This is returned when there's certain confidence that adjusting the client time
    213 // could allow a retry to succeed
    214 type retryableClockSkewError struct{ Err error }
    215 
    216 func (e *retryableClockSkewError) Error() string {
    217 	return fmt.Sprintf("Probable clock skew error: %v", e.Err)
    218 }
    219 
    220 // Unwrap returns the wrapped error.
    221 func (e *retryableClockSkewError) Unwrap() error {
    222 	return e.Err
    223 }
    224 
    225 // RetryableError allows the retryer to retry this request
    226 func (e *retryableClockSkewError) RetryableError() bool {
    227 	return true
    228 }