retry

package
v1.0.63 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 24, 2025 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package retry provides retry policies with configurable backoff strategies for resilient operations.

This package offers a comprehensive retry mechanism with multiple backoff strategies, error classification, and flexible policy configuration. It is designed for handling transient failures in network operations, API calls, and other unreliable operations.

Basic Usage

Use the default executor for simple retry scenarios:

executor := retry.NewDefaultRetryExecutor()
err := executor.Execute(ctx, func() error {
    return apiCall()
})

Custom Policies

Create custom retry policies for specific requirements:

policy := retry.DefaultRetryPolicy().
    WithMaxRetries(5).
    WithInitialDelay(100 * time.Millisecond)
strategy := retry.NewExponentialBackoffStrategy(policy)
executor := retry.NewRetryExecutor(policy, strategy)

Backoff Strategies

Multiple backoff strategies are available:

  • ExponentialBackoffStrategy: Exponential delay increase with jitter
  • ConstantBackoffStrategy: Fixed delay between retries
  • LinearBackoffStrategy: Linear delay increase

Error Classification

Errors can be marked as retryable or non-retryable:

err := retry.MarkRetryable(errors.New("temporary failure"), 503)
if retry.IsRetryableError(err) {
    // Handle retryable error
}
Example (BackoffStrategies)

Example_backoffStrategies demonstrates different backoff strategies

package main

import (
	"fmt"
	"time"

	"github.com/cecil-the-coder/ai-provider-kit/pkg/retry"
)

func main() {
	policy := retry.DefaultRetryPolicy().
		WithJitter(0.0) // No jitter for predictable output

	// Exponential backoff (without jitter for predictable output)
	expStrategy := retry.NewExponentialBackoffStrategy(policy).
		WithJitterType(retry.NoJitter)
	fmt.Printf("Exponential delay (attempt 0): %v\n", expStrategy.NextDelay(0, nil))
	fmt.Printf("Exponential delay (attempt 1): %v\n", expStrategy.NextDelay(1, nil))

	// Constant backoff
	constStrategy := retry.NewConstantBackoffStrategy(2 * time.Second)
	fmt.Printf("Constant delay (attempt 0): %v\n", constStrategy.NextDelay(0, nil))
	fmt.Printf("Constant delay (attempt 1): %v\n", constStrategy.NextDelay(1, nil))

	// Linear backoff
	linearStrategy := retry.NewLinearBackoffStrategy(1*time.Second, 1*time.Second, 10*time.Second)
	fmt.Printf("Linear delay (attempt 0): %v\n", linearStrategy.NextDelay(0, nil))
	fmt.Printf("Linear delay (attempt 1): %v\n", linearStrategy.NextDelay(1, nil))
}
Output:
Exponential delay (attempt 0): 1s
Exponential delay (attempt 1): 2s
Constant delay (attempt 0): 2s
Constant delay (attempt 1): 2s
Linear delay (attempt 0): 1s
Linear delay (attempt 1): 2s
Example (Basic)

Example_basic demonstrates basic retry usage

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/cecil-the-coder/ai-provider-kit/pkg/retry"
)

func main() {
	ctx := context.Background()
	executor := retry.NewDefaultRetryExecutor()

	// Simulate an operation that fails twice then succeeds
	attempt := 0
	err := executor.Execute(ctx, func() error {
		attempt++
		if attempt < 3 {
			return retry.MarkRetryable(errors.New("temporary error"), 503)
		}
		return nil
	})

	if err != nil {
		fmt.Printf("Failed: %v\n", err)
	} else {
		fmt.Printf("Succeeded after %d attempts\n", attempt)
	}
}
Output:
Succeeded after 3 attempts
Example (Callback)

Example_callback demonstrates using retry callbacks for monitoring

package main

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/cecil-the-coder/ai-provider-kit/pkg/retry"
)

