lib/resque/plugins/retry.rb in resque-retry-1.4.0 vs lib/resque/plugins/retry.rb in resque-retry-1.5.0

- old
+ new

@@ -50,16 +50,19 @@ if receiver.instance_variable_get('@fatal_exceptions') && receiver.instance_variable_get('@retry_exceptions') raise AmbiguousRetryStrategyException.new(%{You can't define both "@fatal_exceptions" and "@retry_exceptions"}) end end - # Copy retry criteria checks on inheritance. + # Copy retry criteria checks, try again callbacks, and give up callbacks + # on inheritance. # # @api private def inherited(subclass) super(subclass) subclass.instance_variable_set('@retry_criteria_checks', retry_criteria_checks.dup) + subclass.instance_variable_set('@try_again_callbacks', try_again_callbacks.dup) + subclass.instance_variable_set('@give_up_callbacks', give_up_callbacks.dup) end # @abstract You may override to implement a custom retry identifier, # you should consider doing this if your job arguments # are many/long or may not cleanly convert to strings. @@ -269,15 +272,13 @@ log_message "Exception is #{retry_based_on_exception ? '' : 'not '}sufficient for a retry", args, exception retry_based_on_criteria = false unless retry_based_on_exception # call user retry criteria check blocks. - retry_criteria_checks.each do |criteria_check| - retry_based_on_criteria ||= !!instance_exec(exception, *args, &criteria_check) - end + retry_based_on_criteria = retry_criteria_checks_pass?(exception, *args) + log_message "user retry criteria is #{retry_based_on_criteria ? '' : 'not '}sufficient for a retry", args, exception end - log_message "user retry criteria is #{retry_based_on_criteria ? '' : 'not '}sufficient for a retry", args, exception retry_based_on_exception || retry_based_on_criteria end # Retry criteria checks @@ -303,40 +304,64 @@ false end end # Register a retry criteria check callback to be run before retrying - # the job again + # the job again. Can be registered with a block or a symbol. # # If any callback returns `true`, the job will be retried. # - # @example Using a custom retry criteria check. + # @example Registering a custom retry criteria check. # # retry_criteria_check do |exception, *args| # if exception.message =~ /InvalidJobId/ # # don't retry if we got passed a invalid job id. # false # else # true # end # end # + # @example + # + # retry_criteria_check :my_check + # + # @param [Symbol?] method # @yield [exception, *args] # @yieldparam exception [Exception] the exception that was raised # @yieldparam args [Array] job arguments - # @yieldreturn [Boolean] false == dont retry, true = can retry + # @yieldreturn [Boolean] false == dont retry, true == can retry # # @api public - def retry_criteria_check(&block) - retry_criteria_checks << block + def retry_criteria_check(method=nil, &block) + if method.is_a? Symbol + retry_criteria_checks << method + elsif block_given? + retry_criteria_checks << block + end end + # Returns true if *any* of the retry criteria checks pass. When a retry + # criteria check passes, the remaining ones are not executed. + # + # @returns [Boolean] whether any of the retry criteria checks pass + # + # @api private + def retry_criteria_checks_pass?(exception, *args) + retry_criteria_checks.each do |criteria_check| + return true if !!call_symbol_or_block(criteria_check, exception, *args) + end + false + end + # Retries the job # # @api private def try_again(exception, *args) log_message 'try_again', args, exception + run_try_again_callbacks(exception, *args) + # some plugins define retry_delay and have it take no arguments, so rather than break those, # we'll just check here to see whether it takes the additional exception class argument or not temp_retry_delay = ([-1, 1].include?(method(:retry_delay).arity) ? retry_delay(exception.class) : retry_delay) retry_in_queue = retry_job_delegate ? retry_job_delegate : self @@ -363,10 +388,19 @@ # sleep after requeue if enabled. sleep(sleep_after_requeue) if sleep_after_requeue > 0 end + # We failed and we're not retrying. + # + # @api private + def give_up(exception, *args) + log_message 'retry criteria not sufficient for retry', args, exception + run_give_up_callbacks(exception, *args) + clean_retry_key(*args) + end + # Resque before_perform hook # # Increments `@retry_attempt` count and updates the "retry_key" expiration # time (if applicable) # @@ -420,12 +454,11 @@ end if retry_criteria_valid?(exception, *args) try_again(exception, *args) else - log_message 'retry criteria not sufficient for retry', args, exception - clean_retry_key(*args) + give_up(exception, *args) end @on_failure_retry_hook_already_called = true end @@ -449,9 +482,124 @@ # # @api private def clean_retry_key(*args) log_message 'clean_retry_key', args Resque.redis.del(redis_retry_key(*args)) + end + + # Returns the try again callbacks. + # + # @return [Array<Proc>] + # + # @api public + def try_again_callbacks + @try_again_callbacks ||= [] + end + + # Register a try again callback that will be called when the job fails + # but is trying again. Can be registered with a block or a symbol. + # + # @example Registering a callback with a block + # + # try_again_callback do |exception, *args| + # logger.error( + # "Resque job received exception #{exception} and is trying again") + # end + # + # @example Registering a callback with a Symbol + # + # try_again_callback :my_callback + # + # @param [Symbol?] method + # @yield [exception, *args] + # @yieldparam exception [Exception] the exception that was raised + # @yieldparam args [Array] job arguments + # + # @api public + def try_again_callback(method=nil, &block) + if method.is_a? Symbol + try_again_callbacks << method + elsif block_given? + try_again_callbacks << block + end + end + + # Runs all the try again callbacks. + # + # @param exception [Exception] + # @param args [Object...] + # + # @api private + def run_try_again_callbacks(exception, *args) + try_again_callbacks.each do |callback| + call_symbol_or_block(callback, exception, *args) + end + end + + # Returns the give up callbacks. + # + # @return [Array<Proc>] + # + # @api public + def give_up_callbacks + @give_up_callbacks ||= [] + end + + # Register a give up callback that will be called when the job fails + # and is not retrying. Can be registered with a block or a symbol. + # + # @example Registering a callback with a block + # + # give_up_callback do |exception, *args| + # logger.error( + # "Resque job received exception #{exception} and is giving up") + # end + # + # @example Registering a callback with a Symbol + # + # give_up_callback :my_callback + # + # @param [Symbol?] method + # @yield [exception, *args] + # @yieldparam exception [Exception] the exception that was raised + # @yieldparam args [Array] job arguments + # + # @api public + def give_up_callback(method=nil, &block) + if method.is_a? Symbol + give_up_callbacks << method + elsif block_given? + give_up_callbacks << block + end + end + + # Runs all the give up callbacks. + # + # @param exception [Exception] + # @param args [Object...] + # + # @api private + def run_give_up_callbacks(exception, *args) + give_up_callbacks.each do |callback| + call_symbol_or_block(callback, exception, *args) + end + end + + # Helper to call functions that may be passed as Symbols or Procs. If + # a symbol, it is assumed to refer to a method that is already defined + # on this class. + # + # @param [Symbol|Proc] method + # @param [Object...] *args + # @return [Object] + # + # @api private + def call_symbol_or_block(method, *args) + if method.is_a?(Symbol) + send(method, *args) + elsif method.respond_to?(:call) + instance_exec(*args, &method) + end end end end end