Module: LanguageOperator::Retry

Defined in:
lib/language_operator/retry.rb

Overview

Retry utilities with exponential backoff for handling transient failures

Constant Summary collapse

DEFAULT_MAX_RETRIES =

Default retry configuration

3
DEFAULT_BASE_DELAY =
1.0
DEFAULT_MAX_DELAY =
10.0
DEFAULT_JITTER_FACTOR =
0.1
RETRYABLE_HTTP_CODES =

Common retryable HTTP status codes (transient errors)

[429, 500, 502, 503, 504].freeze

Class Method Summary collapse

Class Method Details

.calculate_backoff(attempt, base_delay = DEFAULT_BASE_DELAY, max_delay = DEFAULT_MAX_DELAY, jitter_factor = DEFAULT_JITTER_FACTOR) ⇒ Float

Calculate exponential backoff delay with jitter

Examples:

LanguageOperator::Retry.calculate_backoff(1) # => ~1.0 seconds
LanguageOperator::Retry.calculate_backoff(2) # => ~2.0 seconds
LanguageOperator::Retry.calculate_backoff(3) # => ~4.0 seconds

Parameters:

  • Retry attempt number (1-based)

  • (defaults to: DEFAULT_BASE_DELAY)

    Initial delay in seconds

  • (defaults to: DEFAULT_MAX_DELAY)

    Maximum delay cap in seconds

  • (defaults to: DEFAULT_JITTER_FACTOR)

    Jitter randomization factor (0.0 to 1.0)

Returns:

  • Delay in seconds



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/language_operator/retry.rb', line 110

def self.calculate_backoff(attempt,
                           base_delay = DEFAULT_BASE_DELAY,
                           max_delay = DEFAULT_MAX_DELAY,
                           jitter_factor = DEFAULT_JITTER_FACTOR)
  # Exponential: base * 2^(attempt-1)
  exponential = base_delay * (2**(attempt - 1))
  # Cap at max
  capped = [exponential, max_delay].min
  # Add jitter: ±(delay * jitter_factor * random)
  jitter = capped * jitter_factor * (rand - 0.5) * 2
  capped + jitter
end

.on_exceptions(exception_types, max_retries: DEFAULT_MAX_RETRIES, base_delay: DEFAULT_BASE_DELAY, max_delay: DEFAULT_MAX_DELAY, jitter_factor: DEFAULT_JITTER_FACTOR) { ... } ⇒ Object

Execute a block with retry for specific exception types

Examples:

LanguageOperator::Retry.on_exceptions([Net::OpenTimeout, Errno::ECONNREFUSED]) do
  smtp.connect
end

Parameters:

  • Exception types to retry on

  • (defaults to: DEFAULT_MAX_RETRIES)

    Maximum number of retry attempts

  • (defaults to: DEFAULT_BASE_DELAY)

    Initial delay in seconds

  • (defaults to: DEFAULT_MAX_DELAY)

    Maximum delay cap in seconds

  • (defaults to: DEFAULT_JITTER_FACTOR)

    Jitter randomization factor

Yields:

  • Block to execute

Returns:

  • Return value of the block

Raises:

  • Re-raises the exception if all retries are exhausted



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/language_operator/retry.rb', line 70

def self.on_exceptions(exception_types, max_retries: DEFAULT_MAX_RETRIES,
                       base_delay: DEFAULT_BASE_DELAY,
                       max_delay: DEFAULT_MAX_DELAY,
                       jitter_factor: DEFAULT_JITTER_FACTOR)
  attempt = 0
  begin
    yield
  rescue *exception_types => e
    if attempt < max_retries
      attempt += 1
      delay = calculate_backoff(attempt, base_delay, max_delay, jitter_factor)
      sleep delay
      retry
    end
    raise e
  end
end

.retryable_http_code?(status) ⇒ Boolean

Check if an HTTP status code is retryable (transient error)

Examples:

LanguageOperator::Retry.retryable_http_code?(503) # => true
LanguageOperator::Retry.retryable_http_code?(404) # => false

Parameters:

  • HTTP status code

Returns:

  • True if status code indicates a transient error



95
96
97
# File 'lib/language_operator/retry.rb', line 95

def self.retryable_http_code?(status)
  RETRYABLE_HTTP_CODES.include?(status)
end

.with_backoff(max_retries: DEFAULT_MAX_RETRIES, base_delay: DEFAULT_BASE_DELAY, max_delay: DEFAULT_MAX_DELAY, jitter_factor: DEFAULT_JITTER_FACTOR, on_retry: nil) { ... } ⇒ Object

Execute a block with exponential backoff retry logic

Examples:

Basic usage

LanguageOperator::Retry.with_backoff(max_retries: 5) do
  client.get_resource(name)
end

With callback

LanguageOperator::Retry.with_backoff(on_retry: ->(attempt, e) {
  puts "Retry attempt #{attempt} after error: #{e.message}"
}) do
  api_call
end

Parameters:

  • (defaults to: DEFAULT_MAX_RETRIES)

    Maximum number of retry attempts (default: 3)

  • (defaults to: DEFAULT_BASE_DELAY)

    Initial delay in seconds (default: 1.0)

  • (defaults to: DEFAULT_MAX_DELAY)

    Maximum delay cap in seconds (default: 10.0)

  • (defaults to: DEFAULT_JITTER_FACTOR)

    Jitter randomization factor (default: 0.1)

  • (defaults to: nil)

    Optional callback called before each retry (receives attempt number and exception)

Yields:

  • Block to execute with retry logic

Returns:

  • Return value of the block

Raises:

  • Re-raises the exception if all retries are exhausted



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/language_operator/retry.rb', line 36

def self.with_backoff(max_retries: DEFAULT_MAX_RETRIES,
                      base_delay: DEFAULT_BASE_DELAY,
                      max_delay: DEFAULT_MAX_DELAY,
                      jitter_factor: DEFAULT_JITTER_FACTOR,
                      on_retry: nil)
  attempt = 0
  begin
    yield
  rescue StandardError => e
    if attempt < max_retries
      attempt += 1
      delay = calculate_backoff(attempt, base_delay, max_delay, jitter_factor)
      on_retry&.call(attempt, e)
      sleep delay
      retry
    end
    raise e
  end
end