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