code.dwrz.net

Go monorepo.
Log | Files | Refs

retryable_error.go (5187B)


      1 package retry
      2 
      3 import (
      4 	"errors"
      5 	"net"
      6 	"net/url"
      7 	"strings"
      8 
      9 	"github.com/aws/aws-sdk-go-v2/aws"
     10 )
     11 
     12 // IsErrorRetryable provides the interface of an implementation to determine if
     13 // a error as the result of an operation is retryable.
     14 type IsErrorRetryable interface {
     15 	IsErrorRetryable(error) aws.Ternary
     16 }
     17 
     18 // IsErrorRetryables is a collection of checks to determine of the error is
     19 // retryable.  Iterates through the checks and returns the state of retryable
     20 // if any check returns something other than unknown.
     21 type IsErrorRetryables []IsErrorRetryable
     22 
     23 // IsErrorRetryable returns if the error is retryable if any of the checks in
     24 // the list return a value other than unknown.
     25 func (r IsErrorRetryables) IsErrorRetryable(err error) aws.Ternary {
     26 	for _, re := range r {
     27 		if v := re.IsErrorRetryable(err); v != aws.UnknownTernary {
     28 			return v
     29 		}
     30 	}
     31 	return aws.UnknownTernary
     32 }
     33 
     34 // IsErrorRetryableFunc wraps a function with the IsErrorRetryable interface.
     35 type IsErrorRetryableFunc func(error) aws.Ternary
     36 
     37 // IsErrorRetryable returns if the error is retryable.
     38 func (fn IsErrorRetryableFunc) IsErrorRetryable(err error) aws.Ternary {
     39 	return fn(err)
     40 }
     41 
     42 // RetryableError is an IsErrorRetryable implementation which uses the
     43 // optional interface Retryable on the error value to determine if the error is
     44 // retryable.
     45 type RetryableError struct{}
     46 
     47 // IsErrorRetryable returns if the error is retryable if it satisfies the
     48 // Retryable interface, and returns if the attempt should be retried.
     49 func (RetryableError) IsErrorRetryable(err error) aws.Ternary {
     50 	var v interface{ RetryableError() bool }
     51 
     52 	if !errors.As(err, &v) {
     53 		return aws.UnknownTernary
     54 	}
     55 
     56 	return aws.BoolTernary(v.RetryableError())
     57 }
     58 
     59 // NoRetryCanceledError detects if the error was an request canceled error and
     60 // returns if so.
     61 type NoRetryCanceledError struct{}
     62 
     63 // IsErrorRetryable returns the error is not retryable if the request was
     64 // canceled.
     65 func (NoRetryCanceledError) IsErrorRetryable(err error) aws.Ternary {
     66 	var v interface{ CanceledError() bool }
     67 
     68 	if !errors.As(err, &v) {
     69 		return aws.UnknownTernary
     70 	}
     71 
     72 	if v.CanceledError() {
     73 		return aws.FalseTernary
     74 	}
     75 	return aws.UnknownTernary
     76 }
     77 
     78 // RetryableConnectionError determines if the underlying error is an HTTP
     79 // connection and returns if it should be retried.
     80 //
     81 // Includes errors such as connection reset, connection refused, net dial,
     82 // temporary, and timeout errors.
     83 type RetryableConnectionError struct{}
     84 
     85 // IsErrorRetryable returns if the error is caused by and HTTP connection
     86 // error, and should be retried.
     87 func (r RetryableConnectionError) IsErrorRetryable(err error) aws.Ternary {
     88 	if err == nil {
     89 		return aws.UnknownTernary
     90 	}
     91 	var retryable bool
     92 
     93 	var conErr interface{ ConnectionError() bool }
     94 	var tempErr interface{ Temporary() bool }
     95 	var timeoutErr interface{ Timeout() bool }
     96 	var urlErr *url.Error
     97 	var netOpErr *net.OpError
     98 
     99 	switch {
    100 	case errors.As(err, &conErr) && conErr.ConnectionError():
    101 		retryable = true
    102 
    103 	case strings.Contains(err.Error(), "connection reset"):
    104 		retryable = true
    105 
    106 	case errors.As(err, &urlErr):
    107 		// Refused connections should be retried as the service may not yet be
    108 		// running on the port. Go TCP dial considers refused connections as
    109 		// not temporary.
    110 		if strings.Contains(urlErr.Error(), "connection refused") {
    111 			retryable = true
    112 		} else {
    113 			return r.IsErrorRetryable(errors.Unwrap(urlErr))
    114 		}
    115 
    116 	case errors.As(err, &netOpErr):
    117 		// Network dial, or temporary network errors are always retryable.
    118 		if strings.EqualFold(netOpErr.Op, "dial") || netOpErr.Temporary() {
    119 			retryable = true
    120 		} else {
    121 			return r.IsErrorRetryable(errors.Unwrap(netOpErr))
    122 		}
    123 
    124 	case errors.As(err, &tempErr) && tempErr.Temporary():
    125 		// Fallback to the generic temporary check, with temporary errors
    126 		// retryable.
    127 		retryable = true
    128 
    129 	case errors.As(err, &timeoutErr) && timeoutErr.Timeout():
    130 		// Fallback to the generic timeout check, with timeout errors
    131 		// retryable.
    132 		retryable = true
    133 
    134 	default:
    135 		return aws.UnknownTernary
    136 	}
    137 
    138 	return aws.BoolTernary(retryable)
    139 
    140 }
    141 
    142 // RetryableHTTPStatusCode provides a IsErrorRetryable based on HTTP status
    143 // codes.
    144 type RetryableHTTPStatusCode struct {
    145 	Codes map[int]struct{}
    146 }
    147 
    148 // IsErrorRetryable return if the passed in error is retryable based on the
    149 // HTTP status code.
    150 func (r RetryableHTTPStatusCode) IsErrorRetryable(err error) aws.Ternary {
    151 	var v interface{ HTTPStatusCode() int }
    152 
    153 	if !errors.As(err, &v) {
    154 		return aws.UnknownTernary
    155 	}
    156 
    157 	_, ok := r.Codes[v.HTTPStatusCode()]
    158 	if !ok {
    159 		return aws.UnknownTernary
    160 	}
    161 
    162 	return aws.TrueTernary
    163 }
    164 
    165 // RetryableErrorCode determines if an attempt should be retried based on the
    166 // API error code.
    167 type RetryableErrorCode struct {
    168 	Codes map[string]struct{}
    169 }
    170 
    171 // IsErrorRetryable return if the error is retryable based on the error codes.
    172 // Returns unknown if the error doesn't have a code or it is unknown.
    173 func (r RetryableErrorCode) IsErrorRetryable(err error) aws.Ternary {
    174 	var v interface{ ErrorCode() string }
    175 
    176 	if !errors.As(err, &v) {
    177 		return aws.UnknownTernary
    178 	}
    179 
    180 	_, ok := r.Codes[v.ErrorCode()]
    181 	if !ok {
    182 		return aws.UnknownTernary
    183 	}
    184 
    185 	return aws.TrueTernary
    186 }