lib/take2.rb in take2-0.0.4 vs lib/take2.rb in take2-0.0.5

- old
+ new

@@ -1,23 +1,23 @@ -require 'net/http' +# frozen_string_literal: true +require 'net/http' require 'take2/version' require 'take2/configuration' module Take2 - def self.included(base) - base.extend ClassMethods - base.send :set_defaults - base.send :include, InstanceMethods + base.extend(ClassMethods) + base.send(:set_defaults) + base.send(:include, InstanceMethods) end class << self attr_accessor :configuration end - def self.configuration + def self.config @configuration ||= Configuration.new end def self.reset(options = {}) @configuration = Configuration.new(options) @@ -26,56 +26,72 @@ def self.local_defaults(options) configuration.validate_options(options) end def self.configure - yield(configuration) if block_given? + yield(config) if block_given? end module InstanceMethods - # Yields a block and retries on retriable errors n times. # The raised error could be the defined retriable or it child. # # Example: # class PizzaService # include Take2 # # number_of_retries 3 # retriable_errors Net::HTTPRetriableError # retriable_condition proc { |error| response_status(error.response) < 500 } - # on_retry proc { |error, tries| puts "#{self.name} - Retrying.. #{tries} of #{self.retriable_configuration[:retries]} (#{error})" } - # sleep_before_retry 3 + # on_retry proc { |error, tries| + # puts "#{self.name} - Retrying.. #{tries} of #{self.retriable_configuration[:retries]} (#{error})" + # } + # backoff_strategy type: :exponential, start: 3 # # def give_me_food # call_api_with_retry do # # Some logic that might raise.. # # If it will raise retriable, magic happens. # # If not the original error re raised # end # end # # end - def call_api_with_retry(options = {}) + def call_api_with_retry(options = {}) config = self.class.retriable_configuration - config.merge! Take2.local_defaults(options) unless options.empty? + config.merge!(Take2.local_defaults(options)) unless options.empty? tries ||= config[:retries] begin yield rescue => e - if config[:retriable].map {|klass| e.class <= klass }.any? + if config[:retriable].map { |klass| e.class <= klass }.any? unless tries.zero? || config[:retry_condition_proc]&.call(e) config[:retry_proc]&.call(e, tries) - sleep(config[:time_to_sleep]) if config[:time_to_sleep] + rest(config, tries) tries -= 1 retry end - end + end raise e end end - + alias_method :with_retry, :call_api_with_retry + + private + + def rest(config, tries) + seconds = if config[:time_to_sleep].to_f > 0 + config[:time_to_sleep].to_f + else + next_interval(config[:backoff_intervals], config[:retries], tries) + end + sleep(seconds) + end + + def next_interval(intervals, retries, current) + intervals[retries - current] + end end module ClassMethods # Sets number of retries. # @@ -99,15 +115,16 @@ # retriable_errors Net::HTTPRetriableError, Errno::ECONNRESET # end # Arguments: # errors: List of retiable errors def retriable_errors(*errors) - raise ArgumentError, 'All retriable errors must be StandardError decendants' unless errors.all? { |e| e <= StandardError } + message = 'All retriable errors must be StandardError decendants' + raise ArgumentError, message unless errors.all? { |e| e <= StandardError } self.retriable = errors end - # Sets condition for retry attempt. + # Sets condition for retry attempt. # If set, it MUST result to +false+ with number left retries greater that zero in order to retry. # # Example: # class PizzaService # include Take2 @@ -118,11 +135,11 @@ def retriable_condition(proc) raise ArgumentError, 'Must be callable' unless proc.respond_to?(:call) self.retry_condition_proc = proc end - # Defines a proc that is called *before* retry attempt. + # Defines a proc that is called *before* retry attempt. # # Example: # class PizzaService # include Take2 # on_retry proc { |error, tries| puts "Retrying.. #{tries} of #{self.class.retriable_configuration[:retries]}" } @@ -132,22 +149,32 @@ def on_retry(proc) raise ArgumentError, 'Must be callable' unless proc.respond_to?(:call) self.retry_proc = proc end - # Sets number of seconds to sleep before next retry. + def sleep_before_retry(seconds) + unless (seconds.is_a?(Integer) || seconds.is_a?(Float)) && seconds.positive? + raise ArgumentError, 'Must be positive numer' + end + puts "DEPRECATION MESSAGE - The sleep_before_retry method is softly deprecated in favor of backoff_stategy \r + where the time to sleep is a starting point on the backoff intervals. Please implement it instead." + self.time_to_sleep = seconds + end + + # Sets the backoff strategy # # Example: # class PizzaService # include Take2 - # sleep_before_retry 1.5 + # backoff_strategy type: :exponential, start: 3 # end # Arguments: - # seconds: number - def sleep_before_retry(seconds) - raise ArgumentError, 'Must be positive numer' unless (seconds.is_a?(Integer) || seconds.is_a?(Float)) && seconds.positive? - self.time_to_sleep = seconds + # hash: object + def backoff_strategy(options) + available_types = [:constant, :linear, :fibonacci, :exponential] + raise ArgumentError, 'Incorrect backoff type' unless available_types.include?(options[:type]) + self.backoff_intervals = Backoff.new(options[:type], options[:start]).intervals end # Exposes current class configuration def retriable_configuration Take2::Configuration::CONFIG_ATTRS.each_with_object({}) do |key, hash| @@ -165,12 +192,10 @@ instance_variable_set("@#{attr}", config[attr]) end end def response_status(response) - return response.status if response.respond_to? :status - response.status_code if response.respond_to? :status_code + return response.status if response.respond_to?(:status) + response.status_code if response.respond_to?(:status_code) end - end - -end \ No newline at end of file +end