module Retry

Support for performing retriable operations

Public Class Methods

do(delay: nil, exceptions: nil, handlers: nil, tries: nil) { || ... } click to toggle source

Executes the code block until it returns successfully, throws a non-retriable exception or some termination condition is met. @param delay [Float] the number of seconds to wait before retrying.

Positive values specify an exact delay, negative values specify a
random delay no longer than this value.

@param exceptions [Hash<Exception, Boolean>] the hash of retriable

exceptions

@param handlers [Hash<Exception|Symbol, Proc>] handlers to be invoked

when specific exceptions occur. A handler should accept the exception
and the number of tries remaining as arguments. It does not need to
re-raise its exception argument, but it may throw another exception
to prevent a retry.

@param tries [Integer] the maximum number of tries @return [Object] the return value of the block

# File lib/retry.rb, line 76
def self.do(delay: nil, exceptions: nil, handlers: nil, tries: nil)
  yield if block_given?
rescue StandardError => exception
  # Decrement the tries-remaining count if appropriate
  tries -= 1 if tries.is_a?(Numeric)
  # Handlers may raise StopRetry to force a return value from the method
  # Check if the exception is retriable
  retriable = retry?(exception, exceptions, tries)
  begin
    # Run the exception handler
    # - this will re-raise the exception if it is not retriable
    handle_exception(exception, handlers, tries, retriable)
    # Run the retry handler and retry
    handle_retry(exception, handlers, tries, retriable, delay)
    retry
  rescue StopRetry => exception
    # Force a return value from the handler
    exception.value
  end
end

Private Class Methods

handle_exception(exception, handlers, tries, retriable) click to toggle source

Executes a handler for an exception @param exception [Exception] the exception @param handlers [Hash<Exception|Symbol, Proc>] the exception handlers @param tries [Integer] the number of tries remaining @param retriable [Boolean] true if the exception is retriable, false if not @return [Object] the return value of the handler, or nil if no handler

was executed
# File lib/retry.rb, line 104
def self.handle_exception(exception, handlers, tries, retriable)
  # Execute the general exception handler
  handler(exception, handlers, tries, retriable, :all)
  # Execute the exception-specific handler
  handler(exception, handlers, tries, retriable)
  # Re-raise the exception if not retriable
  raise exception unless retriable
end
handle_retry(exception, handlers, tries, retriable, delay) click to toggle source

Executes the retry handler @param exception [Exception] the exception @param handlers [Hash<Exception|Symbol, Proc>] the exception handlers @param tries [Integer] the number of tries remaining @param retriable [Boolean] true if the exception is retriable, false if not @param delay [Float] the number of seconds to wait before retrying

# File lib/retry.rb, line 119
def self.handle_retry(exception, handlers, tries, retriable, delay)
  # Wait for the specified delay
  wait(delay) unless delay.zero?
  # Return the result of the retry handler
  handler(exception, handlers, tries, retriable, :retry)
end
handler(exception, handlers, tries, retriable, name = nil) click to toggle source

Executes the specified handler @param exception [Exception] the exception @param handlers [Hash<Exception|Symbol, Proc>] the exception handlers @param tries [Integer] the number of tries remaining @param retriable [Boolean] true if the exception is retriable, false if not @param name [Symbol] the handler name, defaults to the exception class @return [Object] the return value of the handler, or nil if no handler

was executed
# File lib/retry.rb, line 134
def self.handler(exception, handlers, tries, retriable, name = nil)
  handler = nil
  if name.nil?
    # Find the handler for the exception class
    handlers.each do |e, h|
      next unless e.is_a?(Class) && exception.is_a?(e)
      handler = h
      break
    end
    # Use the default handler if no match was found
    handler ||= handlers[:default]
  else
    # Use the named handler, do not use the default if not found
    handler = handlers[name]
  end
  handler ? handler.call(exception, tries, retriable) : nil
end
retry?(exception, exceptions, tries) click to toggle source

Returns true if the exception instance is retriable, false if not @param exception [Exception] the exception instance @param tries [Integer, Proc] the number of tries remaining or a proc

determining whether tries remain

@return [Boolean] true if the exception is retriable, false if not

# File lib/retry.rb, line 157
def self.retry?(exception, exceptions, tries)
  # Return false if there are no more tries remaining
  return false unless tries_remain?(exception, tries)
  # Return true if the exception matches a retriable exception class
  exceptions.each { |e| return true if exception.is_a?(e) }
  # The exception didn't match any retriable classes
  false
end
tries_remain?(exception, tries) click to toggle source

Returns true if there are tries remaining @param exception [Exception] the exception instance @param tries [Integer, Proc] the number of tries remaining or a proc

determining whether tries remain
# File lib/retry.rb, line 170
def self.tries_remain?(exception, tries)
  # If tries is numeric, this is the number of tries remaining
  return false if tries.is_a?(Numeric) && tries.zero?
  # If tries has a #call method, this should return true to allow a retry or
  # false to raise the exception
  return false if tries.respond_to?(:call) && !tries.call(exception)
  # Otherwise allow a retry
  true
end
wait(delay) click to toggle source

Waits for the specified number of seconds. If delay is positive, sleep for that period. If delay is negative, sleep for a random time up to that duration. @param delay [Float] the number of seconds to wait before retrying @return [void]

# File lib/retry.rb, line 185
def self.wait(delay)
  sleep(delay > 0 ? delay : Random.rand(-delay))
end