Class: Faraday::Request::Retry

Inherits:
Middleware show all
Defined in:
lib/faraday/request/retry.rb

Overview

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.

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

Examples:

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

Defined Under Namespace

Classes: Options

Constant Summary collapse

DEFAULT_EXCEPTIONS =
[
  Errno::ETIMEDOUT, 'Timeout::Error',
  Faraday::TimeoutError, Faraday::RetriableResponse
].freeze
IDEMPOTENT_METHODS =
%i[delete get head options put].freeze

Instance Attribute Summary

Attributes included from DependencyLoader

#load_error

Instance Method Summary collapse

Methods included from MiddlewareRegistry

#fetch_middleware, #load_middleware, #lookup_middleware, #middleware_mutex, #register_middleware, #unregister_middleware

Methods included from DependencyLoader

#dependency, #inherited, #loaded?, #new

Constructor Details

#initialize(app, options = nil) ⇒ Retry

Returns a new instance of Retry.

Parameters:

  • app (#call)
  • options (Hash) (defaults to: nil)

Options Hash (options):

  • :max (Integer) — default: 2

    Maximum number of retries

  • :interval (Integer) — default: 0

    Pause in seconds between retries

  • :interval_randomness (Integer) — default: 0

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

  • :max_interval (Integer) — default: Float::MAX

    An upper limit for the interval

  • :backoff_factor (Integer) — default: 1

    The amount to multiply each successive retry’s interval amount by in order to provide backoff

  • :exceptions (Array) — default: [ Errno::ETIMEDOUT, 'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse]

    The list of exceptions to handle. Exceptions can be given as Class, Module, or String.

  • :methods (Array) — default: 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.

  • :retry_if (Block) — default: 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.

  • :retry_block (Block)

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

  • :retry_statuses (Array)

    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.



122
123
124
125
126
# 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

Instance Method Details

#build_exception_matcher(exceptions) ⇒ Module

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.

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.

Parameters:

  • exceptions (Array)

Returns:

  • (Module)

    an exception matcher



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# 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) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
# 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) ⇒ Object

Parameters:



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# 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