class HTTP::Retriable::Performer

@api private
Request performing watchdog.

def calculate_delay(iteration, response)

Returns:
  • (Numeric) -

Other tags:
    Api: - private

Parameters:
  • response (HTTP::Response, nil) --
  • iteration (Integer) --
def calculate_delay(iteration, response)
  @delay_calculator.call(iteration, response)
end

def finish_attempt(client, err)

Returns:
  • (void) -

Other tags:
    Api: - private
def finish_attempt(client, err)
  client.close
  raise err
end

def initialize(tries: 5, delay: nil, exceptions: RETRIABLE_ERRORS, retry_statuses: nil,

Returns:
  • (HTTP::Retriable::Performer) -

Other tags:
    Api: - private

Parameters:
  • should_retry (#call, nil) -- custom retry predicate
  • max_delay (#to_f) -- maximum delay between retries
  • on_retry (#call) -- callback invoked on each retry
  • retry_statuses (Array<#to_i>, nil) -- status codes to retry
  • exceptions (Array) -- exception classes to retry
  • delay (#call, #to_i, nil) -- delay between retries
  • tries (#to_i) -- maximum number of attempts
def initialize(tries: 5, delay: nil, exceptions: RETRIABLE_ERRORS, retry_statuses: nil,
               on_retry: ->(*_args) {}, max_delay: Float::MAX, should_retry: nil)
  @exception_classes = exceptions
  @retry_statuses = retry_statuses
  @tries = tries.to_i
  @on_retry = on_retry
  @should_retry_proc = should_retry
  @delay_calculator = DelayCalculator.new(delay: delay, max_delay: max_delay)
end

def out_of_retries_error(request, response, exception)

Returns:
  • (HTTP::OutOfRetriesError) -

Other tags:
    Api: - private

Parameters:
  • exception (Exception, nil) --
  • response (HTTP::Response, nil) --
  • request (HTTP::Request) --
def out_of_retries_error(request, response, exception)
  message = format("%s <%s> failed", String(request.verb).upcase, request.uri)
  message += " with #{response.status}" if response
  message += ":#{exception}" if exception
  OutOfRetriesError.new(message).tap do |ex|
    ex.cause = exception
    ex.response = response
  end
end

def perform(client, req, &block)

Other tags:
    Api: - private

Returns:
  • (HTTP::Response) -

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)
      retry_attempt(client, req, err, res, attempt)
    elsif err
      finish_attempt(client, err)
    elsif res
      return res
    end
  end
end

def retry_attempt(client, req, err, res, attempt)

Returns:
  • (void) -

Other tags:
    Api: - private
def retry_attempt(client, req, err, res, attempt)
  # 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, effectively closing underlying socket
  # and resetting client's state.
  wait_for_retry_or_raise(req, err, res, attempt)
ensure
  client.close
end

def retry_exception?(err)

Returns:
  • (Boolean) -

Other tags:
    Api: - private
def retry_exception?(err)
  @exception_classes.any? { |e| err.is_a?(e) }
end

def retry_request?(req, err, res, attempt)

Returns:
  • (Boolean) -

Other tags:
    Api: - private
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)

Returns:
  • (Boolean) -

Other tags:
    Api: - private
def retry_response?(res)
  return false unless @retry_statuses
  response_status = Integer(res.status)
  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

Returns:
  • (Array) -

Other tags:
    Api: - private
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)

Returns:
  • (void) -

Other tags:
    Api: - private
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