Documentation
¶
Overview ¶
Package goretry provides convenient functions for common retry scenarios. These functions offer a simple API similar to the original C# System.Retry library.
Package goretry provides retry mechanisms with various backoff policies and transient error strategies. This file contains implementations of different retry policies that determine the delay between retry attempts.
Example (Policies) ¶
Example showing different retry policies
package main
import (
"fmt"
"time"
goretry "github.com/kriscoleman/GoRetry"
)
func main() {
// Fixed delay - same delay between all retries
fixedPolicy := goretry.NewFixedDelayPolicy(500 * time.Millisecond)
// Exponential backoff with jitter
expPolicy := goretry.NewExponentialBackoffPolicy(100*time.Millisecond, 10*time.Second).
WithMultiplier(2.0).
WithJitter(true)
// Linear backoff
linearPolicy := goretry.NewLinearBackoffPolicy(100*time.Millisecond, 100*time.Millisecond, 1*time.Second)
// No delay - immediate retries
noDelayPolicy := goretry.NewNoDelayPolicy()
// Stop policy - wraps another policy with limits
stopPolicy := goretry.NewStopPolicy(expPolicy).
WithMaxAttempts(5).
WithMaxDuration(30 * time.Second)
policies := []goretry.RetryPolicy{
fixedPolicy,
expPolicy,
linearPolicy,
noDelayPolicy,
stopPolicy,
}
for i, policy := range policies {
retrier := goretry.NewRetrier(policy)
_ = retrier // Use the retrier for actual retry logic
fmt.Printf("Policy %d configured\n", i+1)
}
}
Output: Policy 1 configured Policy 2 configured Policy 3 configured Policy 4 configured Policy 5 configured
Index ¶
- func DefaultTransientErrorFunc(err error) bool
- func IfNeeded(fn func() error, options ...Option) error
- func IfNeededWithContext(ctx context.Context, fn func(context.Context) error, options ...Option) error
- func IfNeededWithPolicy(policy RetryPolicy, fn func() error, options ...Option) error
- func IfNeededWithPolicyAndContext(ctx context.Context, policy RetryPolicy, fn func(context.Context) error, ...) error
- type ExponentialBackoffPolicy
- type FixedDelayPolicy
- type LinearBackoffPolicy
- type NoDelayPolicy
- type Option
- type OutOfRetriesError
- type Retrier
- type RetryPolicy
- type StopPolicy
- type TransientErrorFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultTransientErrorFunc ¶
DefaultTransientErrorFunc provides a reasonable default implementation for transient error detection. It considers an error transient if it implements common Go network error interfaces:
- Timeout() bool - for timeout errors
- Temporary() bool - for temporary errors
Context cancellation and deadline exceeded errors are explicitly NOT considered transient, as they indicate intentional cancellation rather than temporary failures.
This function can be used as a starting point, but applications should consider implementing custom transient error logic based on their specific error types and requirements.
Example usage:
retrier := NewRetrier(policy, WithTransientErrorFunc(DefaultTransientErrorFunc))
func IfNeeded ¶
IfNeeded provides a convenient way to retry a function with sensible default settings. This function is designed to mirror the C# library's Retry.IfNeeded method, providing a simple API for the most common retry scenarios.
Default configuration:
- Exponential backoff starting at 100ms, max 5 seconds
- Maximum 3 attempts
- Uses DefaultTransientErrorFunc for error classification
The function will retry if the provided TransientErrorFunc (or default) returns true for the encountered error, up to the maximum number of attempts.
Example:
err := goretry.IfNeeded(func() error {
return apiCall()
})
// With custom options
err := goretry.IfNeeded(func() error {
return apiCall()
}, goretry.WithMaxAttempts(5), goretry.WithTransientErrorFunc(customFunc))
Example ¶
Example demonstrating basic retry functionality
package main
import (
"errors"
"fmt"
"log"
goretry "github.com/kriscoleman/GoRetry"
)
func main() {
callCount := 0
err := goretry.IfNeeded(func() error {
callCount++
if callCount < 3 {
return errors.New("transient network error")
}
return nil
}, goretry.WithTransientErrorFunc(func(err error) bool {
// Custom logic to determine if error is transient
return err.Error() == "transient network error"
}))
if err != nil {
log.Printf("Failed after retries: %v", err)
} else {
fmt.Printf("Success after %d attempts", callCount)
}
}
Output: Success after 3 attempts
func IfNeededWithContext ¶
func IfNeededWithContext(ctx context.Context, fn func(context.Context) error, options ...Option) error
IfNeededWithContext provides a convenient way to retry a function with context support and default settings. This function combines the simplicity of IfNeeded with Go's context package for cancellation and timeout handling. The context is checked before each retry attempt.
Example:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
err := goretry.IfNeededWithContext(ctx, func(ctx context.Context) error {
return apiCallWithContext(ctx)
})
func IfNeededWithPolicy ¶
func IfNeededWithPolicy(policy RetryPolicy, fn func() error, options ...Option) error
IfNeededWithPolicy provides a convenient way to retry with a specific retry policy. This function allows you to specify a custom retry policy while still using the convenient IfNeeded API.
Example:
policy := goretry.NewFixedDelayPolicy(500 * time.Millisecond)
err := goretry.IfNeededWithPolicy(policy, func() error {
return apiCall()
}, goretry.WithMaxAttempts(5))
func IfNeededWithPolicyAndContext ¶
func IfNeededWithPolicyAndContext(ctx context.Context, policy RetryPolicy, fn func(context.Context) error, options ...Option) error
IfNeededWithPolicyAndContext provides the most flexible convenient retry function, combining custom retry policy with context support. This gives you full control over retry behavior while maintaining the simple IfNeeded API.
Example:
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
policy := goretry.NewLinearBackoffPolicy(100*time.Millisecond, 100*time.Millisecond, 2*time.Second)
err := goretry.IfNeededWithPolicyAndContext(ctx, policy, func(ctx context.Context) error {
return apiCallWithContext(ctx)
}, goretry.WithMaxAttempts(10))
Types ¶
type ExponentialBackoffPolicy ¶
type ExponentialBackoffPolicy struct {
// BaseDelay is the initial delay for the first retry
BaseDelay time.Duration
// MaxDelay is the maximum delay that will be applied, providing an upper bound
MaxDelay time.Duration
// Multiplier is the factor by which the delay increases each attempt (default: 2.0)
Multiplier float64
// Jitter adds randomness to delays to prevent thundering herd (default: true)
Jitter bool
}
ExponentialBackoffPolicy implements exponential backoff with optional jitter. This policy increases the delay exponentially with each retry attempt, which helps reduce load on failing services and increases the chance of recovery. Jitter can be enabled to add randomness and prevent thundering herd problems.
The delay is calculated as: BaseDelay * (Multiplier ^ (attempt-1)) If jitter is enabled, the actual delay will be randomized between delay/2 and delay.
Example:
policy := NewExponentialBackoffPolicy(100*time.Millisecond, 30*time.Second). WithMultiplier(2.0). WithJitter(true)
func NewExponentialBackoffPolicy ¶
func NewExponentialBackoffPolicy(baseDelay, maxDelay time.Duration) *ExponentialBackoffPolicy
NewExponentialBackoffPolicy creates a new ExponentialBackoffPolicy with sensible defaults. The default multiplier is 2.0 and jitter is enabled by default.
Parameters:
- baseDelay: the initial delay for the first retry attempt
- maxDelay: the maximum delay that will be applied (prevents infinite growth)
Example:
policy := NewExponentialBackoffPolicy(100*time.Millisecond, 10*time.Second)
func (*ExponentialBackoffPolicy) NextDelay ¶
func (p *ExponentialBackoffPolicy) NextDelay(attempt int) (time.Duration, bool)
NextDelay calculates the delay for the next retry attempt using exponential backoff. The delay grows exponentially with each attempt, capped at MaxDelay. If jitter is enabled, the delay is randomized between delay/2 and delay using cryptographically secure randomness.
func (*ExponentialBackoffPolicy) WithJitter ¶
func (p *ExponentialBackoffPolicy) WithJitter(jitter bool) *ExponentialBackoffPolicy
WithJitter controls whether jitter (randomness) is applied to delays. Jitter is enabled by default and helps prevent thundering herd problems when multiple clients retry simultaneously. Returns the policy instance for method chaining.
Example:
policy := NewExponentialBackoffPolicy(100*time.Millisecond, 10*time.Second). WithJitter(false) // Disable jitter for predictable delays
func (*ExponentialBackoffPolicy) WithMultiplier ¶
func (p *ExponentialBackoffPolicy) WithMultiplier(multiplier float64) *ExponentialBackoffPolicy
WithMultiplier sets the multiplier used for exponential growth. The default multiplier is 2.0. Common values are between 1.5 and 3.0. Returns the policy instance for method chaining.
Example:
policy := NewExponentialBackoffPolicy(100*time.Millisecond, 10*time.Second). WithMultiplier(1.5) // Slower growth than default
type FixedDelayPolicy ¶
type FixedDelayPolicy struct {
// Delay is the fixed duration to wait between retry attempts
Delay time.Duration
}
FixedDelayPolicy implements a retry policy with a constant delay between attempts. This policy provides the simplest retry strategy where each retry waits the same amount of time. It's useful when you want predictable, consistent timing between retries.
Example:
policy := NewFixedDelayPolicy(500 * time.Millisecond) retrier := NewRetrier(policy)
func NewFixedDelayPolicy ¶
func NewFixedDelayPolicy(delay time.Duration) *FixedDelayPolicy
NewFixedDelayPolicy creates a new FixedDelayPolicy with the specified delay duration. The delay will be applied between each retry attempt.
Example:
policy := NewFixedDelayPolicy(1 * time.Second) // 1 second between each retry
type LinearBackoffPolicy ¶
type LinearBackoffPolicy struct {
BaseDelay time.Duration
MaxDelay time.Duration
Increment time.Duration
}
LinearBackoffPolicy implements linear backoff
func NewLinearBackoffPolicy ¶
func NewLinearBackoffPolicy(baseDelay, increment, maxDelay time.Duration) *LinearBackoffPolicy
type NoDelayPolicy ¶
type NoDelayPolicy struct{}
NoDelayPolicy implements immediate retry with no delay
func NewNoDelayPolicy ¶
func NewNoDelayPolicy() *NoDelayPolicy
type Option ¶
type Option func(*Retrier)
Option is a function that configures a Retrier instance. Options are applied during Retrier creation to customize its behavior.
func WithMaxAttempts ¶
WithMaxAttempts sets the maximum number of retry attempts. The total number of function calls will be maxAttempts (including the initial attempt).
Example:
retrier := NewRetrier(policy, WithMaxAttempts(5)) // Will try up to 5 times total
func WithOnRetry ¶
WithOnRetry sets a callback function that is called before each retry attempt. This can be useful for logging, metrics collection, or other side effects. The callback receives the attempt number (1-based) and the error that triggered the retry.
Note: The callback is not called before the initial attempt, only before retries.
Example:
retrier := NewRetrier(policy, WithOnRetry(func(attempt int, err error) {
log.Printf("Retry attempt %d due to error: %v", attempt, err)
}))
func WithTransientErrorFunc ¶
func WithTransientErrorFunc(fn TransientErrorFunc) Option
WithTransientErrorFunc sets a custom function to determine if an error is transient. This function is called for each error encountered to decide whether a retry should be attempted. If the function returns true, the error is considered transient and a retry will be attempted (subject to other constraints like maximum attempts).
Example:
retrier := NewRetrier(policy, WithTransientErrorFunc(func(err error) bool {
return strings.Contains(err.Error(), "temporary") ||
strings.Contains(err.Error(), "timeout")
}))
type OutOfRetriesError ¶
type OutOfRetriesError struct {
// Attempts is the total number of attempts made
Attempts int
// LastErr is the error from the final attempt
LastErr error
// AllErrs contains all errors encountered during retry attempts
AllErrs []error
}
OutOfRetriesError is returned when all retry attempts are exhausted without success. It contains information about all attempts made and provides access to both the last error encountered and all errors that occurred during the retry process.
This error type implements the error interface and can be unwrapped to access the last error.
Example:
var outOfRetriesErr *OutOfRetriesError
if errors.As(err, &outOfRetriesErr) {
fmt.Printf("Failed after %d attempts\n", outOfRetriesErr.Attempts)
fmt.Printf("Last error: %v\n", outOfRetriesErr.LastErr)
}
func (*OutOfRetriesError) Error ¶
func (e *OutOfRetriesError) Error() string
Error returns a string representation of the OutOfRetriesError.
func (*OutOfRetriesError) Unwrap ¶
func (e *OutOfRetriesError) Unwrap() error
Unwrap returns the last error, allowing for error unwrapping with errors.Is and errors.As.
type Retrier ¶
type Retrier struct {
// contains filtered or unexported fields
}
Retrier provides retry functionality with configurable policies and transient error strategies. It encapsulates the retry logic and can be configured with various options such as maximum attempts, custom transient error detection, and retry callbacks.
Retrier is safe for concurrent use by multiple goroutines.
Example:
retrier := NewRetrier(
NewExponentialBackoffPolicy(100*time.Millisecond, 5*time.Second),
WithMaxAttempts(5),
WithTransientErrorFunc(func(err error) bool {
return strings.Contains(err.Error(), "temporary")
}),
)
err := retrier.Do(func() error {
return riskyOperation()
})
Example (CustomTransientErrors) ¶
Example with custom transient error detection
package main
import (
"errors"
"fmt"
"log"
"time"
goretry "github.com/kriscoleman/GoRetry"
)
func main() {
// Define what errors should trigger a retry
isTransient := func(err error) bool {
if err == nil {
return false
}
// Retry on specific error messages
msg := err.Error()
return msg == "connection refused" ||
msg == "timeout" ||
msg == "temporary failure"
}
retrier := goretry.NewRetrier(
goretry.NewLinearBackoffPolicy(100*time.Millisecond, 50*time.Millisecond, 500*time.Millisecond),
goretry.WithTransientErrorFunc(isTransient),
goretry.WithMaxAttempts(3),
goretry.WithOnRetry(func(attempt int, err error) {
log.Printf("Retry attempt %d due to: %v", attempt, err)
}),
)
err := retrier.Do(func() error {
// Simulate different types of errors
return errors.New("connection refused")
})
if err != nil {
fmt.Printf("Final error: %v", err)
}
}
Example (ExponentialBackoff) ¶
Example with exponential backoff policy
package main
import (
"fmt"
"net/http"
"time"
goretry "github.com/kriscoleman/GoRetry"
)
func main() {
policy := goretry.NewExponentialBackoffPolicy(100*time.Millisecond, 2*time.Second)
retrier := goretry.NewRetrier(policy, goretry.WithMaxAttempts(5))
err := retrier.Do(func() error {
// Simulate a service call that might fail
resp, err := http.Get("https://httpbin.org/status/500")
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 500 {
return fmt.Errorf("server error: %d", resp.StatusCode)
}
return nil
})
if err != nil {
fmt.Printf("Request failed: %v", err)
}
}
Example (WithContext) ¶
Example with context cancellation
package main
import (
"context"
"errors"
"fmt"
"time"
goretry "github.com/kriscoleman/GoRetry"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
retrier := goretry.NewRetrier(goretry.NewFixedDelayPolicy(200 * time.Millisecond))
err := retrier.DoWithContext(ctx, func(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
// Simulate work that might fail
return errors.New("simulated failure")
}
})
if err != nil {
fmt.Printf("Operation failed or timed out: %v", err)
}
}
func NewRetrier ¶
func NewRetrier(policy RetryPolicy, options ...Option) *Retrier
NewRetrier creates a new Retrier with the specified retry policy and optional configuration. The retry policy determines the delay strategy between attempts. Additional options can be provided to customize behavior such as maximum attempts, transient error detection, and retry callbacks.
Default configuration:
- Maximum attempts: 3
- Transient error function: DefaultTransientErrorFunc
- No retry callback
Example:
retrier := NewRetrier( NewExponentialBackoffPolicy(100*time.Millisecond, 5*time.Second), WithMaxAttempts(5), WithTransientErrorFunc(customTransientFunc), )
func (*Retrier) Do ¶
Do executes the given function with retry logic. The function will be retried according to the configured policy and options until it succeeds, a non-transient error occurs, or the maximum attempts are reached.
The function should be idempotent as it may be called multiple times. If all retry attempts are exhausted, an OutOfRetriesError is returned.
Example:
err := retrier.Do(func() error {
resp, err := http.Get("https://api.example.com/data")
if err != nil {
return err
}
defer resp.Body.Close()
return processResponse(resp)
})
func (*Retrier) DoWithContext ¶
DoWithContext executes the given function with retry logic and context support. This method provides the same retry functionality as Do, but with context support for cancellation and timeout handling.
The context is checked before each retry attempt, and if cancelled or timed out, the context error is returned immediately without further retries.
Example:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
err := retrier.DoWithContext(ctx, func(ctx context.Context) error {
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return processResponse(resp)
})
type RetryPolicy ¶
type RetryPolicy interface {
// NextDelay calculates the delay before the next retry attempt.
// attempt is 1-based (first retry is attempt 1).
// Returns the delay duration and whether retrying should continue.
NextDelay(attempt int) (time.Duration, bool)
}
RetryPolicy defines the retry behavior and delay calculation strategy. Implementations determine how long to wait between retry attempts and whether to continue retrying.
The NextDelay method receives the current attempt number (1-based) and returns:
- delay: duration to wait before the next attempt
- shouldContinue: whether to proceed with another attempt
Common implementations include exponential backoff, fixed delays, and linear backoff.
type StopPolicy ¶
type StopPolicy struct {
// contains filtered or unexported fields
}
StopPolicy wraps another policy and stops retrying after a specified condition. Note: For duration-based stopping, timing is managed by the retrier, not the policy, to ensure thread safety and correct timing behavior.
func NewStopPolicy ¶
func NewStopPolicy(policy RetryPolicy) *StopPolicy
func (*StopPolicy) GetMaxDuration ¶
func (p *StopPolicy) GetMaxDuration() time.Duration
GetMaxDuration returns the maximum duration setting for use by the retrier
func (*StopPolicy) WithMaxAttempts ¶
func (p *StopPolicy) WithMaxAttempts(attempts int) *StopPolicy
func (*StopPolicy) WithMaxDuration ¶
func (p *StopPolicy) WithMaxDuration(duration time.Duration) *StopPolicy
type TransientErrorFunc ¶
TransientErrorFunc is a function that determines if an error is transient and should trigger a retry. It receives an error and returns true if the error is considered transient (temporary/recoverable), false if the error is persistent and retries should not be attempted.
Example:
func isNetworkError(err error) bool {
return strings.Contains(err.Error(), "connection refused") ||
strings.Contains(err.Error(), "timeout")
}