Module: Crimson::RetryHandler
- Defined in:
- lib/crimson/retry_handler.rb
Overview
Retry logic for API calls with exponential backoff and retry-After header support.
Constant Summary collapse
- MAX_RETRIES =
Maximum number of retry attempts.
3- BASE_DELAY =
Base delay in seconds for exponential backoff.
1.0- MAX_DELAY =
Maximum delay cap in seconds.
30.0- RETRYABLE_MESSAGES =
Patterns in error messages that indicate a retry is appropriate.
[ /rate.?limit/i, /too many requests/i, /429/, /5\d{2}/, /timeout/i, /timed?\s*out/i, /connection.*reset/i, /connection.*refused/i, /ECONNRESET/, /ECONNREFUSED/, /ETIMEDOUT/, /ENOTFOUND/, /network/i, /overloaded/i, /capacity/i, /server error/i, /service unavailable/i, /bad gateway/i, /gateway timeout/i, /internal server error/i ].freeze
Class Method Summary collapse
-
.compute_delay(error, attempt, base_delay, max_delay) ⇒ Float
Compute delay using exponential backoff, respecting Retry-After headers.
-
.extract_retry_after(error) ⇒ Object
private
Extract Retry-After header from an error response.
-
.retryable?(error) ⇒ Boolean
Check whether an error is retryable based on its message.
-
.with_retry(max_retries: MAX_RETRIES, base_delay: BASE_DELAY, max_delay: MAX_DELAY) { ... } ⇒ Object
Execute a block with retry logic.
Class Method Details
.compute_delay(error, attempt, base_delay, max_delay) ⇒ Float
Compute delay using exponential backoff, respecting Retry-After headers.
77 78 79 80 81 82 83 |
# File 'lib/crimson/retry_handler.rb', line 77 def self.compute_delay(error, attempt, base_delay, max_delay) retry_after = extract_retry_after(error) return [retry_after, max_delay].min if retry_after && retry_after > 0 delay = [base_delay * (2 ** (attempt - 1)), max_delay].min delay + rand * 0.5 end |
.extract_retry_after(error) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Extract Retry-After header from an error response.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/crimson/retry_handler.rb', line 87 def self.extract_retry_after(error) return nil unless error.respond_to?(:response) response = error.response return nil unless response.is_a?(Hash) headers = response[:headers] || response["headers"] return nil unless headers.is_a?(Hash) retry_after = headers["Retry-After"] || headers["retry-after"] return nil unless retry_after retry_after.to_f rescue nil end |
.retryable?(error) ⇒ Boolean
Check whether an error is retryable based on its message.
66 67 68 69 |
# File 'lib/crimson/retry_handler.rb', line 66 def self.retryable?(error) = "#{error.class}: #{error.}" RETRYABLE_MESSAGES.any? { |pattern| .match?(pattern) } end |
.with_retry(max_retries: MAX_RETRIES, base_delay: BASE_DELAY, max_delay: MAX_DELAY) { ... } ⇒ Object
Execute a block with retry logic.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/crimson/retry_handler.rb', line 44 def self.with_retry(max_retries: MAX_RETRIES, base_delay: BASE_DELAY, max_delay: MAX_DELAY) attempts = 0 last_error = nil loop do attempts += 1 begin return yield rescue => e last_error = e raise e if attempts > max_retries raise e unless retryable?(e) delay = compute_delay(e, attempts, base_delay, max_delay) sleep delay end end end |