func main() {
	ctx := context.Background()
	executor := retry.NewDefaultRetryExecutor()

	attempt := 0
	err := executor.ExecuteWithCallback(
		ctx,
		func() error {
			attempt++
			if attempt < 3 {
				return retry.MarkRetryable(errors.New("temporary error"), 503)
			}
			return nil
		},
		func(attemptNum int, err error, delay time.Duration) {
			// This callback is called before each retry
			// In a real application, you might log this or send metrics
			_ = attemptNum
			_ = err
			_ = delay
		},
	)

	if err != nil {
		fmt.Printf("Failed: %v\n", err)
	} else {
		fmt.Println("Success")
	}
}
Output:
Success
Example (CustomPolicy)

Example_customPolicy demonstrates using a custom retry policy

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/cecil-the-coder/ai-provider-kit/pkg/retry"
)

func main() {
	ctx := context.Background()

	// Create a custom policy with aggressive retries
	policy := retry.DefaultRetryPolicy().
		WithMaxRetries(5).
		WithInitialDelay(100 * time.Millisecond)

	strategy := retry.NewExponentialBackoffStrategy(policy).
		WithJitterType(retry.FullJitter)

	executor := retry.NewRetryExecutor(policy, strategy)

	// Execute with custom policy
	err := executor.Execute(ctx, func() error {
		// Your operation here
		return nil
	})

	if err != nil {
		fmt.Printf("Failed: %v\n", err)
	} else {
		fmt.Println("Success")
	}
}
Output:
Success
Example (ErrorClassification)

Example_errorClassification demonstrates error classification

package main

import (
	"errors"
	"fmt"

	"github.com/cecil-the-coder/ai-provider-kit/pkg/retry"
)

func main() {
	// Check if status codes are retryable
	fmt.Printf("429 retryable: %v\n", retry.IsRetryableStatusCode(429))
	fmt.Printf("500 retryable: %v\n", retry.IsRetryableStatusCode(500))
	fmt.Printf("400 retryable: %v\n", retry.IsRetryableStatusCode(400))

	// Create and check retryable errors
	err := retry.MarkRetryable(errors.New("temporary failure"), 503)
	fmt.Printf("Error retryable: %v\n", retry.IsRetryableError(err))

}
Output:
429 retryable: true
500 retryable: true
400 retryable: false
Error retryable: true
Example (ExecutorWithCustomPolicy)

Example_executorWithCustomPolicy demonstrates modifying executor behavior

package main

import (
	"context"
	"fmt"

	"github.com/cecil-the-coder/ai-provider-kit/pkg/retry"
)

func main() {
	ctx := context.Background()
	executor := retry.NewDefaultRetryExecutor()

	// Use a conservative policy for this specific operation
	conservativeExecutor := executor.WithPolicy(retry.ConservativeRetryPolicy())

	err := conservativeExecutor.Execute(ctx, func() error {
		// Operation with conservative retry behavior
		return nil
	})

	if err != nil {
		fmt.Printf("Failed: %v\n", err)
	} else {
		fmt.Println("Success with conservative retry")
	}
}
Output:
Success with conservative retry
Example (ExecutorWithCustomStrategy)

Example_executorWithCustomStrategy demonstrates modifying backoff strategy

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/cecil-the-coder/ai-provider-kit/pkg/retry"
)

func main() {
	ctx := context.Background()
	executor := retry.NewDefaultRetryExecutor()

	// Use constant backoff instead of exponential
	constantExecutor := executor.WithStrategy(
		retry.NewConstantBackoffStrategy(1 * time.Second),
	)

	err := constantExecutor.Execute(ctx, func() error {
		// Operation with constant backoff
		return nil
	})

	if err != nil {
		fmt.Printf("Failed: %v\n", err)
	} else {
		fmt.Println("Success with constant backoff")
	}
}
Output:
Success with constant backoff
Example (PresetPolicies)

Example_presetPolicies demonstrates using preset retry policies

package main

import (
	"fmt"

	"github.com/cecil-the-coder/ai-provider-kit/pkg/retry"
)

