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  )