From 28f40618923579674a1a71ddfe76901552f8c5b6 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Mon, 4 Jul 2016 11:57:30 +0100 Subject: [PATCH] Add two more classes of error Fatal and NoRetry These are for remotes to signal that they have a fatal error and don't want to continue (eg cap exceeded) or that a particular file shouldn't be retried for some reason. --- fs/error.go | 110 ++++++++++++++++++++++++++++++++++---- fstest/fstests/fstests.go | 2 +- pacer/pacer_test.go | 6 +-- rclone.go | 8 +++ 4 files changed, 113 insertions(+), 13 deletions(-) diff --git a/fs/error.go b/fs/error.go index 6567aae9e..5b91f9a25 100644 --- a/fs/error.go +++ b/fs/error.go @@ -12,11 +12,11 @@ import ( "github.com/pkg/errors" ) -// Retry is an optional interface for error as to whether the +// Retrier is an optional interface for error as to whether the // operation should be retried at a high level. // // This should be returned from Update or Put methods as required -type Retry interface { +type Retrier interface { error Retry() bool } @@ -35,40 +35,132 @@ func (r retryError) Retry() bool { } // Check interface -var _ Retry = retryError("") +var _ Retrier = retryError("") // RetryErrorf makes an error which indicates it would like to be retried func RetryErrorf(format string, a ...interface{}) error { return retryError(fmt.Sprintf(format, a...)) } -// PlainRetryError is an error wrapped so it will retry -type plainRetryError struct { +// wrappedRetryError is an error wrapped so it will satisfy the +// Retrier interface and return true +type wrappedRetryError struct { error } // Retry interface -func (err plainRetryError) Retry() bool { +func (err wrappedRetryError) Retry() bool { return true } // Check interface -var _ Retry = plainRetryError{(error)(nil)} +var _ Retrier = wrappedRetryError{(error)(nil)} // RetryError makes an error which indicates it would like to be retried func RetryError(err error) error { - return plainRetryError{err} + return wrappedRetryError{err} } // IsRetryError returns true if err conforms to the Retry interface // and calling the Retry method returns true. func IsRetryError(err error) bool { - if r, ok := err.(Retry); ok { + if err == nil { + return false + } + err = errors.Cause(err) + if r, ok := err.(Retrier); ok { return r.Retry() } return false } +// Fataler is an optional interface for error as to whether the +// operation should cause the entire operation to finish immediately. +// +// This should be returned from Update or Put methods as required +type Fataler interface { + error + Fatal() bool +} + +// wrappedFatalError is an error wrapped so it will satisfy the +// Retrier interface and return true +type wrappedFatalError struct { + error +} + +// Fatal interface +func (err wrappedFatalError) Fatal() bool { + return true +} + +// Check interface +var _ Fataler = wrappedFatalError{(error)(nil)} + +// FatalError makes an error which indicates it is a fatal error and +// the sync should stop. +func FatalError(err error) error { + return wrappedFatalError{err} +} + +// IsFatalError returns true if err conforms to the Fatal interface +// and calling the Fatal method returns true. +func IsFatalError(err error) bool { + if err == nil { + return false + } + err = errors.Cause(err) + if r, ok := err.(Fataler); ok { + return r.Fatal() + } + return false +} + +// NoRetrier is an optional interface for error as to whether the +// operation should not be retried at a high level. +// +// If only NoRetry errors are returned in a sync then the sync won't +// be retried. +// +// This should be returned from Update or Put methods as required +type NoRetrier interface { + error + NoRetry() bool +} + +// wrappedNoRetryError is an error wrapped so it will satisfy the +// Retrier interface and return true +type wrappedNoRetryError struct { + error +} + +// NoRetry interface +func (err wrappedNoRetryError) NoRetry() bool { + return true +} + +// Check interface +var _ NoRetrier = wrappedNoRetryError{(error)(nil)} + +// NoRetryError makes an error which indicates the sync shouldn't be +// retried. +func NoRetryError(err error) error { + return wrappedNoRetryError{err} +} + +// IsNoRetryError returns true if err conforms to the NoRetry +// interface and calling the NoRetry method returns true. +func IsNoRetryError(err error) bool { + if err == nil { + return false + } + err = errors.Cause(err) + if r, ok := err.(NoRetrier); ok { + return r.NoRetry() + } + return false +} + // isClosedConnError reports whether err is an error from use of a closed // network connection. // diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go index 47e90e5b0..8fe9b2589 100644 --- a/fstest/fstests/fstests.go +++ b/fstest/fstests/fstests.go @@ -199,7 +199,7 @@ again: obj, err := remote.Put(in, obji) if err != nil { // Retry if err returned a retry error - if r, ok := err.(fs.Retry); ok && r.Retry() && tries < maxTries { + if fs.IsRetryError(err) && tries < maxTries { t.Logf("Put error: %v - low level retry %d/%d", err, tries, maxTries) tries++ diff --git a/pacer/pacer_test.go b/pacer/pacer_test.go index 435761166..0b0a65182 100644 --- a/pacer/pacer_test.go +++ b/pacer/pacer_test.go @@ -361,7 +361,7 @@ func Test_callRetry(t *testing.T) { if err == errFoo { t.Errorf("err didn't want %v got %v", errFoo, err) } - _, ok := err.(fs.Retry) + _, ok := err.(fs.Retrier) if !ok { t.Errorf("didn't return a retry error") } @@ -375,7 +375,7 @@ func TestCall(t *testing.T) { if dp.called != 20 { t.Errorf("called want %d got %d", 20, dp.called) } - _, ok := err.(fs.Retry) + _, ok := err.(fs.Retrier) if !ok { t.Errorf("didn't return a retry error") } @@ -389,7 +389,7 @@ func TestCallNoRetry(t *testing.T) { if dp.called != 1 { t.Errorf("called want %d got %d", 1, dp.called) } - _, ok := err.(fs.Retry) + _, ok := err.(fs.Retrier) if !ok { t.Errorf("didn't return a retry error") } diff --git a/rclone.go b/rclone.go index f976be9ce..c9f9d9c57 100644 --- a/rclone.go +++ b/rclone.go @@ -526,6 +526,14 @@ func main() { if !command.Retry || (err == nil && !fs.Stats.Errored()) { break } + if fs.IsFatalError(err) { + fs.Log(nil, "Fatal error received - not attempting retries") + break + } + if fs.IsNoRetryError(err) { + fs.Log(nil, "Can't retry this error - not attempting retries") + break + } if err != nil { fs.Log(nil, "Attempt %d/%d failed with %d errors and: %v", try, *retries, fs.Stats.GetErrors(), err) } else {