From 01635296a369b20545dc5854eac154c23c67bad0 Mon Sep 17 00:00:00 2001 From: ICHINOSE Shogo Date: Wed, 19 Jun 2024 00:36:10 +0900 Subject: [PATCH 1/5] introduce MarkTemporary --- retry.go | 24 ++++++++++++++++++++++++ retry_test.go | 23 ++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/retry.go b/retry.go index cea71da..ba47947 100644 --- a/retry.go +++ b/retry.go @@ -88,6 +88,9 @@ func (p *Policy) Do(ctx context.Context, f func() error) error { if err := retrier.err; err != nil { return err } + if err, ok := err.(*temporaryError); ok { + return err.error + } return err } @@ -118,6 +121,27 @@ func MarkPermanent(err error) error { return &permanentError{err} } +type temporaryError struct { + error +} + +// implements interface{ Temporary() bool } +// Inspecting errors https://dave.cheney.net/2014/12/24/inspecting-errors +func (e *temporaryError) Temporary() bool { + return true +} + +// Unwrap implements errors.Wrapper. +func (e *temporaryError) Unwrap() error { + return e.error +} + +// MarkTemporary marks err as a temporary error. +// It returns the error that implements interface{ Temporary() bool } and Temporary() returns true. +func MarkTemporary(err error) error { + return &temporaryError{err} +} + // Continue returns whether retrying should be continued. func (r *Retrier) Continue() bool { r.count++ diff --git a/retry_test.go b/retry_test.go index 7081484..a3c7d70 100644 --- a/retry_test.go +++ b/retry_test.go @@ -266,13 +266,34 @@ func TestDo_Success(t *testing.T) { func TestDo_MarkPermanent(t *testing.T) { permanentErr := errors.New("permanent error") - policy := &Policy{} + policy := &Policy{MaxCount: 10} + count := 0 err := policy.Do(context.Background(), func() error { + count++ return MarkPermanent(permanentErr) }) if err != permanentErr { t.Errorf("want error is %#v, got %#v", err, permanentErr) } + if count != 1 { + t.Errorf("want %d, got %d", 1, count) + } +} + +func TestDo_MarkTemporary(t *testing.T) { + temporaryErr := errors.New("temporary error") + policy := &Policy{MaxCount: 10} + count := 0 + err := policy.Do(context.Background(), func() error { + count++ + return MarkTemporary(temporaryErr) + }) + if err != temporaryErr { + t.Errorf("want error is %#v, got %#v", err, temporaryErr) + } + if count != 10 { + t.Errorf("want %d, got %d", 10, count) + } } type customError bool From a9c39c4b0e630a7f1035386c3cc01bf9330781fa Mon Sep 17 00:00:00 2001 From: ICHINOSE Shogo Date: Wed, 19 Jun 2024 00:49:54 +0900 Subject: [PATCH 2/5] Update retry.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- retry.go | 1 + 1 file changed, 1 insertion(+) diff --git a/retry.go b/retry.go index ba47947..4f20109 100644 --- a/retry.go +++ b/retry.go @@ -89,6 +89,7 @@ func (p *Policy) Do(ctx context.Context, f func() error) error { return err } if err, ok := err.(*temporaryError); ok { + // Unwrap the error if it's marked as temporary. return err.error } return err From 0618fb5f87e43196c3ec7f7f78fe23a91dcbf6f3 Mon Sep 17 00:00:00 2001 From: ICHINOSE Shogo Date: Wed, 19 Jun 2024 00:51:38 +0900 Subject: [PATCH 3/5] Update retry.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- retry.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/retry.go b/retry.go index 4f20109..0981147 100644 --- a/retry.go +++ b/retry.go @@ -137,8 +137,9 @@ func (e *temporaryError) Unwrap() error { return e.error } -// MarkTemporary marks err as a temporary error. -// It returns the error that implements interface{ Temporary() bool } and Temporary() returns true. +// MarkTemporary wraps an error as a temporary error, allowing retry mechanisms to handle it appropriately. +// This is especially useful in scenarios where errors may not require immediate termination of a process, +// but rather can be resolved through retrying operations. func MarkTemporary(err error) error { return &temporaryError{err} } From dcd4f978ba7c0fa58d8568138272cd861ee9fa82 Mon Sep 17 00:00:00 2001 From: ICHINOSE Shogo Date: Wed, 19 Jun 2024 00:52:29 +0900 Subject: [PATCH 4/5] Update retry_test.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- retry_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/retry_test.go b/retry_test.go index a3c7d70..5ff05a7 100644 --- a/retry_test.go +++ b/retry_test.go @@ -266,6 +266,7 @@ func TestDo_Success(t *testing.T) { func TestDo_MarkPermanent(t *testing.T) { permanentErr := errors.New("permanent error") + // TestDo_MarkPermanent checks that a permanent error stops retries after one occurrence as expected. policy := &Policy{MaxCount: 10} count := 0 err := policy.Do(context.Background(), func() error { From 566c8a51cfcbe02b0f386fd3b5d71c282829f8c1 Mon Sep 17 00:00:00 2001 From: ICHINOSE Shogo Date: Thu, 20 Jun 2024 11:25:34 +0900 Subject: [PATCH 5/5] introduce myError --- func.go | 11 +++++++++-- func_test.go | 23 ++++++++++++++++++++++- retry.go | 39 ++++++++++++++------------------------- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/func.go b/func.go index 9941ee3..774bd65 100644 --- a/func.go +++ b/func.go @@ -23,8 +23,11 @@ func DoValue[T any](ctx context.Context, policy *Policy, f func() (T, error)) (T return v, nil } - // short cut for calling isPermanent and Unwrap - if err, ok := err.(*permanentError); ok { + // short cut for calling Unwrap + if err, ok := err.(*myError); ok { + if err.temporary { + continue + } return zero, err.error } @@ -41,5 +44,9 @@ func DoValue[T any](ctx context.Context, policy *Policy, f func() (T, error)) (T if err := retrier.err; err != nil { return zero, err } + if err, ok := err.(*myError); ok { + // Unwrap the error if it's marked as temporary. + return zero, err.error + } return zero, err } diff --git a/func_test.go b/func_test.go index 7c98ddf..cc70370 100644 --- a/func_test.go +++ b/func_test.go @@ -30,11 +30,32 @@ func TestDoValue_Success(t *testing.T) { func TestDoValue_MarkPermanent(t *testing.T) { permanentErr := errors.New("permanent error") - policy := &Policy{} + policy := &Policy{MaxCount: 10} + count := 0 _, err := DoValue(context.Background(), policy, func() (int, error) { + count++ return 0, MarkPermanent(permanentErr) }) if err != permanentErr { t.Errorf("want error is %#v, got %#v", err, permanentErr) } + if count != 1 { + t.Errorf("want %d, got %d", 1, count) + } +} + +func TestDoValue_MarkTemporary(t *testing.T) { + temporaryErr := errors.New("temporary error") + policy := &Policy{MaxCount: 10} + count := 0 + _, err := DoValue(context.Background(), policy, func() (int, error) { + count++ + return 0, MarkTemporary(temporaryErr) + }) + if err != temporaryErr { + t.Errorf("want error is %#v, got %#v", err, temporaryErr) + } + if count != 10 { + t.Errorf("want %d, got %d", 10, count) + } } diff --git a/retry.go b/retry.go index 0981147..bd955fc 100644 --- a/retry.go +++ b/retry.go @@ -70,8 +70,11 @@ func (p *Policy) Do(ctx context.Context, f func() error) error { return nil } - // short cut for calling isPermanent and Unwrap - if err, ok := err.(*permanentError); ok { + // short cut for calling Unwrap + if err, ok := err.(*myError); ok { + if err.temporary { + continue + } return err.error } @@ -88,7 +91,7 @@ func (p *Policy) Do(ctx context.Context, f func() error) error { if err := retrier.err; err != nil { return err } - if err, ok := err.(*temporaryError); ok { + if err, ok := err.(*myError); ok { // Unwrap the error if it's marked as temporary. return err.error } @@ -99,49 +102,35 @@ type temporary interface { Temporary() bool } -var _ temporary = (*permanentError)(nil) +var _ temporary = (*myError)(nil) -type permanentError struct { +type myError struct { error + temporary bool } // implements interface{ Temporary() bool } // Inspecting errors https://dave.cheney.net/2014/12/24/inspecting-errors -func (e *permanentError) Temporary() bool { - return false +func (e *myError) Temporary() bool { + return e.temporary } // Unwrap implements errors.Wrapper. -func (e *permanentError) Unwrap() error { +func (e *myError) Unwrap() error { return e.error } // MarkPermanent marks err as a permanent error. // It returns the error that implements interface{ Temporary() bool } and Temporary() returns false. func MarkPermanent(err error) error { - return &permanentError{err} -} - -type temporaryError struct { - error -} - -// implements interface{ Temporary() bool } -// Inspecting errors https://dave.cheney.net/2014/12/24/inspecting-errors -func (e *temporaryError) Temporary() bool { - return true -} - -// Unwrap implements errors.Wrapper. -func (e *temporaryError) Unwrap() error { - return e.error + return &myError{err, false} } // MarkTemporary wraps an error as a temporary error, allowing retry mechanisms to handle it appropriately. // This is especially useful in scenarios where errors may not require immediate termination of a process, // but rather can be resolved through retrying operations. func MarkTemporary(err error) error { - return &temporaryError{err} + return &myError{err, true} } // Continue returns whether retrying should be continued.