Skip to content

Commit

Permalink
refactoring of methods and structs
Browse files Browse the repository at this point in the history
  • Loading branch information
Francesco Cosentino committed Jan 19, 2023
1 parent 1342787 commit 60634d2
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 50 deletions.
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@

`go-again` **thread safely** wraps a given function and executes it until it returns a nil error or exceeds the maximum number of retries.
The configuration consists of the maximum number of retries, the interval, a jitter to add a randomized backoff, the timeout, and a registry to store errors that you consider temporary, hence worth a retry.
The `Retry` method takes a context, a function, and an optional list of `temporary errors` as arguments. It supports cancellation from the context and a channel invoking the `Cancel()` function.
The returned type is `RetryErrors` which contains the list of errors returned at each attempt and the last error returned by the function.
The `Do` method takes a context, a function, and an optional list of `temporary errors` as arguments. It supports cancellation from the context and a channel invoking the `Cancel()` function.
The returned type is `Errors` which contains the list of errors returned at each attempt and the last error returned by the function.

```golang
// RetryErrors holds the error returned by the retry function along with the trace of each attempt.
type RetryErrors struct {
// Errors holds the error returned by the retry function along with the trace of each attempt.
type Errors struct {
// Retries hold the trace of each attempt.
Retries map[int]error
// ExitError holds the last error returned by the retry function.
ExitError error
// Last holds the last error returned by the retry function.
Last error
}
```

Expand All @@ -29,34 +29,34 @@ The registry only allows you to retry a function if it returns a registered erro

