Class: HTTP::Retriable::Performer Private

Inherits:
Object
  • Object
show all
Defined in:
lib/http/retriable/performer.rb,
sig/http.rbs

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Request performing watchdog.

Constant Summary collapse

RETRIABLE_ERRORS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Exceptions we should retry

Returns:

  • (Array[singleton(Exception)])
[
  HTTP::TimeoutError,
  HTTP::ConnectionError,
  IO::EAGAINWaitReadable,
  Errno::ECONNRESET,
  Errno::ECONNREFUSED,
  Errno::EHOSTUNREACH,
  OpenSSL::SSL::SSLError,
  EOFError,
  IOError
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(tries: 5, delay: nil, exceptions: RETRIABLE_ERRORS, retry_statuses: nil, on_retry: ->(*_args) {}, max_delay: Float::MAX, should_retry: nil) ⇒ HTTP::Retriable::Performer

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.

Create a new retry performer

Parameters:

  • tries (#to_i) (defaults to: 5)

    maximum number of attempts

  • delay (#call, #to_i, nil) (defaults to: nil)

    delay between retries

  • exceptions (Array<Exception>) (defaults to: RETRIABLE_ERRORS)

    exception classes to retry

  • retry_statuses (Array<#to_i>, nil) (defaults to: nil)

    status codes to retry

  • on_retry (#call) (defaults to: ->(*_args) {})

    callback invoked on each retry

  • max_delay (#to_f) (defaults to: Float::MAX)

    maximum delay between retries

  • should_retry (#call, nil) (defaults to: nil)

    custom retry predicate

  • tries: (Integer) (defaults to: 5)
  • delay: (Numeric, ^(Integer) -> Numeric, nil) (defaults to: nil)
  • exceptions: (Array[singleton(Exception)]) (defaults to: RETRIABLE_ERRORS)
  • retry_statuses: (Object) (defaults to: nil)
  • on_retry: (^(Request, Exception?, Response?) -> void) (defaults to: ->(*_args) {})
  • max_delay: (Numeric) (defaults to: Float::MAX)
  • should_retry: (^(Request, Exception?, Response?, Integer) -> bool, nil) (defaults to: nil)


38
39
40
41
42
43
44
45
46
# File 'lib/http/retriable/performer.rb', line 38

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

Instance Method Details

#calculate_delay(iteration, response) ⇒ Numeric

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.

Calculates delay between retries

Parameters:

Returns:

  • (Numeric)


73
74
75
# File 'lib/http/retriable/performer.rb', line 73

def calculate_delay(iteration, response)
  @delay_calculator.call(iteration, response)
end

#finish_attempt(client, err) ⇒ void

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.

This method returns an undefined value.

Closes client and raises the error

Parameters:

  • client (Client)
  • err (Exception)


99
100
101
102
# File 'lib/http/retriable/performer.rb', line 99

def finish_attempt(client, err)
  client.close
  raise err
end

#out_of_retries_error(request, response, exception) ⇒ HTTP::OutOfRetriesError

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.

Builds OutOfRetriesError

Parameters:

Returns:



184
185
186
187
188
189
190
191
192
193
194
# File 'lib/http/retriable/performer.rb', line 184

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

#perform(client, req) { ... } ⇒ HTTP::Response

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.

Execute request with retry logic

Parameters:

Yields:

Yield Returns:

Returns:

See Also:



53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/http/retriable/performer.rb', line 53

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

#retry_attempt(client, req, err, res, attempt) ⇒ void

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.

This method returns an undefined value.

Executes a single retry attempt

Parameters:



83
84
85
86
87
88
89
90
91
92
93
# File 'lib/http/retriable/performer.rb', line 83

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

#retry_exception?(err) ⇒ Boolean

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.

Checks whether the exception is retriable

Parameters:

  • err (Exception)

Returns:

  • (Boolean)


140
141
142
# File 'lib/http/retriable/performer.rb', line 140

def retry_exception?(err)
  @exception_classes.any? { |e| err.is_a?(e) }
end

#retry_request?(req, err, res, attempt) ⇒ Boolean

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.

Checks whether the request should be retried

Parameters:

Returns:

  • (Boolean)


126
127
128
129
130
131
132
133
134
# File 'lib/http/retriable/performer.rb', line 126

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

#retry_response?(res) ⇒ Boolean

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.

Checks whether the response status warrants retry

Parameters:

  • res (Object)

Returns:

  • (Boolean)


148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/http/retriable/performer.rb', line 148

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

#try_request { ... } ⇒ Array

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.

Attempts to execute the request block

rubocop:disable Lint/RescueException

Yields:

Yield Returns:

Returns:

  • (Array)


109
110
111
112
113
114
115
116
117
118
119
# File 'lib/http/retriable/performer.rb', line 109

def try_request
  err, res = nil

  begin
    res = yield
  rescue Exception => e
    err = e
  end

  [err, res]
end

#wait_for_retry_or_raise(req, err, res, attempt) ⇒ void

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.

This method returns an undefined value.

Waits for retry delay or raises if out of attempts

Parameters:



167
168
169
170
171
172
173
174
175
# File 'lib/http/retriable/performer.rb', line 167

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