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  }