func main() {
	// Default policy (balanced retries)
	defaultPolicy := retry.DefaultRetryPolicy()
	fmt.Printf("Default MaxRetries: %d\n", defaultPolicy.MaxRetries)
	fmt.Printf("Default InitialDelay: %v\n", defaultPolicy.InitialDelay)

	// No retry policy (disable retries)
	noRetryPolicy := retry.NoRetryPolicy()
	fmt.Printf("NoRetry MaxRetries: %d\n", noRetryPolicy.MaxRetries)

	// Aggressive policy (more retries, shorter delays)
	aggressivePolicy := retry.AggressiveRetryPolicy()
	fmt.Printf("Aggressive MaxRetries: %d\n", aggressivePolicy.MaxRetries)
	fmt.Printf("Aggressive InitialDelay: %v\n", aggressivePolicy.InitialDelay)

	// Conservative policy (fewer retries, longer delays)
	conservativePolicy := retry.ConservativeRetryPolicy()
	fmt.Printf("Conservative MaxRetries: %d\n", conservativePolicy.MaxRetries)
	fmt.Printf("Conservative InitialDelay: %v\n", conservativePolicy.InitialDelay)

}
Output:
Default MaxRetries: 3
Default InitialDelay: 1s
NoRetry MaxRetries: 0
Aggressive MaxRetries: 5
Aggressive InitialDelay: 500ms
Conservative MaxRetries: 2
Conservative InitialDelay: 2s
Example (Typed)

Example_typed demonstrates using typed retry operations

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/cecil-the-coder/ai-provider-kit/pkg/retry"
)

func main() {
	ctx := context.Background()
	executor := retry.NewDefaultRetryExecutor()

	// Execute a typed operation
	attempt := 0
	result, err := retry.ExecuteTyped(ctx, executor, func() (string, error) {
		attempt++
		if attempt < 2 {
			return "", retry.MarkRetryable(errors.New("temporary error"), 503)
		}
		return "success", nil
	})

	if err != nil {
		fmt.Printf("Failed: %v\n", err)
	} else {
		fmt.Printf("Result: %s\n", result)
	}
}
Output:
Result: success

Index

Examples

Constants

View Source
const (
	// NoJitter applies no randomization to the delay.
	NoJitter = retry.NoJitter

	// FullJitter randomizes the delay between 0 and the calculated delay.
	FullJitter = retry.FullJitter

	// EqualJitter splits the delay evenly between fixed and random components.
	EqualJitter = retry.EqualJitter

	// DecorrelatedJitter uses AWS's decorrelated jitter algorithm.
	DecorrelatedJitter = retry.DecorrelatedJitter
)

Jitter type constants.

View Source
const (
	StatusTooManyRequests     = retry.StatusTooManyRequests     // 429
	StatusInternalServerError = retry.StatusInternalServerError // 500
	StatusBadGateway          = retry.StatusBadGateway          // 502
	StatusServiceUnavailable  = retry.StatusServiceUnavailable  // 503
	StatusGatewayTimeout      = retry.StatusGatewayTimeout      // 504
	StatusInsufficientStorage = retry.StatusInsufficientStorage // 507
	StatusNetworkAuthRequired = retry.StatusNetworkAuthRequired // 511
)

Common HTTP status codes that may trigger retries.

Variables

This section is empty.

Functions

func ExecuteTyped

func ExecuteTyped[T any](ctx context.Context, executor *RetryExecutor, operation ExecuteFunc[T]) (T, error)

ExecuteTyped executes a typed function with retry logic. It wraps the generic ExecuteWithResult to provide type-safe returns.

Example:

result, err := retry.ExecuteTyped(ctx, executor, func() (string, error) {
    return api.Call()
})

func GetStatusCode

func GetStatusCode(err error) int

GetStatusCode extracts the HTTP status code from an error if available. Returns 0 if no status code is associated with the error.

func IsRetryableError

func IsRetryableError(err error) bool

IsRetryableError checks if an error is retryable. It examines the error chain for RetryableError implementations.

Example:

if retry.IsRetryableError(err) {
    // Handle retryable error
}

func IsRetryableStatusCode

func IsRetryableStatusCode(statusCode int) bool

IsRetryableStatusCode checks if an HTTP status code is retryable. Returns true for common transient status codes (429, 500, 502, 503, 504, 507, 511).

