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.
This commit is contained in:
Nick Craig-Wood 2016-07-04 11:57:30 +01:00
parent 018fe80bcb
commit 28f4061892
4 changed files with 113 additions and 13 deletions

View file

@ -12,11 +12,11 @@ import (
"github.com/pkg/errors" "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. // operation should be retried at a high level.
// //
// This should be returned from Update or Put methods as required // This should be returned from Update or Put methods as required
type Retry interface { type Retrier interface {
error error
Retry() bool Retry() bool
} }
@ -35,40 +35,132 @@ func (r retryError) Retry() bool {
} }
// Check interface // Check interface
var _ Retry = retryError("") var _ Retrier = retryError("")
// RetryErrorf makes an error which indicates it would like to be retried // RetryErrorf makes an error which indicates it would like to be retried
func RetryErrorf(format string, a ...interface{}) error { func RetryErrorf(format string, a ...interface{}) error {
return retryError(fmt.Sprintf(format, a...)) return retryError(fmt.Sprintf(format, a...))
} }
// PlainRetryError is an error wrapped so it will retry // wrappedRetryError is an error wrapped so it will satisfy the
type plainRetryError struct { // Retrier interface and return true
type wrappedRetryError struct {
error error
} }
// Retry interface // Retry interface
func (err plainRetryError) Retry() bool { func (err wrappedRetryError) Retry() bool {
return true return true
} }
// Check interface // Check interface
var _ Retry = plainRetryError{(error)(nil)} var _ Retrier = wrappedRetryError{(error)(nil)}
// RetryError makes an error which indicates it would like to be retried // RetryError makes an error which indicates it would like to be retried
func RetryError(err error) error { func RetryError(err error) error {
return plainRetryError{err} return wrappedRetryError{err}
} }
// IsRetryError returns true if err conforms to the Retry interface // IsRetryError returns true if err conforms to the Retry interface
// and calling the Retry method returns true. // and calling the Retry method returns true.
func IsRetryError(err error) bool { 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 r.Retry()
} }
return false 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 // isClosedConnError reports whether err is an error from use of a closed
// network connection. // network connection.
// //

View file

@ -199,7 +199,7 @@ again:
obj, err := remote.Put(in, obji) obj, err := remote.Put(in, obji)
if err != nil { if err != nil {
// Retry if err returned a retry error // 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) t.Logf("Put error: %v - low level retry %d/%d", err, tries, maxTries)
tries++ tries++

View file

@ -361,7 +361,7 @@ func Test_callRetry(t *testing.T) {
if err == errFoo { if err == errFoo {
t.Errorf("err didn't want %v got %v", errFoo, err) t.Errorf("err didn't want %v got %v", errFoo, err)
} }
_, ok := err.(fs.Retry) _, ok := err.(fs.Retrier)
if !ok { if !ok {
t.Errorf("didn't return a retry error") t.Errorf("didn't return a retry error")
} }
@ -375,7 +375,7 @@ func TestCall(t *testing.T) {
if dp.called != 20 { if dp.called != 20 {
t.Errorf("called want %d got %d", 20, dp.called) t.Errorf("called want %d got %d", 20, dp.called)
} }
_, ok := err.(fs.Retry) _, ok := err.(fs.Retrier)
if !ok { if !ok {
t.Errorf("didn't return a retry error") t.Errorf("didn't return a retry error")
} }
@ -389,7 +389,7 @@ func TestCallNoRetry(t *testing.T) {
if dp.called != 1 { if dp.called != 1 {
t.Errorf("called want %d got %d", 1, dp.called) t.Errorf("called want %d got %d", 1, dp.called)
} }
_, ok := err.(fs.Retry) _, ok := err.(fs.Retrier)
if !ok { if !ok {
t.Errorf("didn't return a retry error") t.Errorf("didn't return a retry error")
} }

View file

@ -526,6 +526,14 @@ func main() {
if !command.Retry || (err == nil && !fs.Stats.Errored()) { if !command.Retry || (err == nil && !fs.Stats.Errored()) {
break 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 { if err != nil {
fs.Log(nil, "Attempt %d/%d failed with %d errors and: %v", try, *retries, fs.Stats.GetErrors(), err) fs.Log(nil, "Attempt %d/%d failed with %d errors and: %v", try, *retries, fs.Stats.GetErrors(), err)
} else { } else {