github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/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/rclone/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 // NoLowLevelRetrier is an optional interface for error as to whether 182 // the operation should not be retried at a low level. 183 // 184 // NoLowLevelRetry errors won't be retried by low level retry loops. 185 type NoLowLevelRetrier interface { 186 error 187 NoLowLevelRetry() bool 188 } 189 190 // wrappedNoLowLevelRetryError is an error wrapped so it will satisfy the 191 // NoLowLevelRetrier interface and return true 192 type wrappedNoLowLevelRetryError struct { 193 error 194 } 195 196 // NoLowLevelRetry interface 197 func (err wrappedNoLowLevelRetryError) NoLowLevelRetry() bool { 198 return true 199 } 200 201 // Check interface 202 var _ NoLowLevelRetrier = wrappedNoLowLevelRetryError{error(nil)} 203 204 // NoLowLevelRetryError makes an error which indicates the sync 205 // shouldn't be low level retried. 206 func NoLowLevelRetryError(err error) error { 207 return wrappedNoLowLevelRetryError{err} 208 } 209 210 // Cause returns the underlying error 211 func (err wrappedNoLowLevelRetryError) Cause() error { 212 return err.error 213 } 214 215 // IsNoLowLevelRetryError returns true if err conforms to the NoLowLevelRetry 216 // interface and calling the NoLowLevelRetry method returns true. 217 func IsNoLowLevelRetryError(err error) (isNoLowLevelRetry bool) { 218 errors.Walk(err, func(err error) bool { 219 if r, ok := err.(NoLowLevelRetrier); ok { 220 isNoLowLevelRetry = r.NoLowLevelRetry() 221 return true 222 } 223 return false 224 }) 225 return 226 } 227 228 // RetryAfter is an optional interface for error as to whether the 229 // operation should be retried after a given delay 230 // 231 // This should be returned from Update or Put methods as required and 232 // will cause the entire sync to be retried after a delay. 233 type RetryAfter interface { 234 error 235 RetryAfter() time.Time 236 } 237 238 // ErrorRetryAfter is an error which expresses a time that should be 239 // waited for until trying again 240 type ErrorRetryAfter time.Time 241 242 // NewErrorRetryAfter returns an ErrorRetryAfter with the given 243 // duration as an endpoint 244 func NewErrorRetryAfter(d time.Duration) ErrorRetryAfter { 245 return ErrorRetryAfter(time.Now().Add(d)) 246 } 247 248 // Error returns the textual version of the error 249 func (e ErrorRetryAfter) Error() string { 250 return fmt.Sprintf("try again after %v (%v)", time.Time(e).Format(time.RFC3339Nano), time.Time(e).Sub(time.Now())) 251 } 252 253 // RetryAfter returns the time the operation should be retried at or 254 // after 255 func (e ErrorRetryAfter) RetryAfter() time.Time { 256 return time.Time(e) 257 } 258 259 // Check interface 260 var _ RetryAfter = ErrorRetryAfter{} 261 262 // RetryAfterErrorTime returns the time that the RetryAfter error 263 // indicates or a Zero time.Time 264 func RetryAfterErrorTime(err error) (retryAfter time.Time) { 265 errors.Walk(err, func(err error) bool { 266 if r, ok := err.(RetryAfter); ok { 267 retryAfter = r.RetryAfter() 268 return true 269 } 270 return false 271 }) 272 return 273 } 274 275 // IsRetryAfterError returns true if err is an ErrorRetryAfter 276 func IsRetryAfterError(err error) bool { 277 return !RetryAfterErrorTime(err).IsZero() 278 } 279 280 // CountableError is an optional interface for error. It stores a boolean 281 // which signifies if the error has already been counted or not 282 type CountableError interface { 283 error 284 Count() 285 IsCounted() bool 286 } 287 288 // wrappedFatalError is an error wrapped so it will satisfy the 289 // Retrier interface and return true 290 type wrappedCountableError struct { 291 error 292 isCounted bool 293 } 294 295 // CountableError interface 296 func (err *wrappedCountableError) Count() { 297 err.isCounted = true 298 } 299 300 // CountableError interface 301 func (err *wrappedCountableError) IsCounted() bool { 302 return err.isCounted 303 } 304 305 func (err *wrappedCountableError) Cause() error { 306 return err.error 307 } 308 309 // IsCounted returns true if err conforms to the CountableError interface 310 // and has already been counted 311 func IsCounted(err error) bool { 312 if r, ok := err.(CountableError); ok { 313 return r.IsCounted() 314 } 315 return false 316 } 317 318 // Count sets the isCounted variable on the error if it conforms to the 319 // CountableError interface 320 func Count(err error) { 321 if r, ok := err.(CountableError); ok { 322 r.Count() 323 } 324 } 325 326 // Check interface 327 var _ CountableError = &wrappedCountableError{error: error(nil)} 328 329 // FsError makes an error which can keep a record that it is already counted 330 // or not 331 func FsError(err error) error { 332 if err == nil { 333 err = errors.New("countable error") 334 } 335 return &wrappedCountableError{error: err} 336 } 337 338 // Cause is a souped up errors.Cause which can unwrap some standard 339 // library errors too. It returns true if any of the intermediate 340 // errors had a Timeout() or Temporary() method which returned true. 341 func Cause(cause error) (retriable bool, err error) { 342 errors.Walk(cause, func(c error) bool { 343 // Check for net error Timeout() 344 if x, ok := c.(interface { 345 Timeout() bool 346 }); ok && x.Timeout() { 347 retriable = true 348 } 349 350 // Check for net error Temporary() 351 if x, ok := c.(interface { 352 Temporary() bool 353 }); ok && x.Temporary() { 354 retriable = true 355 } 356 err = c 357 return false 358 }) 359 return 360 } 361 362 // retriableErrorStrings is a list of phrases which when we find it 363 // in an error, we know it is a networking error which should be 364 // retried. 365 // 366 // This is incredibly ugly - if only errors.Cause worked for all 367 // errors and all errors were exported from the stdlib. 368 var retriableErrorStrings = []string{ 369 "use of closed network connection", // internal/poll/fd.go 370 "unexpected EOF reading trailer", // net/http/transfer.go 371 "transport connection broken", // net/http/transport.go 372 "http: ContentLength=", // net/http/transfer.go 373 "server closed idle connection", // net/http/transport.go 374 "bad record MAC", // crypto/tls/alert.go 375 "stream error:", // net/http/h2_bundle.go 376 "tls: use of closed connection", // crypto/tls/conn.go 377 } 378 379 // Errors which indicate networking errors which should be retried 380 // 381 // These are added to in retriable_errors*.go 382 var retriableErrors = []error{ 383 io.EOF, 384 io.ErrUnexpectedEOF, 385 } 386 387 // ShouldRetry looks at an error and tries to work out if retrying the 388 // operation that caused it would be a good idea. It returns true if 389 // the error implements Timeout() or Temporary() or if the error 390 // indicates a premature closing of the connection. 391 func ShouldRetry(err error) bool { 392 if err == nil { 393 return false 394 } 395 396 // If error has been marked to NoLowLevelRetry then don't retry 397 if IsNoLowLevelRetryError(err) { 398 return false 399 } 400 401 // Find root cause if available 402 retriable, err := Cause(err) 403 if retriable { 404 return true 405 } 406 407 // Check if it is a retriable error 408 for _, retriableErr := range retriableErrors { 409 if err == retriableErr { 410 return true 411 } 412 } 413 414 // Check error strings (yuch!) too 415 errString := err.Error() 416 for _, phrase := range retriableErrorStrings { 417 if strings.Contains(errString, phrase) { 418 return true 419 } 420 } 421 422 return false 423 } 424 425 // ShouldRetryHTTP returns a boolean as to whether this resp deserves. 426 // It checks to see if the HTTP response code is in the slice 427 // retryErrorCodes. 428 func ShouldRetryHTTP(resp *http.Response, retryErrorCodes []int) bool { 429 if resp == nil { 430 return false 431 } 432 for _, e := range retryErrorCodes { 433 if resp.StatusCode == e { 434 return true 435 } 436 } 437 return false 438 } 439 440 type causer interface { 441 Cause() error 442 } 443 444 var ( 445 _ causer = wrappedRetryError{} 446 _ causer = wrappedFatalError{} 447 _ causer = wrappedNoRetryError{} 448 )