Module: DurableHuggingfaceHub::Utils::Retry

Defined in:
lib/durable_huggingface_hub/utils/retry.rb

Overview

Retry logic with exponential backoff for HTTP requests.

This module provides retry functionality for handling transient failures in HTTP requests, with configurable retry attempts and exponential backoff.

Constant Summary collapse

DEFAULT_MAX_RETRIES =

Default maximum number of retry attempts

3
DEFAULT_INITIAL_DELAY =

Default initial delay in seconds

1
MAX_DELAY =

Maximum delay between retries (in seconds)

60
BACKOFF_MULTIPLIER =

Multiplier for exponential backoff

2
RETRYABLE_STATUS_CODES =

HTTP status codes that should trigger a retry

[
  408, # Request Timeout
  429, # Too Many Requests
  500, # Internal Server Error
  502, # Bad Gateway
  503, # Service Unavailable
  504  # Gateway Timeout
].freeze
RETRYABLE_ERRORS =

Errors that should trigger a retry

[
  Faraday::TimeoutError,
  Faraday::ConnectionFailed,
  Faraday::SSLError
].freeze

Class Method Summary collapse

Class Method Details

.calculate_delay(attempt, initial_delay) ⇒ Float

Calculates the delay for a retry attempt using exponential backoff.

Examples:

Retry.calculate_delay(1, 1.0)  # => 1.0
Retry.calculate_delay(2, 1.0)  # => 2.0
Retry.calculate_delay(3, 1.0)  # => 4.0
Retry.calculate_delay(4, 1.0)  # => 8.0

Parameters:

  • attempt (Integer)

    Current attempt number (1-based)

  • initial_delay (Float)

    Initial delay in seconds

Returns:

  • (Float)

    Delay in seconds (capped at MAX_DELAY)



132
133
134
135
136
137
138
# File 'lib/durable_huggingface_hub/utils/retry.rb', line 132

def self.calculate_delay(attempt, initial_delay)
  # Exponential backoff: initial_delay * (2 ^ (attempt - 1))
  delay = initial_delay * (BACKOFF_MULTIPLIER**(attempt - 1))

  # Cap at maximum delay
  [delay, MAX_DELAY].min
end

.retryable_error?(error) ⇒ Boolean

Checks if an error should trigger a retry.

Parameters:

  • error (Exception)

    The error to check

Returns:

  • (Boolean)

    True if error is retryable



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/durable_huggingface_hub/utils/retry.rb', line 103

def self.retryable_error?(error)
  # Check if it's a known retryable error class
  return true if RETRYABLE_ERRORS.any? { |klass| error.is_a?(klass) }

  # Check if it's an HTTP error with retryable status
  if error.is_a?(HfHubHTTPError) && error.status_code
    return RETRYABLE_STATUS_CODES.include?(error.status_code)
  end

  # Check Faraday response errors
  if error.respond_to?(:response) && error.response
    status = error.response[:status]
    return RETRYABLE_STATUS_CODES.include?(status) if status
  end

  false
end

.with_retry(max_retries: DEFAULT_MAX_RETRIES, initial_delay: DEFAULT_INITIAL_DELAY, logger: nil) { ... } ⇒ Object

Executes a block with retry logic.

Examples:

Basic usage

result = Retry.with_retry do
  perform_http_request
end

Custom retry configuration

result = Retry.with_retry(max_retries: 5, initial_delay: 2) do
  risky_operation
end

Parameters:

  • max_retries (Integer) (defaults to: DEFAULT_MAX_RETRIES)

    Maximum number of retry attempts (must be >= 0)

  • initial_delay (Float) (defaults to: DEFAULT_INITIAL_DELAY)

    Initial delay in seconds (must be > 0)

  • logger (Logger, nil) (defaults to: nil)

    Logger for retry messages

Yields:

  • Block to execute with retry

Yield Returns:

  • Result of the block

Returns:

  • Result of the block if successful

Raises:

  • (ArgumentError)

    If parameters are invalid

  • Last exception if all retries exhausted



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/durable_huggingface_hub/utils/retry.rb', line 61

def self.with_retry(max_retries: DEFAULT_MAX_RETRIES, initial_delay: DEFAULT_INITIAL_DELAY, logger: nil)
  # Validate parameters
  validate_max_retries(max_retries)
  validate_initial_delay(initial_delay)

  attempt = 0
  last_error = nil

  loop do
    begin
      return yield
    rescue => e
      attempt += 1
      last_error = e

      # Check if error is retryable
      unless retryable_error?(e)
        raise e
      end

      # Check if we've exhausted retries
      if attempt > max_retries
        logger&.error("Max retries (#{max_retries}) exhausted for #{e.class}: #{e.message}")
        raise e
      end

      # Calculate delay with exponential backoff
      delay = calculate_delay(attempt, initial_delay)

      # Log retry attempt
      logger&.warn("Retry attempt #{attempt}/#{max_retries} after #{delay}s due to #{e.class}: #{e.message}")

      # Wait before retrying
      sleep(delay)
    end
  end
end