lib/active_job/retry.rb in activejob-retry-0.5.1 vs lib/active_job/retry.rb in activejob-retry-0.6.0

- old
+ new

@@ -9,99 +9,109 @@ unless ActiveJob::Base.method_defined?(:deserialize) require 'active_job/retry/deserialize_monkey_patch' end +def choose_strategy(strategy, options) + case strategy + when :constant then ActiveJob::Retry::ConstantBackoffStrategy.new(options) + when :variable then ActiveJob::Retry::VariableBackoffStrategy.new(options) + when :exponential then ActiveJob::Retry::ExponentialBackoffStrategy.new(options) + else strategy + end +end + module ActiveJob - module Retry + class Retry < Module PROBLEMATIC_ADAPTERS = [ 'ActiveJob::QueueAdapters::InlineAdapter', 'ActiveJob::QueueAdapters::QuAdapter', 'ActiveJob::QueueAdapters::SneakersAdapter', 'ActiveJob::QueueAdapters::SuckerPunchAdapter' ].freeze - def self.included(base) - if PROBLEMATIC_ADAPTERS.include?(ActiveJob::Base.queue_adapter.name) - warn("#{ActiveJob::Base.queue_adapter.name} does not support delayed retries, " \ - 'so does not work with ActiveJob::Retry. You may experience strange ' \ - 'behaviour.') - end - - base.extend(ClassMethods) - end - ################# # Configuration # ################# + def initialize(strategy: nil, **options) + check_adapter! + @backoff_strategy = choose_strategy(strategy, options) - module ClassMethods - attr_reader :backoff_strategy - - def constant_retry(options) - retry_with(ConstantBackoffStrategy.new(options)) + unless backoff_strategy_valid? + raise InvalidConfigurationError, + 'Backoff strategies must define `should_retry?(attempt, exception)`, ' \ + 'and `retry_delay(attempt, exception)`.' end + end - def variable_retry(options) - retry_with(VariableBackoffStrategy.new(options)) - end + def included(base) + define_backoff_strategy(base) + define_retry_attempt_tracking(base) + define_retry_method(base) + define_retry_logic(base) + end - def exponential_retry(options) - retry_with(ExponentialBackoffStrategy.new(options)) - end + private - def retry_with(backoff_strategy) - unless backoff_strategy_valid?(backoff_strategy) - raise InvalidConfigurationError, - 'Backoff strategies must define `should_retry?(attempt, exception)`, ' \ - 'and `retry_delay(attempt, exception)`.' - end + attr_reader :backoff_strategy - @backoff_strategy = backoff_strategy - end + def define_backoff_strategy(klass) + klass.instance_variable_set(:@backoff_strategy, @backoff_strategy) + klass.define_singleton_method(:backoff_strategy) { @backoff_strategy } + end - def backoff_strategy_valid?(backoff_strategy) - backoff_strategy.respond_to?(:should_retry?) && - backoff_strategy.respond_to?(:retry_delay) && - backoff_strategy.method(:should_retry?).arity == 2 && - backoff_strategy.method(:retry_delay).arity == 2 + def define_retry_attempt_tracking(klass) + klass.instance_eval do + define_method(:serialize) do |*args| + super(*args).merge('retry_attempt' => retry_attempt) + end + define_method :deserialize do |job_data| + super(job_data) + @retry_attempt = job_data['retry_attempt'] + end + define_method(:retry_attempt) { @retry_attempt ||= 1 } end end - ############################# - # Storage of attempt number # - ############################# - - def serialize - super.merge('retry_attempt' => retry_attempt) + def define_retry_method(klass) + klass.instance_eval do + define_method :internal_retry do |exception| + this_delay = self.class.backoff_strategy.retry_delay(retry_attempt, exception) + # TODO: This breaks DelayedJob and Resque for some weird ActiveSupport reason. + # logger.info("Retrying (attempt #{retry_attempt + 1}, waiting #{this_delay}s)") + @retry_attempt += 1 + retry_job(wait: this_delay) + end + end end - def deserialize(job_data) - super(job_data) - @retry_attempt = job_data['retry_attempt'] + def define_retry_logic(klass) + klass.instance_eval do + # Override `rescue_with_handler` to make sure our catch is before callbacks, + # so `rescue_from`s will only be run after any retry attempts have been exhausted. + define_method :rescue_with_handler do |exception| + if self.class.backoff_strategy.should_retry?(retry_attempt, exception) + internal_retry(exception) + return true # Exception has been handled + else + return super(exception) + end + end + end end - def retry_attempt - @retry_attempt ||= 1 + def check_adapter! + if PROBLEMATIC_ADAPTERS.include?(ActiveJob::Base.queue_adapter.name) + warn("#{ActiveJob::Base.queue_adapter.name} does not support delayed retries, " \ + 'so does not work with ActiveJob::Retry. You may experience strange ' \ + 'behaviour.') + end end - ########################## - # Performing the retries # - ########################## - - # Override `rescue_with_handler` to make sure our catch is before callbacks, - # so `rescue_from`s will only be run after any retry attempts have been exhausted. - def rescue_with_handler(exception) - unless self.class.backoff_strategy.should_retry?(retry_attempt, exception) - return super - end - - this_delay = self.class.backoff_strategy.retry_delay(retry_attempt, exception) - # TODO: This breaks DelayedJob and Resque for some weird ActiveSupport reason. - # logger.info("Retrying (attempt #{retry_attempt + 1}, waiting #{this_delay}s)") - @retry_attempt += 1 - retry_job(wait: this_delay) - - true # Exception has been handled + def backoff_strategy_valid? + backoff_strategy.respond_to?(:should_retry?) && + backoff_strategy.respond_to?(:retry_delay) && + backoff_strategy.method(:should_retry?).arity == 2 && + backoff_strategy.method(:retry_delay).arity == 2 end end end