defer retrier.Registry.UnRegisterTemporaryError("http.ErrAbortHandler")
var retryCount int
errs := retrier.Retry(context.TODO(), func() error {
errs := retrier.Do(context.TODO(), func() error {
retryCount++
if retryCount < 3 {
return http.ErrAbortHandler
}
return nil
}, "http.ErrAbortHandler")

if errs.ExitError != nil {
if errs.Last != nil {
// handle error
}
```

Should you retry regardless of the error returned, that's easy. It's enough calling the Retry function without passing a plausible set of registered error names:
Should you retry regardless of the error returned, that's easy. It's enough calling the Do function without passing a plausible set of registered error names:

```go
var retryCount int
retrier := again.NewRetrier(again.WithTimeout(1*time.Second),
again.WithJitter(500*time.Millisecond),
again.WithMaxRetries(3))
errs := retrier.Retry(context.TODO(), func() error {
errs := retrier.Do(context.TODO(), func() error {
retryCount++
if retryCount < 3 {
return http.ErrAbortHandler
}
return nil
})
if errs.ExitError != nil {
if errs.Last != nil {
// handle error
}
```
Expand Down Expand Up @@ -121,11 +121,11 @@ func main() {
})

// Retry a function.
errs := retrier.Retry(context.TODO(), func() error {
errs := retrier.Do(context.TODO(), func() error {
// Do something here.
return fmt.Errorf("temporary error")
}, "temporary error")
if errs.ExitError != nil {
if errs.Last != nil {
fmt.Println(err)
}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/chan/chan.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func main() {
}

// Retry the function.
errs := retrier.Retry(ctx, fn)
if errs.ExitError != nil {
errs := retrier.Do(ctx, fn)
if errs.Last != nil {
fmt.Println(errs)
} else {
fmt.Println("success")
Expand Down
4 changes: 2 additions & 2 deletions examples/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ func main() {
}

// Retry the function.
errs := retrier.Retry(ctx, fn)
errs := retrier.Do(ctx, fn)

if errs.ExitError != nil {
if errs.Last != nil {
fmt.Println(errs)
} else {
fmt.Println("success")
Expand Down
44 changes: 22 additions & 22 deletions retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
)

var (
// retryErrorsPool is a pool of RetryErrors objects.
retryErrorsPool = sync.Pool{
// errorsPool is a pool of Errors objects.
errorsPool = sync.Pool{
New: func() interface{} {
return &RetryErrors{
return &Errors{
Retries: make(map[int]error),
}
},
Expand All @@ -22,12 +22,12 @@ var (
// RetryableFunc signature of retryable function
type RetryableFunc func() error

// RetryErrors holds the error returned by the retry function along with the trace of each attempt.
type RetryErrors struct {
// Errors holds the error returned by the retry function along with the trace of each attempt.
type Errors struct {
// Retries holds the trace of each attempt.
Retries map[int]error
// ExitError holds the last error returned by the retry function.
ExitError error
// Last holds the last error returned by the retry function.
Last error
}

// Retrier is a type that retries a function until it returns a nil error or the maximum number of retries is reached.
Expand Down Expand Up @@ -103,31 +103,31 @@ func (r *Retrier) SetRegistry(reg *registry) error {
return nil
}

// Retry retries a `retryableFunc` until it returns a nil error or the maximum number of retries is reached.
// - If the maximum number of retries is reached, the function returns a `RetryError` object.
// - If the `retryableFunc` returns a nil error, the function assigns a `RetryErrors.ExitError` before returning.
// Do retries a `retryableFunc` until it returns a nil error or the maximum number of retries is reached.
// - If the maximum number of retries is reached, the function returns an `Errors` object.
// - If the `retryableFunc` returns a nil error, the function assigns an `Errors.Last` before returning.
// - If the `retryableFunc` returns a temporary error, the function retries the function.
// - If the `retryableFunc` returns a non-temporary error, the function assigns the error to `RetryErrors.ExitError` and returns.
// - If the `retryableFunc` returns a non-temporary error, the function assigns the error to `Errors.Last` and returns.
// - If the `temporaryErrors` list is empty, the function retries the function until the maximum number of retries is reached.
// - The context is used to cancel the retries, or set a deadline if the `retryableFunc` hangs.
func (r *Retrier) Retry(ctx context.Context, retryableFunc RetryableFunc, temporaryErrors ...string) (errs *RetryErrors) {
func (r *Retrier) Do(ctx context.Context, retryableFunc RetryableFunc, temporaryErrors ...string) (errs *Errors) {
// lock the mutex to synchronize access to the timer.
r.mutex.RLock()
defer r.mutex.RUnlock()

// get a new RetryErrors object from the pool.
errs = retryErrorsPool.Get().(*RetryErrors)
defer retryErrorsPool.Put(errs)
// get a new Errors object from the pool.
errs = errorsPool.Get().(*Errors)
defer errorsPool.Put(errs)

// If the maximum number of retries is 0, call the function once and return the result.
if r.MaxRetries == 0 {
errs.ExitError = retryableFunc()
errs.Last = retryableFunc()
return
}

// Check for invalid inputs.
if retryableFunc == nil {
errs.ExitError = errors.New("failed to invoke the function. It appears to be is nil")
errs.Last = errors.New("failed to invoke the function. It appears to be is nil")
return
}

Expand All @@ -143,7 +143,7 @@ func (r *Retrier) Retry(ctx context.Context, retryableFunc RetryableFunc, tempor

// If the function returns a nil error, return nil.
if r.err == nil {
errs.ExitError = nil
errs.Last = nil
return
}

Expand All @@ -158,7 +158,7 @@ func (r *Retrier) Retry(ctx context.Context, retryableFunc RetryableFunc, tempor
// Check if the context is cancelled.
select {
case <-ctx.Done():
errs.ExitError = ctx.Err()
errs.Last = ctx.Err()
return
default:
}
Expand All @@ -172,11 +172,11 @@ func (r *Retrier) Retry(ctx context.Context, retryableFunc RetryableFunc, tempor
select {
case <-r.cancel:
r.timer.put(timer)
errs.ExitError = errors.New("retries cancelled")
errs.Last = errors.New("retries cancelled")
return
case <-r.stop:
r.timer.put(timer)
errs.ExitError = errors.New("retries stopped")
errs.Last = errors.New("retries stopped")
return
case <-timer.C:
r.timer.put(timer)
Expand All @@ -186,7 +186,7 @@ func (r *Retrier) Retry(ctx context.Context, retryableFunc RetryableFunc, tempor
return
}

// Cancel cancels the retries notifying the `Retry` function to return.
// Cancel cancels the retries notifying the `Do` function to return.
func (r *Retrier) Cancel() {
r.once.Do(func() {
close(r.cancel)
Expand Down
22 changes: 11 additions & 11 deletions retry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ func TestRetry(t *testing.T) {

defer retrier.Registry.UnRegisterTemporaryError("http.ErrAbortHandler")

errs := retrier.Retry(context.TODO(), func() error {
errs := retrier.Do(context.TODO(), func() error {
retryCount++
if retryCount < 3 {
return http.ErrAbortHandler
}
return nil
}, "http.ErrAbortHandler")

if errs.ExitError != nil {
t.Errorf("retry returned an unexpected error: %v", errs.ExitError)
if errs.Last != nil {
t.Errorf("retry returned an unexpected error: %v", errs.Last)
}
if retryCount != 3 {
t.Errorf("retry did not retry the function the expected number of times. Got: %d, Expecting: %d", retryCount, 3)
Expand All @@ -38,16 +38,16 @@ func TestWithoutRegistry(t *testing.T) {
var retryCount int
retrier := NewRetrier()

errs := retrier.Retry(context.TODO(), func() error {
errs := retrier.Do(context.TODO(), func() error {
retryCount++
if retryCount < 3 {
return http.ErrAbortHandler
}
return nil
})

if errs.ExitError != nil {
t.Errorf("retry returned an unexpected error: %v", errs.ExitError)
if errs.Last != nil {
t.Errorf("retry returned an unexpected error: %v", errs.Last)
}
if retryCount != 3 {
t.Errorf("retry did not retry the function the expected number of times. Got: %d, Expecting: %d", retryCount, 1)
Expand All @@ -62,16 +62,16 @@ func TestRetryWithDefaults(t *testing.T) {

defer retrier.Registry.Clean()

errs := retrier.Retry(context.TODO(), func() error {
errs := retrier.Do(context.TODO(), func() error {
retryCount++
if retryCount < 3 {
return http.ErrHandlerTimeout
}
return nil
}, "http.ErrHandlerTimeout")

if errs.ExitError != nil {
t.Errorf("retry returned an unexpected error: %v", errs.ExitError)
if errs.Last != nil {
t.Errorf("retry returned an unexpected error: %v", errs.Last)
}
if retryCount != 3 {
t.Errorf("retry did not retry the function the expected number of times. Got: %d, Expecting: %d", retryCount, 3)
Expand Down Expand Up @@ -126,7 +126,7 @@ func TestRegistry(t *testing.T) {
// }
// return nil
// }
// err := r.Retry(fn, "temporary error").(*RetryError)
// err := r.Retry(fn, "temporary error").(*Errors)
// if err != nil || err.MaxRetries != 50 {
// b.Errorf("retry returned an unexpected error: %v", err)
// }
Expand All @@ -149,7 +149,7 @@ func BenchmarkRetry(b *testing.B) {
return nil
}
b.StartTimer()
r.Retry(context.TODO(), fn, "temporary error")
r.Do(context.TODO(), fn, "temporary error")
b.StopTimer()
}
}

0 comments on commit 60634d2

Please sign in to comment.