github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/fserrors/error.go (about) 1 // Package fserrors provides errors and error handling 2 package fserrors 3 4 import ( 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "strings" 11 "time" 12 13 liberrors "github.com/rclone/rclone/lib/errors" 14 ) 15 16 // Must be satisfied for errors.Is/errors.As/Errors.Unwrap 17 type unwrapper interface { 18 Unwrap() error 19 } 20 21 // Retrier is an optional interface for error as to whether the 22 // operation should be retried at a high level. 23 // 24 // This should be returned from Update or Put methods as required 25 type Retrier interface { 26 error 27 Retry() bool 28 } 29 30 // retryError is a type of error 31 type retryError string 32 33 // Error interface 34 func (r retryError) Error() string { 35 return string(r) 36 } 37 38 // Retry interface 39 func (r retryError) Retry() bool { 40 return true 41 } 42 43 // Check interfaces 44 var _ Retrier = retryError("") 45 46 // RetryErrorf makes an error which indicates it would like to be retried 47 func RetryErrorf(format string, a ...interface{}) error { 48 return retryError(fmt.Sprintf(format, a...)) 49 } 50 51 // wrappedRetryError is an error wrapped so it will satisfy the 52 // Retrier interface and return true 53 type wrappedRetryError struct { 54 error 55 } 56 57 // Retry interface 58 func (err wrappedRetryError) Retry() bool { 59 return true 60 } 61 62 // Check interfaces 63 var _ Retrier = wrappedRetryError{error(nil)} 64 var _ unwrapper = wrappedRetryError{} 65 66 // RetryError makes an error which indicates it would like to be retried 67 func RetryError(err error) error { 68 if err == nil { 69 err = errors.New("needs retry") 70 } 71 return wrappedRetryError{err} 72 } 73 74 func (err wrappedRetryError) Unwrap() error { 75 return err.error 76 } 77 78 // IsRetryError returns true if err conforms to the Retry interface 79 // and calling the Retry method returns true. 80 func IsRetryError(err error) (isRetry bool) { 81 liberrors.Walk(err, func(err error) bool { 82 if r, ok := err.(Retrier); ok { 83 isRetry = r.Retry() 84 return true 85 } 86 return false 87 }) 88 return 89 } 90 91 // Fataler is an optional interface for error as to whether the 92 // operation should cause the entire operation to finish immediately. 93 // 94 // This should be returned from Update or Put methods as required 95 type Fataler interface { 96 error 97 Fatal() bool 98 } 99 100 // wrappedFatalError is an error wrapped so it will satisfy the 101 // Retrier interface and return true 102 type wrappedFatalError struct { 103 error 104 } 105 106 // Fatal interface 107 func (err wrappedFatalError) Fatal() bool { 108 return true 109 } 110 111 // Check interfaces 112 var _ Fataler = wrappedFatalError{error(nil)} 113 var _ unwrapper = wrappedFatalError{} 114 115 // FatalError makes an error which indicates it is a fatal error and 116 // the sync should stop. 117 func FatalError(err error) error { 118 if err == nil { 119 err = errors.New("fatal error") 120 } 121 return wrappedFatalError{err} 122 } 123 124 func (err wrappedFatalError) Unwrap() error { 125 return err.error 126 } 127 128 // IsFatalError returns true if err conforms to the Fatal interface 129 // and calling the Fatal method returns true. 130 func IsFatalError(err error) (isFatal bool) { 131 liberrors.Walk(err, func(err error) bool { 132 if r, ok := err.(Fataler); ok { 133 isFatal = r.Fatal() 134 return true 135 } 136 return false 137 }) 138 return 139 } 140 141 // NoRetrier is an optional interface for error as to whether the 142 // operation should not be retried at a high level. 143 // 144 // If only NoRetry errors are returned in a sync then the sync won't 145 // be retried. 146 // 147 // This should be returned from Update or Put methods as required 148 type NoRetrier interface { 149 error 150 NoRetry() bool 151 } 152 153 // wrappedNoRetryError is an error wrapped so it will satisfy the 154 // Retrier interface and return true 155 type wrappedNoRetryError struct { 156 error 157 } 158 159 // NoRetry interface 160 func (err wrappedNoRetryError) NoRetry() bool { 161 return true 162 } 163 164 // Check interfaces 165 var _ NoRetrier = wrappedNoRetryError{error(nil)} 166 var _ unwrapper = wrappedNoRetryError{} 167 168 // NoRetryError makes an error which indicates the sync shouldn't be 169 // retried. 170 func NoRetryError(err error) error { 171 return wrappedNoRetryError{err} 172 } 173 174 func (err wrappedNoRetryError) Unwrap() error { 175 return err.error 176 } 177 178 // IsNoRetryError returns true if err conforms to the NoRetry 179 // interface and calling the NoRetry method returns true. 180 func IsNoRetryError(err error) (isNoRetry bool) { 181 liberrors.Walk(err, func(err error) bool { 182 if r, ok := err.(NoRetrier); ok { 183 isNoRetry = r.NoRetry() 184 return true 185 } 186 return false 187 }) 188 return 189 } 190 191 // NoLowLevelRetrier is an optional interface for error as to whether 192 // the operation should not be retried at a low level. 193 // 194 // NoLowLevelRetry errors won't be retried by low level retry loops. 195 type NoLowLevelRetrier interface { 196 error 197 NoLowLevelRetry() bool 198 } 199 200 // wrappedNoLowLevelRetryError is an error wrapped so it will satisfy the 201 // NoLowLevelRetrier interface and return true 202 type wrappedNoLowLevelRetryError struct { 203 error 204 } 205 206 // NoLowLevelRetry interface 207 func (err wrappedNoLowLevelRetryError) NoLowLevelRetry() bool { 208 return true 209 } 210 211 // Check interfaces 212 var _ NoLowLevelRetrier = wrappedNoLowLevelRetryError{error(nil)} 213 var _ unwrapper = wrappedNoLowLevelRetryError{} 214 215 // NoLowLevelRetryError makes an error which indicates the sync 216 // shouldn't be low level retried. 217 func NoLowLevelRetryError(err error) error { 218 return wrappedNoLowLevelRetryError{err} 219 } 220 221 // Unwrap returns the underlying error 222 func (err wrappedNoLowLevelRetryError) Unwrap() error { 223 return err.error 224 } 225 226 // IsNoLowLevelRetryError returns true if err conforms to the NoLowLevelRetry 227 // interface and calling the NoLowLevelRetry method returns true. 228 func IsNoLowLevelRetryError(err error) (isNoLowLevelRetry bool) { 229 liberrors.Walk(err, func(err error) bool { 230 if r, ok := err.(NoLowLevelRetrier); ok { 231 isNoLowLevelRetry = r.NoLowLevelRetry() 232 return true 233 } 234 return false 235 }) 236 return 237 } 238 239 // RetryAfter is an optional interface for error as to whether the 240 // operation should be retried after a given delay 241 // 242 // This should be returned from Update or Put methods as required and 243 // will cause the entire sync to be retried after a delay. 244 type RetryAfter interface { 245 error 246 RetryAfter() time.Time 247 } 248 249 // ErrorRetryAfter is an error which expresses a time that should be 250 // waited for until trying again 251 type ErrorRetryAfter time.Time 252 253 // NewErrorRetryAfter returns an ErrorRetryAfter with the given 254 // duration as an endpoint 255 func NewErrorRetryAfter(d time.Duration) ErrorRetryAfter { 256 return ErrorRetryAfter(time.Now().Add(d)) 257 } 258 259 // Error returns the textual version of the error 260 func (e ErrorRetryAfter) Error() string { 261 return fmt.Sprintf("try again after %v (%v)", time.Time(e).Format(time.RFC3339Nano), time.Until(time.Time(e))) 262 } 263 264 // RetryAfter returns the time the operation should be retried at or 265 // after 266 func (e ErrorRetryAfter) RetryAfter() time.Time { 267 return time.Time(e) 268 } 269 270 // Check interfaces 271 var _ RetryAfter = ErrorRetryAfter{} 272 273 // RetryAfterErrorTime returns the time that the RetryAfter error 274 // indicates or a Zero time.Time 275 func RetryAfterErrorTime(err error) (retryAfter time.Time) { 276 liberrors.Walk(err, func(err error) bool { 277 if r, ok := err.(RetryAfter); ok { 278 retryAfter = r.RetryAfter() 279 return true 280 } 281 return false 282 }) 283 return 284 } 285 286 // IsRetryAfterError returns true if err is an ErrorRetryAfter 287 func IsRetryAfterError(err error) bool { 288 return !RetryAfterErrorTime(err).IsZero() 289 } 290 291 // CountableError is an optional interface for error. It stores a boolean 292 // which signifies if the error has already been counted or not 293 type CountableError interface { 294 error 295 Count() 296 IsCounted() bool 297 } 298 299 // wrappedCountableError is an error wrapped so it will satisfy the 300 // Retrier interface and return true 301 type wrappedCountableError struct { 302 error 303 isCounted bool 304 } 305 306 // CountableError interface 307 func (err *wrappedCountableError) Count() { 308 err.isCounted = true 309 } 310 311 // CountableError interface 312 func (err *wrappedCountableError) IsCounted() bool { 313 return err.isCounted 314 } 315 316 func (err wrappedCountableError) Unwrap() error { 317 return err.error 318 } 319 320 // IsCounted returns true if err conforms to the CountableError interface 321 // and has already been counted 322 func IsCounted(err error) bool { 323 if r, ok := err.(CountableError); ok { 324 return r.IsCounted() 325 } 326 return false 327 } 328 329 // Count sets the isCounted variable on the error if it conforms to the 330 // CountableError interface 331 func Count(err error) { 332 if r, ok := err.(CountableError); ok { 333 r.Count() 334 } 335 } 336 337 // Check interface 338 var _ CountableError = &wrappedCountableError{error: error(nil)} 339 var _ unwrapper = wrappedCountableError{} 340 341 // FsError makes an error which can keep a record that it is already counted 342 // or not 343 func FsError(err error) error { 344 if err == nil { 345 err = errors.New("countable error") 346 } 347 return &wrappedCountableError{error: err} 348 } 349 350 // Cause is a souped up errors.Cause which can unwrap some standard 351 // library errors too. It returns true if any of the intermediate 352 // errors had a Timeout() or Temporary() method which returned true. 353 func Cause(cause error) (retriable bool, err error) { 354 liberrors.Walk(cause, func(c error) bool { 355 // Check for net error Timeout() 356 if x, ok := c.(interface { 357 Timeout() bool 358 }); ok && x.Timeout() { 359 retriable = true 360 } 361 362 // Check for net error Temporary() 363 if x, ok := c.(interface { 364 Temporary() bool 365 }); ok && x.Temporary() { 366 retriable = true 367 } 368 err = c 369 return false 370 }) 371 return 372 } 373 374 // retriableErrorStrings is a list of phrases which when we find it 375 // in an error, we know it is a networking error which should be 376 // retried. 377 // 378 // This is incredibly ugly - if only errors.Cause worked for all 379 // errors and all errors were exported from the stdlib. 380 var retriableErrorStrings = []string{ 381 "use of closed network connection", // internal/poll/fd.go 382 "unexpected EOF reading trailer", // net/http/transfer.go 383 "transport connection broken", // net/http/transport.go 384 "http: ContentLength=", // net/http/transfer.go 385 "server closed idle connection", // net/http/transport.go 386 "bad record MAC", // crypto/tls/alert.go 387 "stream error:", // net/http/h2_bundle.go 388 "tls: use of closed connection", // crypto/tls/conn.go 389 } 390 391 // Errors which indicate networking errors which should be retried 392 // 393 // These are added to in retriable_errors*.go 394 var retriableErrors = []error{ 395 io.EOF, 396 io.ErrUnexpectedEOF, 397 } 398 399 // ShouldRetry looks at an error and tries to work out if retrying the 400 // operation that caused it would be a good idea. It returns true if 401 // the error implements Timeout() or Temporary() or if the error 402 // indicates a premature closing of the connection. 403 func ShouldRetry(err error) bool { 404 if err == nil { 405 return false 406 } 407 408 // If error has been marked to NoLowLevelRetry then don't retry 409 if IsNoLowLevelRetryError(err) { 410 return false 411 } 412 413 // Find root cause if available 414 retriable, err := Cause(err) 415 if retriable { 416 return true 417 } 418 419 // Check if it is a retriable error 420 for _, retriableErr := range retriableErrors { 421 if err == retriableErr { 422 return true 423 } 424 } 425 426 // Check error strings (yuch!) too 427 errString := err.Error() 428 for _, phrase := range retriableErrorStrings { 429 if strings.Contains(errString, phrase) { 430 return true 431 } 432 } 433 434 return false 435 } 436 437 // ShouldRetryHTTP returns a boolean as to whether this resp deserves. 438 // It checks to see if the HTTP response code is in the slice 439 // retryErrorCodes. 440 func ShouldRetryHTTP(resp *http.Response, retryErrorCodes []int) bool { 441 if resp == nil { 442 return false 443 } 444 for _, e := range retryErrorCodes { 445 if resp.StatusCode == e { 446 return true 447 } 448 } 449 return false 450 } 451 452 // ContextError checks to see if ctx is in error. 453 // 454 // If it is in error then it overwrites *perr with the context error 455 // if *perr was nil and returns true. 456 // 457 // Otherwise it returns false. 458 func ContextError(ctx context.Context, perr *error) bool { 459 if ctxErr := ctx.Err(); ctxErr != nil { 460 if *perr == nil { 461 *perr = ctxErr 462 } 463 return true 464 } 465 return false 466 }