Example:

if retry.IsRetryableStatusCode(resp.StatusCode) {
    // Trigger retry
}

func MarkNonRetryable

func MarkNonRetryable(err error, statusCode int) error

MarkNonRetryable wraps an error to mark it as non-retryable with the given status code. This is a convenience function for NewRetryableError with retryable=false.

Example:

return retry.MarkNonRetryable(authErr, 401)

func MarkRetryable

func MarkRetryable(err error, statusCode int) error

MarkRetryable wraps an error to mark it as retryable with the given status code. This is a convenience function for NewRetryableError with retryable=true.

Example:

return retry.MarkRetryable(apiErr, 503)

func NewRetryableError

func NewRetryableError(err error, retryable bool, statusCode int) error

NewRetryableError creates a new retryable error with the specified status code. The retryable parameter determines if the error should trigger retries.

Example:

err := retry.NewRetryableError(errors.New("API error"), true, 503)

Types

type BackoffStrategy

type BackoffStrategy = retry.BackoffStrategy

BackoffStrategy defines the interface for calculating retry delays. Implementations provide different algorithms for determining how long to wait between retry attempts.

type ConstantBackoffStrategy

type ConstantBackoffStrategy = retry.ConstantBackoffStrategy

ConstantBackoffStrategy implements a constant delay between retries. The same delay is used for all retry attempts.

func NewConstantBackoffStrategy

func NewConstantBackoffStrategy(delay time.Duration) *ConstantBackoffStrategy

NewConstantBackoffStrategy creates a new constant backoff strategy. The same delay is used for all retry attempts.

Example:

strategy := retry.NewConstantBackoffStrategy(2 * time.Second)

type ExecuteFunc

type ExecuteFunc[T any] func() (T, error)

ExecuteFunc is a generic function type that works with any return type. This is useful for strongly-typed operations.

type ExponentialBackoffStrategy

type ExponentialBackoffStrategy = retry.ExponentialBackoffStrategy

ExponentialBackoffStrategy implements exponential backoff with configurable jitter. The delay increases exponentially with each retry attempt, optionally randomized with jitter.

func NewExponentialBackoffStrategy

func NewExponentialBackoffStrategy(policy *Policy) *ExponentialBackoffStrategy

NewExponentialBackoffStrategy creates a new exponential backoff strategy. Uses EqualJitter by default, which can be changed with WithJitterType.

Example:

policy := retry.DefaultRetryPolicy()
strategy := retry.NewExponentialBackoffStrategy(policy).
    WithJitterType(retry.FullJitter)

type JitterType

type JitterType = retry.JitterType

JitterType defines different types of jitter strategies for randomizing delays. Jitter helps prevent thundering herd problems when multiple clients retry simultaneously.

type LinearBackoffStrategy

type LinearBackoffStrategy = retry.LinearBackoffStrategy

LinearBackoffStrategy implements a linear increase in delay. The delay increases by a fixed increment with each retry attempt.

func NewLinearBackoffStrategy

func NewLinearBackoffStrategy(initialDelay, increment, maxDelay time.Duration) *LinearBackoffStrategy

NewLinearBackoffStrategy creates a new linear backoff strategy. The delay increases by a fixed increment with each retry attempt, capped at maxDelay.

Example:

strategy := retry.NewLinearBackoffStrategy(
    1*time.Second,  // initialDelay
    500*time.Millisecond, // increment
    30*time.Second, // maxDelay
)

type OnRetryFunc

type OnRetryFunc = retry.OnRetryFunc

OnRetryFunc is a callback function type that is called before each retry attempt. It receives the current attempt number, the error that triggered the retry, and the delay before the next attempt.

type Policy

type Policy = retry.RetryPolicy

Policy defines the configuration for retry behavior. It specifies how many times to retry, delays between attempts, and which errors/status codes should trigger retries.

func AggressiveRetryPolicy

func AggressiveRetryPolicy() *Policy

AggressiveRetryPolicy returns a policy with more retry attempts and shorter delays. Useful for testing or services with very high availability:

  • MaxRetries: 5
  • InitialDelay: 500ms
  • MaxDelay: 10 seconds
  • Multiplier: 1.5
  • Jitter: 0.2 (20%)

