Skip to content

Commit

Permalink
code improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Ranielly Ferreira committed Jul 27, 2024
1 parent b31100f commit 080aaf2
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 46 deletions.
64 changes: 25 additions & 39 deletions retryable.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,39 +26,29 @@ func SetLoggerWriter(writer func(string, ...interface{})) {
// MustRetry executes a function until it succeeds or the maximum number of attempts is reached.
// It uses the global variables DefaultMaxAttempts and DefaultDelay for the retry configuration.
func MustRetry[T any](fn func() (T, error)) (T, error) {
return Retry(fn, DefaultMaxAttempts, DefaultDelay)
}

// Retry attempts to execute the provided function up to a maximum number of times, pausing with a delay between each try, regardless of the error type.
// It's a relentless retry strategy that stops only when a success is achieved or the maxAttempts are exhausted.
func Retry[T any](fn func() (T, error), maxAttempts int, delay time.Duration) (T, error) {
var result T
var err error
for attempt := 1; attempt <= DefaultMaxAttempts; attempt++ {
for attempt := 1; attempt <= maxAttempts; attempt++ {
result, err = fn()
if err == nil {
return result, nil
}
logPrintf("Attempt %d/%d failed: %v. Retrying in %v...", attempt, DefaultMaxAttempts, err, DefaultDelay)
time.Sleep(DefaultDelay)
logPrintf("Attempt %d/%d failed: %v. Retrying in %v...", attempt, maxAttempts, err, delay)
time.Sleep(delay)
}
return result, err // Return the last error encountered
}

// MustRetryWithCustomCheck executes a function until it succeeds, the maximum number of attempts is reached,
// or the provided custom check function returns false indicating that the error is not retryable.
func MustRetryWithCustomCheck[T any](fn func() (T, error), isRetryable func(error) bool) (T, error) {
var result T
var err error
for attempt := 1; attempt <= DefaultMaxAttempts; attempt++ {
result, err = fn()
if err == nil {
return result, nil
}

// Use the provided function to decide if we should retry.
if !isRetryable(err) {
return result, err // Do not retry if the error is not retryable.
}

logPrintf("Attempt %d/%d failed with an error: %v. Retrying in %v...", attempt, DefaultMaxAttempts, err, DefaultDelay)
time.Sleep(DefaultDelay)
}
return result, err // Return the last error encountered.
return RetryWithCustomCheck(fn, DefaultMaxAttempts, DefaultDelay, isRetryable)
}

// RetryWithCustomCheck provides a flexible retry mechanism, allowing custom logic to determine retryable errors.
Expand All @@ -83,6 +73,12 @@ func RetryWithCustomCheck[T any](fn func() (T, error), maxAttempts int, delay ti
return result, err // Last error encountered.
}

// MustRetryWithNonRetryableErrors attempts to execute the provided function until it succeeds,
// the maximum number of attempts is reached, or a non-retryable error is encountered.
func MustRetryWithNonRetryableErrors[T any](fn func() (T, error), nonRetryableErrors []string) (T, error) {
return RetryWithNonRetryableErrors(fn, DefaultMaxAttempts, DefaultDelay, nonRetryableErrors)
}

// RetryWithNonRetryableErrors gracefully handles retry logic for functions that may fail with retryable errors.
// It supports custom delays and distinguishes between errors that should halt retries.
func RetryWithNonRetryableErrors[T any](fn func() (T, error), maxAttempts int, delay time.Duration, nonRetryableErrors []string) (T, error) {
Expand All @@ -105,6 +101,12 @@ func RetryWithNonRetryableErrors[T any](fn func() (T, error), maxAttempts int, d
return result, err // Last error encountered.
}

// MustRetryWithRetryableErrors attempts to execute the provided function until it succeeds,
// the maximum number of attempts is reached, or a non-retryable error is encountered.
func MustRetryWithRetryableErrors[T any](fn func() (T, error), retryableErrors []string) (T, error) {
return RetryWithRetryableErrors(fn, DefaultMaxAttempts, DefaultDelay, retryableErrors)
}

// RetryWithRetryableErrors executes a function until it succeeds, the maximum number of attempts is reached,
// or a non-retryable error is encountered.
func RetryWithRetryableErrors[T any](fn func() (T, error), maxAttempts int, delay time.Duration, retryableErrors []string) (T, error) {
Expand All @@ -127,27 +129,11 @@ func RetryWithRetryableErrors[T any](fn func() (T, error), maxAttempts int, dela
return result, err // Return the last error encountered.
}

// RetryAlways attempts to execute the provided function up to a maximum number of times, pausing with a delay between each try, regardless of the error type.
// It's a relentless retry strategy that stops only when a success is achieved or the maxAttempts are exhausted.
func RetryAlways[T any](fn func() (T, error), maxAttempts int, delay time.Duration) (T, error) {
var result T
var err error
for attempt := 1; attempt <= maxAttempts; attempt++ {
result, err = fn()
if err == nil {
return result, nil
}
logPrintf("Attempt %d/%d failed: %v. Retrying in %v...", attempt, maxAttempts, err, delay)
time.Sleep(delay)
}
return result, err // Return the last error encountered
}

// ContainsError checks if the error message contains any of the substrings
// in the list of errors allowed for retrying.
func ContainsError(err error, retryableErrors []string) bool {
for _, retryableError := range retryableErrors {
if strings.Contains(err.Error(), retryableError) {
func ContainsError(err error, listErrors []string) bool {
for _, strErr := range listErrors {
if strings.Contains(err.Error(), strErr) {
return true
}
}
Expand Down
14 changes: 7 additions & 7 deletions retryable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestRetryWithNonRetryableErrors(t *testing.T) {
return false, errors.New("fatal error")
}

result, err := retryable.RetryWithNonRetryableErrors(fn, 3, 1*time.Millisecond, nonRetryableErrors)
result, err := retryable.MustRetryWithNonRetryableErrors(fn, nonRetryableErrors)
if err == nil || err.Error() != "fatal error" {
t.Errorf("Expected non-retryable error, got %v", err)
}
Expand All @@ -88,13 +88,13 @@ func TestRetryWithRetryableErrors(t *testing.T) {
return true, nil
}

result, err := retryable.RetryWithRetryableErrors(fn, 3, 1*time.Millisecond, retryableErrors)
result, err := retryable.MustRetryWithRetryableErrors(fn, retryableErrors)
if err != nil || !result {
t.Errorf("Expected successful retry on retryable error, got %v with error %v", result, err)
}
}

func TestRetryAlways(t *testing.T) {
func TestRetry(t *testing.T) {
var attempt int
fn := func() (int, error) {
attempt++
Expand All @@ -105,7 +105,7 @@ func TestRetryAlways(t *testing.T) {
}

expected := 4
result, err := retryable.RetryAlways(fn, 5, 1*time.Millisecond)
result, err := retryable.Retry(fn, 5, 1*time.Millisecond)
if err != nil || result != expected {
t.Errorf("Expected result %d after retries, got %d with error %v", expected, result, err)
}
Expand Down Expand Up @@ -156,13 +156,13 @@ func TestRetryWithCustomCheckNonRetryableError(t *testing.T) {
}
}

// TestRetryAlwaysFailure tests the RetryAlways function where it always fails.
func TestRetryAlwaysFailure(t *testing.T) {
// TestRetryFailure tests the Retry function where it always fails.
func TestRetryFailure(t *testing.T) {
fn := func() (bool, error) {
return false, errors.New("error")
}

_, err := retryable.RetryAlways(fn, 3, 1*time.Millisecond)
_, err := retryable.Retry(fn, 3, 1*time.Millisecond)
if err == nil {
t.Errorf("Expected an error after maximum attempts, got nil")
}
Expand Down

0 comments on commit 080aaf2

Please sign in to comment.