github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fs/fserrors/error.go (about) 1 // Package fserrors provides errors and error handling 2 package fserrors 3 4 import ( 5 "fmt" 6 "io" 7 "net/http" 8 "strings" 9 "time" 10 11 "github.com/ncw/rclone/lib/errors" 12 ) 13 14 // Retrier is an optional interface for error as to whether the 15 // operation should be retried at a high level. 16 // 17 // This should be returned from Update or Put methods as required 18 type Retrier interface { 19 error 20 Retry() bool 21 } 22 23 // retryError is a type of error 24 type retryError string 25 26 // Error interface 27 func (r retryError) Error() string { 28 return string(r) 29 } 30 31 // Retry interface 32 func (r retryError) Retry() bool { 33 return true 34 } 35 36 // Check interface 37 var _ Retrier = retryError("") 38 39 // RetryErrorf makes an error which indicates it would like to be retried 40 func RetryErrorf(format string, a ...interface{}) error { 41 return retryError(fmt.Sprintf(format, a...)) 42 } 43 44 // wrappedRetryError is an error wrapped so it will satisfy the 45 // Retrier interface and return true 46 type wrappedRetryError struct { 47 error 48 } 49 50 // Retry interface 51 func (err wrappedRetryError) Retry() bool { 52 return true 53 } 54 55 // Check interface 56 var _ Retrier = wrappedRetryError{error(nil)} 57 58 // RetryError makes an error which indicates it would like to be retried 59 func RetryError(err error) error { 60 if err == nil { 61 err = errors.New("needs retry") 62 } 63 return wrappedRetryError{err} 64 } 65 66 func (err wrappedRetryError) Cause() error { 67 return err.error 68 } 69 70 // IsRetryError returns true if err conforms to the Retry interface 71 // and calling the Retry method returns true. 72 func IsRetryError(err error) (isRetry bool) { 73 errors.Walk(err, func(err error) bool { 74 if r, ok := err.(Retrier); ok { 75 isRetry = r.Retry() 76 return true 77 } 78 return false 79 }) 80 return 81 } 82 83 // Fataler is an optional interface for error as to whether the 84 // operation should cause the entire operation to finish immediately. 85 // 86 // This should be returned from Update or Put methods as required 87 type Fataler interface { 88 error 89 Fatal() bool 90 } 91 92 // wrappedFatalError is an error wrapped so it will satisfy the 93 // Retrier interface and return true 94 type wrappedFatalError struct { 95 error 96 } 97 98 // Fatal interface 99 func (err wrappedFatalError) Fatal() bool { 100 return true 101 } 102 103 // Check interface 104 var _ Fataler = wrappedFatalError{error(nil)} 105 106 // FatalError makes an error which indicates it is a fatal error and 107 // the sync should stop. 108 func FatalError(err error) error { 109 if err == nil { 110 err = errors.New("fatal error") 111 } 112 return wrappedFatalError{err} 113 } 114 115 func (err wrappedFatalError) Cause() error { 116 return err.error 117 } 118 119 // IsFatalError returns true if err conforms to the Fatal interface 120 // and calling the Fatal method returns true. 121 func IsFatalError(err error) (isFatal bool) { 122 errors.Walk(err, func(err error) bool { 123 if r, ok := err.(Fataler); ok { 124 isFatal = r.Fatal() 125 return true 126 } 127 return false 128 }) 129 return 130 } 131 132 // NoRetrier is an optional interface for error as to whether the 133 // operation should not be retried at a high level. 134 // 135 // If only NoRetry errors are returned in a sync then the sync won't 136 // be retried. 137 // 138 // This should be returned from Update or Put methods as required 139 type NoRetrier interface { 140 error 141 NoRetry() bool 142 } 143 144 // wrappedNoRetryError is an error wrapped so it will satisfy the 145 // Retrier interface and return true 146 type wrappedNoRetryError struct { 147 error 148 } 149 150 // NoRetry interface 151 func (err wrappedNoRetryError) NoRetry() bool { 152 return true 153 } 154 155 // Check interface 156 var _ NoRetrier = wrappedNoRetryError{error(nil)} 157 158 // NoRetryError makes an error which indicates the sync shouldn't be 159 // retried. 160 func NoRetryError(err error) error { 161 return wrappedNoRetryError{err} 162 } 163 164 func (err wrappedNoRetryError) Cause() error { 165 return err.error 166 } 167 168 // IsNoRetryError returns true if err conforms to the NoRetry 169 // interface and calling the NoRetry method returns true. 170 func IsNoRetryError(err error) (isNoRetry bool) { 171 errors.Walk(err, func(err error) bool { 172 if r, ok := err.(NoRetrier); ok { 173 isNoRetry = r.NoRetry() 174 return true 175 } 176 return false 177 }) 178 return 179 } 180 181 // RetryAfter is an optional interface for error as to whether the 182 // operation should be retried after a given delay 183 // 184 // This should be returned from Update or Put methods as required and 185 // will cause the entire sync to be retried after a delay. 186 type RetryAfter interface { 187 error 188 RetryAfter() time.Time 189 } 190 191 // ErrorRetryAfter is an error which expresses a time that should be 192 // waited for until trying again 193 type ErrorRetryAfter time.Time 194 195 // NewErrorRetryAfter returns an ErrorRetryAfter with the given 196 // duration as an endpoint 197 func NewErrorRetryAfter(d time.Duration) ErrorRetryAfter { 198 return ErrorRetryAfter(time.Now().Add(d)) 199 } 200 201 // Error returns the textual version of the error 202 func (e ErrorRetryAfter) Error() string { 203 return fmt.Sprintf("try again after %v (%v)", time.Time(e).Format(time.RFC3339Nano), time.Time(e).Sub(time.Now())) 204 } 205 206 // RetryAfter returns the time the operation should be retried at or 207 // after 208 func (e ErrorRetryAfter) RetryAfter() time.Time { 209 return time.Time(e) 210 } 211 212 // Check interface 213 var _ RetryAfter = ErrorRetryAfter{} 214 215 // RetryAfterErrorTime returns the time that the RetryAfter error 216 // indicates or a Zero time.Time 217 func RetryAfterErrorTime(err error) (retryAfter time.Time) { 218 errors.Walk(err, func(err error) bool { 219 if r, ok := err.(RetryAfter); ok { 220 retryAfter = r.RetryAfter() 221 return true 222 } 223 return false 224 }) 225 return 226 } 227 228 // IsRetryAfterError returns true if err is an ErrorRetryAfter 229 func IsRetryAfterError(err error) bool { 230 return !RetryAfterErrorTime(err).IsZero() 231 } 232 233 // Cause is a souped up errors.Cause which can unwrap some standard 234 // library errors too. It returns true if any of the intermediate 235 // errors had a Timeout() or Temporary() method which returned true. 236 func Cause(cause error) (retriable bool, err error) { 237 errors.Walk(cause, func(c error) bool { 238 // Check for net error Timeout() 239 if x, ok := err.(interface { 240 Timeout() bool 241 }); ok && x.Timeout() { 242 retriable = true 243 } 244 245 // Check for net error Temporary() 246 if x, ok := err.(interface { 247 Temporary() bool 248 }); ok && x.Temporary() { 249 retriable = true 250 } 251 err = c 252 return false 253 }) 254 return 255 } 256 257 // retriableErrorStrings is a list of phrases which when we find it 258 // in an an error, we know it is a networking error which should be 259 // retried. 260 // 261 // This is incredibly ugly - if only errors.Cause worked for all 262 // errors and all errors were exported from the stdlib. 263 var retriableErrorStrings = []string{ 264 "use of closed network connection", // internal/poll/fd.go 265 "unexpected EOF reading trailer", // net/http/transfer.go 266 "transport connection broken", // net/http/transport.go 267 "http: ContentLength=", // net/http/transfer.go 268 "server closed idle connection", // net/http/transport.go 269 } 270 271 // Errors which indicate networking errors which should be retried 272 // 273 // These are added to in retriable_errors*.go 274 var retriableErrors = []error{ 275 io.EOF, 276 io.ErrUnexpectedEOF, 277 } 278 279 // ShouldRetry looks at an error and tries to work out if retrying the 280 // operation that caused it would be a good idea. It returns true if 281 // the error implements Timeout() or Temporary() or if the error 282 // indicates a premature closing of the connection. 283 func ShouldRetry(err error) bool { 284 if err == nil { 285 return false 286 } 287 288 // Find root cause if available 289 retriable, err := Cause(err) 290 if retriable { 291 return true 292 } 293 294 // Check if it is a retriable error 295 for _, retriableErr := range retriableErrors { 296 if err == retriableErr { 297 return true 298 } 299 } 300 301 // Check error strings (yuch!) too 302 errString := err.Error() 303 for _, phrase := range retriableErrorStrings { 304 if strings.Contains(errString, phrase) { 305 return true 306 } 307 } 308 309 return false 310 } 311 312 // ShouldRetryHTTP returns a boolean as to whether this resp deserves. 313 // It checks to see if the HTTP response code is in the slice 314 // retryErrorCodes. 315 func ShouldRetryHTTP(resp *http.Response, retryErrorCodes []int) bool { 316 if resp == nil { 317 return false 318 } 319 for _, e := range retryErrorCodes { 320 if resp.StatusCode == e { 321 return true 322 } 323 } 324 return false 325 } 326 327 type causer interface { 328 Cause() error 329 } 330 331 var ( 332 _ causer = wrappedRetryError{} 333 _ causer = wrappedFatalError{} 334 _ causer = wrappedNoRetryError{} 335 )