func ConservativeRetryPolicy

func ConservativeRetryPolicy() *Policy

ConservativeRetryPolicy returns a policy with fewer retries and longer delays. Useful for rate-limited APIs or to avoid overwhelming services:

  • MaxRetries: 2
  • InitialDelay: 2 seconds
  • MaxDelay: 60 seconds
  • Multiplier: 3.0
  • Jitter: 0.05 (5%)

func DefaultRetryPolicy

func DefaultRetryPolicy() *Policy

DefaultRetryPolicy returns a retry policy with sensible defaults:

  • MaxRetries: 3
  • InitialDelay: 1 second
  • MaxDelay: 30 seconds
  • Multiplier: 2.0
  • Jitter: 0.1 (10%)

func NoRetryPolicy

func NoRetryPolicy() *Policy

NoRetryPolicy returns a policy that never retries (MaxRetries: 0). Useful for disabling retry behavior while maintaining a consistent API.

type RetryExecutor

type RetryExecutor struct {
	// contains filtered or unexported fields
}

RetryExecutor handles the execution of operations with retry logic. It combines a retry policy with a backoff strategy to provide automatic retry functionality for operations.

func NewDefaultRetryExecutor

func NewDefaultRetryExecutor() *RetryExecutor

NewDefaultRetryExecutor creates a retry executor with default settings. Uses DefaultRetryPolicy() and ExponentialBackoffStrategy with EqualJitter.

func NewRetryExecutor

func NewRetryExecutor(policy *Policy, strategy BackoffStrategy) *RetryExecutor

NewRetryExecutor creates a new retry executor with the given policy and strategy.

Example:

policy := retry.DefaultRetryPolicy()
strategy := retry.NewExponentialBackoffStrategy(policy)
executor := retry.NewRetryExecutor(policy, strategy)

func (*RetryExecutor) Execute

func (r *RetryExecutor) Execute(ctx context.Context, operation func() error) error

Execute executes a function with retry logic. The function is called repeatedly until it succeeds, a non-retryable error occurs, or the maximum number of retries is reached.

The context can be used to cancel the retry operation.

func (*RetryExecutor) ExecuteWithCallback

func (r *RetryExecutor) ExecuteWithCallback(
	ctx context.Context,
	operation func() error,
	onRetry OnRetryFunc,
) error

ExecuteWithCallback executes a function with retry logic and callback notifications. The onRetry callback is invoked before each retry attempt, allowing for logging, metrics collection, or custom retry handling.

func (*RetryExecutor) ExecuteWithResult

func (r *RetryExecutor) ExecuteWithResult(ctx context.Context, operation func() (interface{}, error)) (interface{}, error)

ExecuteWithResult executes a function that returns a result and error with retry logic. Returns the result from the first successful attempt or the last result if all retries fail.

func (*RetryExecutor) GetPolicy

func (r *RetryExecutor) GetPolicy() *Policy

GetPolicy returns the current retry policy.

func (*RetryExecutor) GetStrategy

func (r *RetryExecutor) GetStrategy() BackoffStrategy

GetStrategy returns the current backoff strategy.

func (*RetryExecutor) WithPolicy

func (r *RetryExecutor) WithPolicy(policy *Policy) *RetryExecutor

WithPolicy creates a new executor with a different policy. Returns a new RetryExecutor instance without modifying the original.

func (*RetryExecutor) WithStrategy

func (r *RetryExecutor) WithStrategy(strategy BackoffStrategy) *RetryExecutor

WithStrategy creates a new executor with a different strategy. Returns a new RetryExecutor instance without modifying the original.

type RetryStrategy

type RetryStrategy = retry.RetryStrategy

RetryStrategy defines the interface for retry execution. It provides the NextDelay method for calculating delays before retry attempts.

type RetryableError

type RetryableError = retry.RetryableError

RetryableError is an interface for errors that can indicate retry possibility. Errors implementing this interface can explicitly declare whether they should be retried.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL