Skip to content

feat: added retryMax middleware #2808

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

BohdanStarunskyi
Copy link

Add RetryMax Middleware for Automatic Request Retries

Description

Implements a new RetryMax middleware that provides automatic retry functionality for failed requests, addressing the need for production-ready retry mechanisms in Echo applications.

Feature request #2507

Features

Core Functionality

  • Configurable retry attempts: Set maximum number of retry attempts per request
  • Flexible backoff strategies: Exponential backoff (with jitter) and linear backoff options
  • Smart retry logic: Configurable conditions for determining which errors should trigger retries
  • Timeout control: Configurable minimum and maximum delays between retry attempts

Advanced Features

  • Pluggable storage: Interface-based storage system supporting in-memory and external stores
  • Per-identifier tracking: Track retry attempts per IP, user ID, or custom identifier
  • Memory management: Built-in cleanup mechanisms prevent memory leaks in long-running applications
  • Context awareness: Proper cancellation support and timeout handling
  • Observability hooks: Callbacks for monitoring retry attempts and debugging

Configuration Options

type RetryMaxConfig struct {
    // Core settings
    MaxAttempts     int           // Maximum retry attempts (default: 5)
    MinTimeout      time.Duration // Minimum delay between retries (default: 1s)
    MaxTimeout      time.Duration // Maximum delay between retries (default: 5s)
    
    // Advanced options
    BackoffStrategy     BackoffStrategy     // Retry delay calculation
    RetryableChecker    RetryableChecker    // Determines if error is retryable
    IdentifierExtractor Extractor           // Extract identifier for tracking
    Store              RetryMaxStore       // Optional storage backend
    
    // Hooks and handlers
    OnRetry      func(...)  // Called before each retry
    ErrorHandler func(...)  // Handle extraction errors
    DenyHandler  func(...)  // Handle max attempts exceeded
}

Usage Examples

Basic Usage

// Simple retry with defaults
e.Use(middleware.RetryMaxWithConfig(middleware.RetryMaxConfig{
    MaxAttempts: 3,
    MinTimeout:  100 * time.Millisecond,
    MaxTimeout:  1 * time.Second,
}))

Advanced Configuration

// Custom retry logic with observability
e.Use(middleware.RetryMaxWithConfig(middleware.RetryMaxConfig{
    MaxAttempts: 5,
    MinTimeout:  500 * time.Millisecond,
    MaxTimeout:  30 * time.Second,
    BackoffStrategy: middleware.ExponentialBackoff,
    RetryableChecker: func(err error) bool {
        // Only retry server errors and timeouts
        if httpErr, ok := err.(*echo.HTTPError); ok {
            return httpErr.Code >= 500 || httpErr.Code == 408
        }
        return true
    },
    OnRetry: func(c echo.Context, identifier string, attempt int, err error) {
        log.Printf("Retry %d for %s: %v", attempt, identifier, err)
    },
}))

With External Store

// Using persistent storage for retry tracking
store := middleware.NewRetryMaxMemoryStore(10 * time.Minute)
e.Use(middleware.RetryMax(store))

Implementation Details

Retry Flow

  1. Execute handler function
  2. On success: reset retry counter and return
  3. On error: check if error is retryable
  4. If retryable and under max attempts: wait (backoff) and retry
  5. If max attempts reached: return original error

Storage Interface

The middleware supports pluggable storage through the RetryMaxStore interface:

type RetryMaxStore interface {
    AllowAttempt(identifier string) (bool, error)
    ResetAttempts(identifier string) error
}

Default Behavior

  • No store: Retries happen within single request scope
  • Memory store: Tracks attempts across requests with expiration
  • Custom store: Implement interface for Redis, database, etc.

Error Handling

  • Retryable errors: Server errors (5xx), timeouts, network issues
  • Non-retryable errors: Client errors (4xx), validation failures
  • Custom logic: Configurable via RetryableChecker function

Testing

Comprehensive test suite covering:

  • Success on first attempt
  • Retry until success
  • Max attempts exhaustion
  • Skipper functionality
  • Timeout respect and backoff timing
  • Store integration
  • Error handling scenarios

Migration from Custom Implementation

For users with existing retry implementations:

// Before (custom implementation)
app.use(customRetryMax({
  maxAttempts: 5, 
  minTimeout: 1000, 
  maxTimeout: 5000 
}));

// After (Echo middleware)
e.Use(middleware.RetryMaxWithConfig(middleware.RetryMaxConfig{
    MaxAttempts: 5,
    MinTimeout:  1000 * time.Millisecond,
    MaxTimeout:  5000 * time.Millisecond,
}))

Performance Considerations

  • Memory efficient: Automatic cleanup prevents unbounded growth
  • Lock optimization: Read/write locks minimize contention
  • Jitter support: Prevents thundering herd problems
  • Context cancellation: Respects request timeouts and cancellation

Breaking Changes

None - this is a new middleware addition with no impact on existing code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant