Class: Pvectl::Connection::RetryHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/pvectl/connection/retry_handler.rb

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.

Examples:

Basic usage with read operation

handler = RetryHandler.new(max_retries: 3, base_delay: 1, max_delay: 30)
result = handler.with_retry(method: :get) { api.nodes.get }

Write operation with retries disabled (default)

handler.with_retry(method: :post) { api.nodes[node].qemu.post(data) }
# Will NOT retry on failure

Write operation with retries enabled

handler = RetryHandler.new(max_retries: 3, base_delay: 1, max_delay: 30, retry_writes: true)
handler.with_retry(method: :post) { api.nodes[node].qemu.post(data) }
# Will retry on transient failures

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 options].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_retries:, base_delay:, max_delay:, retry_writes: false, logger: nil) ⇒ RetryHandler

Creates a new RetryHandler.

Parameters:

  • max_retries (Integer)

    maximum number of retry attempts (0 = no retries)

  • base_delay (Numeric)

    base delay in seconds for exponential backoff

  • max_delay (Numeric)

    maximum delay cap in seconds

  • retry_writes (Boolean) (defaults to: false)

    whether to retry write operations (default: false)

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

    optional logger for retry messages



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_delayNumeric (readonly)

Returns base delay in seconds for exponential backoff.

Returns:

  • (Numeric)

    base delay in seconds for exponential backoff



58
59
60
# File 'lib/pvectl/connection/retry_handler.rb', line 58

def base_delay
  @base_delay
end

#loggerLogger? (readonly)

Returns optional logger for retry messages.

Returns:

  • (Logger, nil)

    optional logger for retry messages



67
68
69
# File 'lib/pvectl/connection/retry_handler.rb', line 67

def logger
  @logger
end

#max_delayNumeric (readonly)

Returns maximum delay cap in seconds.

Returns:

  • (Numeric)

    maximum delay cap in seconds



61
62
63
# File 'lib/pvectl/connection/retry_handler.rb', line 61

def max_delay
  @max_delay
end

#max_retriesInteger (readonly)

Returns maximum number of retry attempts.

Returns:

  • (Integer)

    maximum number of retry attempts



55
56
57
# File 'lib/pvectl/connection/retry_handler.rb', line 55

def max_retries
  @max_retries
end

#retry_writesBoolean (readonly)

Returns whether to retry write operations.

Returns:

  • (Boolean)

    whether to retry write operations



64
65
66
# File 'lib/pvectl/connection/retry_handler.rb', line 64

def retry_writes
  @retry_writes
end

Instance Method Details

#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.

Examples:

handler.with_retry(method: :get) { api.nodes.get }

Parameters:

  • method (Symbol) (defaults to: :get)

    HTTP method (:get, :post, :put, :delete, :head, :options)

Yields:

  • the API call to execute

Returns:

  • (Object)

    result of the block

Raises:

  • (Exception)

    the last error after all retries exhausted



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