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  )