module Retry
Support for performing retriable operations
Public Class Methods
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
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
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
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
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
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
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