Module: Retry

Defined in:
lib/retry.rb

Overview

Support for performing retriable operations

Defined Under Namespace

Modules: Exceptions Classes: Engine, StopRetry

Class Method Summary collapse

Class Method Details

.do(delay: nil, exceptions: nil, handlers: nil, tries: nil) ⇒ Object

Executes the code block until it returns successfully, throws a non-retriable exception or some termination condition is met.

Parameters:

  • delay (Float) (defaults to: nil)

    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.

  • exceptions (Hash<Exception, Boolean>) (defaults to: nil)

    the hash of retriable exceptions

  • handlers (Hash<Exception|Symbol, Proc>) (defaults to: nil)

    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.

  • tries (Integer) (defaults to: nil)

    the maximum number of tries

Returns:

  • (Object)

    the return value of the block



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# 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

.handle_exception(exception, handlers, tries, retriable) ⇒ Object

Executes a handler for an exception

Parameters:

  • exception (Exception)

    the exception

  • handlers (Hash<Exception|Symbol, Proc>)

    the exception handlers

  • tries (Integer)

    the number of tries remaining

  • retriable (Boolean)

    true if the exception is retriable, false if not

Returns:

  • (Object)

    the return value of the handler, or nil if no handler was executed



104
105
106
107
108
109
110
111
# 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) ⇒ Object

Executes the retry handler

Parameters:

  • exception (Exception)

    the exception

  • handlers (Hash<Exception|Symbol, Proc>)

    the exception handlers

  • tries (Integer)

    the number of tries remaining

  • retriable (Boolean)

    true if the exception is retriable, false if not

  • delay (Float)

    the number of seconds to wait before retrying



119
120
121
122
123
124
# 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) ⇒ Object

Executes the specified handler

Parameters:

  • exception (Exception)

    the exception

  • handlers (Hash<Exception|Symbol, Proc>)

    the exception handlers

  • tries (Integer)

    the number of tries remaining

  • retriable (Boolean)

    true if the exception is retriable, false if not

  • name (Symbol) (defaults to: nil)

    the handler name, defaults to the exception class

Returns:

  • (Object)

    the return value of the handler, or nil if no handler was executed



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# 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) ⇒ Boolean

Returns true if the exception instance is retriable, false if not

Parameters:

  • exception (Exception)

    the exception instance

  • tries (Integer, Proc)

    the number of tries remaining or a proc determining whether tries remain

Returns:

  • (Boolean)

    true if the exception is retriable, false if not



157
158
159
160
161
162
163
164
# 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) ⇒ Boolean

Returns true if there are tries remaining

Parameters:

  • exception (Exception)

    the exception instance

  • tries (Integer, Proc)

    the number of tries remaining or a proc determining whether tries remain

Returns:

  • (Boolean)


170
171
172
173
174
175
176
177
178
# 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) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • delay (Float)

    the number of seconds to wait before retrying



185
186
187
# File 'lib/retry.rb', line 185

def self.wait(delay)
  sleep(delay > 0 ? delay : Random.rand(-delay))
end