Class: Pvectl::Connection::RetryHandler
- Inherits:
-
Object
- Object
- Pvectl::Connection::RetryHandler
- Defined in:
- lib/pvectl/connection/retry_handler.rb,
sig/pvectl/connection/retry_handler.rbs
Overview
Handles retry logic with exponential backoff for API requests.
RetryHandler wraps API calls to automatically retry on transient errors like network timeouts, server errors (5xx), and rate limiting (429). By default, only read operations (GET) are retried; write operations require explicit opt-in via retry_writes due to idempotency concerns.
Constant Summary collapse
- RETRYABLE_EXCEPTIONS =
Exceptions that indicate transient failures safe to retry.
Includes:
- Connection timeouts (OpenTimeout, ReadTimeout)
- Server errors (5xx)
- Rate limiting (429)
- Network errors (ECONNREFUSED, ECONNRESET, SocketError)
- Global timeout (Timeout::Error from Ruby's Timeout module)
[ RestClient::Exceptions::OpenTimeout, RestClient::Exceptions::ReadTimeout, RestClient::InternalServerError, # 500 RestClient::BadGateway, # 502 RestClient::ServiceUnavailable, # 503 RestClient::GatewayTimeout, # 504 RestClient::TooManyRequests, # 429 Errno::ECONNREFUSED, Errno::ECONNRESET, SocketError, Timeout::Error ].freeze
- READ_METHODS =
HTTP methods considered safe to retry (read-only, idempotent).
i[get head ].freeze
Instance Attribute Summary collapse
-
#base_delay ⇒ Numeric
readonly
Base delay in seconds for exponential backoff.
-
#logger ⇒ Logger?
readonly
Optional logger for retry messages.
-
#max_delay ⇒ Numeric
readonly
Maximum delay cap in seconds.
-
#max_retries ⇒ Integer
readonly
Maximum number of retry attempts.
-
#retry_writes ⇒ Boolean
readonly
Whether to retry write operations.
Instance Method Summary collapse
-
#calculate_delay(attempt) ⇒ Numeric
Calculates delay using exponential backoff.
-
#initialize(max_retries:, base_delay:, max_delay:, retry_writes: false, logger: nil) ⇒ RetryHandler
constructor
Creates a new RetryHandler.
-
#log_retry(attempt, delay, error) ⇒ void
Logs a retry attempt.
-
#should_retry?(method, attempts) ⇒ Boolean
Determines if the operation should be retried.
-
#with_retry(method: :get) { ... } ⇒ Object
Executes a block with retry logic.
Constructor Details
#initialize(max_retries:, base_delay:, max_delay:, retry_writes: false, logger: nil) ⇒ RetryHandler
Creates a new RetryHandler.
76 77 78 79 80 81 82 |
# File 'lib/pvectl/connection/retry_handler.rb', line 76 def initialize(max_retries:, base_delay:, max_delay:, retry_writes: false, logger: nil) @max_retries = max_retries @base_delay = base_delay @max_delay = max_delay @retry_writes = retry_writes @logger = logger end |
Instance Attribute Details
#base_delay ⇒ Numeric (readonly)
58 59 60 |
# File 'lib/pvectl/connection/retry_handler.rb', line 58 def base_delay @base_delay end |
#logger ⇒ Logger? (readonly)
67 68 69 |
# File 'lib/pvectl/connection/retry_handler.rb', line 67 def logger @logger end |
#max_delay ⇒ Numeric (readonly)
61 62 63 |
# File 'lib/pvectl/connection/retry_handler.rb', line 61 def max_delay @max_delay end |
#max_retries ⇒ Integer (readonly)
55 56 57 |
# File 'lib/pvectl/connection/retry_handler.rb', line 55 def max_retries @max_retries end |
#retry_writes ⇒ Boolean (readonly)
64 65 66 |
# File 'lib/pvectl/connection/retry_handler.rb', line 64 def retry_writes @retry_writes end |
Instance Method Details
#calculate_delay(attempt) ⇒ Numeric
Calculates delay using exponential backoff.
Formula: delay = min(base_delay * 2^(attempt-1), max_delay)
Example with base_delay=1, max_delay=30:
- Attempt 1: 1s
- Attempt 2: 2s
- Attempt 3: 4s
- Attempt 4: 8s
- Attempt 5+: capped at 30s
139 140 141 142 |
# File 'lib/pvectl/connection/retry_handler.rb', line 139 def calculate_delay(attempt) delay = base_delay * (2**(attempt - 1)) [delay, max_delay].min end |
#log_retry(attempt, delay, error) ⇒ void
This method returns an undefined value.
Logs a retry attempt.
Security: Only logs error class name, NOT error.message which may contain sensitive data (URLs with tokens, request bodies, etc.)
152 153 154 155 156 157 158 |
# File 'lib/pvectl/connection/retry_handler.rb', line 152 def log_retry(attempt, delay, error) return unless logger logger.warn( "Retry #{attempt}/#{max_retries} after #{delay}s: #{error.class.name}" ) end |
#should_retry?(method, attempts) ⇒ Boolean
Determines if the operation should be retried.
119 120 121 122 123 124 |
# File 'lib/pvectl/connection/retry_handler.rb', line 119 def should_retry?(method, attempts) return false if attempts > max_retries return true if READ_METHODS.include?(method) retry_writes end |
#with_retry(method: :get) { ... } ⇒ Object
Executes a block with retry logic.
Retries the block on transient errors using exponential backoff. By default, only read operations (GET, HEAD, OPTIONS) are retried. Write operations require retry_writes: true in the constructor.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/pvectl/connection/retry_handler.rb', line 97 def with_retry(method: :get) attempts = 0 begin attempts += 1 yield rescue *RETRYABLE_EXCEPTIONS => e raise unless should_retry?(method, attempts) delay = calculate_delay(attempts) log_retry(attempts, delay, e) sleep(delay) retry end end |