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:
parent
018fe80bcb
commit
28f4061892
4 changed files with 113 additions and 13 deletions
110
fs/error.go
110
fs/error.go
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -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++
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue