class HTTP::Retriable::Performer
@api private
Request performing watchdog.
def calculate_delay(iteration, response)
def calculate_delay(iteration, response) @delay_calculator.call(iteration, response) end
def initialize(opts)
(**opts)
-
:should_retry
(#call
) -- -
:max_delay
(#to_f
) -- -
:on_retry
(#call
) -- -
:retry_statuses
(Array(#to_i)
) -- -
:exceptions
(Array(Exception)
) -- -
:delay
(#call, #to_i
) -- -
:tries
(#to_i
) --
Parameters:
-
opts
(Hash
) --
def initialize(opts) @exception_classes = opts.fetch(:exceptions, RETRIABLE_ERRORS) @retry_statuses = opts[:retry_statuses] @tries = opts.fetch(:tries, 5).to_i @on_retry = opts.fetch(:on_retry, ->(*) {}) @should_retry_proc = opts[:should_retry] @delay_calculator = DelayCalculator.new(opts) end
def out_of_retries_error(request, response, exception)
-
exception
(Exception, nil
) -- -
status
(HTTP::Response, nil
) -- -
request
(HTTP::Request
) --
def out_of_retries_error(request, response, exception) message = "#{request.verb.to_s.upcase} <#{request.uri}> failed" message += " with #{response.status}" if response message += ":#{exception}" if exception HTTP::OutOfRetriesError.new(message).tap do |ex| ex.cause = exception ex.response = response end end
def perform(client, req, &block)
- Api: - private
Other tags:
- See: #initialize -
def perform(client, req, &block) 1.upto(Float::INFINITY) do |attempt| # infinite loop with index err, res = try_request(&block) if retry_request?(req, err, res, attempt) begin wait_for_retry_or_raise(req, err, res, attempt) ensure # Some servers support Keep-Alive on any response. Thus we should # flush response before retry, to avoid state error (when socket # has pending response data and we try to write new request). # Alternatively, as we don't need response body here at all, we # are going to close client, effectivle closing underlying socket # and resetting client's state. client.close end elsif err client.close raise err elsif res return res end end end
def retry_exception?(err)
def retry_exception?(err) @exception_classes.any? { |e| err.is_a?(e) } end
def retry_request?(req, err, res, attempt)
def retry_request?(req, err, res, attempt) if @should_retry_proc @should_retry_proc.call(req, err, res, attempt) elsif err retry_exception?(err) else retry_response?(res) end end
def retry_response?(res)
def retry_response?(res) return false unless @retry_statuses response_status = res.status.to_i retry_matchers = [@retry_statuses].flatten retry_matchers.any? do |matcher| case matcher when Range then matcher.cover?(response_status) when Numeric then matcher == response_status else matcher.call(response_status) end end end
def try_request
def try_request err, res = nil begin res = yield rescue Exception => e err = e end [err, res] end
def wait_for_retry_or_raise(req, err, res, attempt)
def wait_for_retry_or_raise(req, err, res, attempt) if attempt < @tries @on_retry.call(req, err, res) sleep calculate_delay(attempt, res) else res&.flush raise out_of_retries_error(req, res, err) end end