Module: Kernel

Defined in:
lib/retries.rb

Instance Method Summary collapse

Instance Method Details

#with_retries(options = {}) {|attempt_number| ... } ⇒ Object

Runs the supplied code block an retries with an exponential backoff.

Parameters:

  • options (Hash) (defaults to: {})

    the retry options.

Options Hash (options):

  • :max_tries (Fixnum) — default: 3

    The maximum number of times to run the block.

  • :base_sleep_seconds (Float) — default: 0.5

    The starting delay between retries.

  • :max_sleep_seconds (Float) — default: 1.0

    The maximum to which to expand the delay between retries.

  • :handler (Proc) — default: nil

    If not nil, a Proc that will be called for each retry. It will be passed three arguments, exception (the rescued exception), attempt_number, and total_delay (seconds since start of first attempt).

  • :rescue (Exception, <Exception>) — default: StandardError

    This may be a specific exception class to rescue or an array of classes.

Yields:

  • (attempt_number)

    The (required) block to be executed, which is passed the attempt number as a parameter.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/retries.rb', line 27

def with_retries(options = {}, &block)
  # Check the options and set defaults
  options_error_string = "Error with options to with_retries:"
  max_tries = options[:max_tries] || 3
  raise "#{options_error_string} :max_tries must be greater than 0." unless max_tries > 0
  base_sleep_seconds = options[:base_sleep_seconds] || 0.5
  max_sleep_seconds = options[:max_sleep_seconds] || 1.0
  if base_sleep_seconds > max_sleep_seconds
    raise "#{options_error_string} :base_sleep_seconds cannot be greater than :max_sleep_seconds."
  end
  handler = options[:handler]
  exception_types_to_rescue = Array(options[:rescue] || StandardError)
  raise "#{options_error_string} with_retries must be passed a block" unless block_given?

  # Let's do this thing
  attempts = 0
  start_time = Time.now
  begin
    attempts += 1
    return block.call(attempts)
  rescue *exception_types_to_rescue => exception
    raise exception if attempts >= max_tries
    handler.call(exception, attempts, Time.now - start_time) if handler
    # Don't sleep at all if sleeping is disabled (used in testing).
    if Retries.sleep_enabled
      # The sleep time is an exponentially-increasing function of base_sleep_seconds. But, it never exceeds
      # max_sleep_seconds.
      sleep_seconds = [base_sleep_seconds * (2 ** (attempts - 1)), max_sleep_seconds].min
      # Randomize to a random value in the range sleep_seconds/2 .. sleep_seconds
      sleep_seconds = sleep_seconds * (0.5 * (1 + rand()))
      # But never sleep less than base_sleep_seconds
      sleep_seconds = [base_sleep_seconds, sleep_seconds].max
      sleep sleep_seconds
    end
    retry
  end
end