class Faraday::Request::Retry

Catches exceptions and retries each request a limited number of times.

By default, it retries 2 times and handles only timeout exceptions. It can be configured with an arbitrary number of retries, a list of exceptions to handle, a retry interval, a percentage of randomness to add to the retry interval, and a backoff factor.

@example Configure Retry middleware using intervals

Faraday.new do |conn|
  conn.request(:retry, max: 2,
                       interval: 0.05,
                       interval_randomness: 0.5,
                       backoff_factor: 2,
                       exceptions: [CustomException, 'Timeout::Error'])

  conn.adapter(:net_http) # NB: Last middleware must be the adapter
end

This example will result in a first interval that is random between 0.05 and 0.075 and a second interval that is random between 0.1 and 0.125.

Constants

DEFAULT_EXCEPTIONS
IDEMPOTENT_METHODS

Public Class Methods

new(app, options = nil) click to toggle source

@param app [#call] @param options [Hash] @option options [Integer] :max (2) Maximum number of retries @option options [Integer] :interval (0) Pause in seconds between retries @option options [Integer] :interval_randomness (0) The maximum random

interval amount expressed as a float between
0 and 1 to use in addition to the interval.

@option options [Integer] :max_interval (Float::MAX) An upper limit

for the interval

@option options [Integer] :backoff_factor (1) The amount to multiply

each successive retry's interval amount by in order to provide backoff

@option options [Array] :exceptions ([ Errno::ETIMEDOUT,

'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse])
The list of exceptions to handle. Exceptions can be given as
Class, Module, or String.

@option options [Array] :methods (the idempotent HTTP methods

in IDEMPOTENT_METHODS) A list of HTTP methods to retry without
calling retry_if. Pass an empty Array to call retry_if
for all exceptions.

@option options [Block] :retry_if (false) block that will receive

the env object and the exception raised
and should decide if the code should retry still the action or
not independent of the retry count. This would be useful
if the exception produced is non-recoverable or if the
the HTTP method called is not idempotent.

@option options [Block] :retry_block block that is executed before

every retry. Request environment, middleware options, current number
of retries and the exception is passed to the block as parameters.

@option options [Array] :retry_statuses Array of Integer HTTP status

codes or a single Integer value that determines whether to raise
a Faraday::RetriableResponse exception based on the HTTP status code
of an HTTP response.
Calls superclass method Faraday::Middleware::new
# File lib/faraday/request/retry.rb, line 122
def initialize(app, options = nil)
  super(app)
  @options = Options.from(options)
  @errmatch = build_exception_matcher(@options.exceptions)
end

Public Instance Methods

build_exception_matcher(exceptions) click to toggle source

An exception matcher for the rescue clause can usually be any object that responds to `===`, but for Ruby 1.8 it has to be a Class or Module.

@param exceptions [Array] @api private @return [Module] an exception matcher

# File lib/faraday/request/retry.rb, line 176
def build_exception_matcher(exceptions)
  matcher = Module.new
  (
  class << matcher
    self
  end).class_eval do
    define_method(:===) do |error|
      exceptions.any? do |ex|
        if ex.is_a? Module
          error.is_a? ex
        else
          error.class.to_s == ex.to_s
        end
      end
    end
  end
  matcher
end
calculate_sleep_amount(retries, env) click to toggle source
# File lib/faraday/request/retry.rb, line 128
def calculate_sleep_amount(retries, env)
  retry_after = calculate_retry_after(env)
  retry_interval = calculate_retry_interval(retries)

  return if retry_after && retry_after > @options.max_interval

  if retry_after && retry_after >= retry_interval
    retry_after
  else
    retry_interval
  end
end
call(env) click to toggle source

@param env [Faraday::Env]

# File lib/faraday/request/retry.rb, line 142
def call(env)
  retries = @options.max
  request_body = env[:body]
  begin
    # after failure env[:body] is set to the response body
    env[:body] = request_body
    @app.call(env).tap do |resp|
      if @options.retry_statuses.include?(resp.status)
        raise Faraday::RetriableResponse.new(nil, resp)
      end
    end
  rescue @errmatch => e
    if retries.positive? && retry_request?(env, e)
      retries -= 1
      rewind_files(request_body)
      @options.retry_block.call(env, @options, retries, e)
      if (sleep_amount = calculate_sleep_amount(retries + 1, env))
        sleep sleep_amount
        retry
      end
    end

    raise unless e.is_a?(Faraday::RetriableResponse)

    e.response
  end
end

Private Instance Methods

calculate_retry_after(env) click to toggle source

MDN spec for Retry-After header: developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After

# File lib/faraday/request/retry.rb, line 212
def calculate_retry_after(env)
  response_headers = env[:response_headers]
  return unless response_headers

  retry_after_value = env[:response_headers]['Retry-After']

  # Try to parse date from the header value
  begin
    datetime = DateTime.rfc2822(retry_after_value)
    datetime.to_time - Time.now.utc
  rescue ArgumentError
    retry_after_value.to_f
  end
end
calculate_retry_interval(retries) click to toggle source
# File lib/faraday/request/retry.rb, line 227
def calculate_retry_interval(retries)
  retry_index = @options.max - retries
  current_interval = @options.interval *
                     (@options.backoff_factor**retry_index)
  current_interval = [current_interval, @options.max_interval].min
  random_interval = rand * @options.interval_randomness.to_f *
                    @options.interval

  current_interval + random_interval
end
retry_request?(env, exception) click to toggle source
# File lib/faraday/request/retry.rb, line 197
def retry_request?(env, exception)
  @options.methods.include?(env[:method]) ||
    @options.retry_if.call(env, exception)
end
rewind_files(body) click to toggle source
# File lib/faraday/request/retry.rb, line 202
def rewind_files(body)
  return unless body.is_a?(Hash)

  body.each do |_, value|
    value.rewind if value.is_a?(UploadIO)
  end
end