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 }