diff --git a/lib/sidekiq/middleware/server/retry_jobs.rb b/lib/sidekiq/middleware/server/retry_jobs.rb index e40bd366..dc9ea984 100644 --- a/lib/sidekiq/middleware/server/retry_jobs.rb +++ b/lib/sidekiq/middleware/server/retry_jobs.rb @@ -20,6 +20,13 @@ module Sidekiq # # { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'] } # + # The 'retry' option also accepts a number (in place of 'true'): + # + # { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'], 'retry' => 5 } + # + # The job will be retried this number of times before giving up. (If simply + # 'true', Sidekiq retries 25 times) + # # We'll add a bit more data to the message to support retries: # # * 'queue' - the queue to use @@ -35,13 +42,14 @@ module Sidekiq include Sidekiq::Util # delayed_job uses the same basic formula - MAX_COUNT = 25 + DEFAULT_MAX_RETRY_ATTEMPTS = 25 DELAY = proc { |count| (count ** 4) + 15 } def call(worker, msg, queue) yield rescue Exception => e raise e unless msg['retry'] + max_retry_attempts = retry_attempts_from(msg['retry'], DEFAULT_MAX_RETRY_ATTEMPTS) msg['queue'] = queue msg['error_message'] = e.message @@ -60,7 +68,7 @@ module Sidekiq msg['error_backtrace'] = e.backtrace[0..msg['backtrace'].to_i] end - if count <= MAX_COUNT + if count <= max_retry_attempts delay = DELAY.call(count) logger.debug { "Failure! Retry #{count} in #{delay} seconds" } retry_at = Time.now.to_f + delay @@ -75,6 +83,14 @@ module Sidekiq raise e end + def retry_attempts_from(msg_retry, default) + if msg_retry.is_a?(Fixnum) + msg_retry + else + default + end + end + end end end diff --git a/test/test_retry.rb b/test/test_retry.rb index eb3cb65d..91bbd3e2 100644 --- a/test/test_retry.rb +++ b/test/test_retry.rb @@ -88,7 +88,25 @@ class TestRetry < MiniTest::Unit::TestCase @redis.verify end - it 'throws away old messages after too many retries' do + it 'handles a recurring failed message before reaching user-specifed max' do + @redis.expect :zadd, 1, ['retry', String, String] + now = Time.now.utc + msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], 'retry' => 11, "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>10} + handler = Sidekiq::Middleware::Server::RetryJobs.new + assert_raises RuntimeError do + handler.call('', msg, 'default') do + raise "kerblammo!" + end + end + assert_equal 'default', msg["queue"] + assert_equal 'kerblammo!', msg["error_message"] + assert_equal 'RuntimeError', msg["error_class"] + assert_equal 11, msg["retry_count"] + assert msg["failed_at"] + @redis.verify + end + + it 'throws away old messages after too many retries (using the default)' do now = Time.now.utc msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry"=>true, "retry_count"=>25} @redis.expect :zadd, 1, [ 'retry', String, String ] @@ -101,6 +119,20 @@ class TestRetry < MiniTest::Unit::TestCase # MiniTest can't assert that a method call did NOT happen!? assert_raises(MockExpectationError) { @redis.verify } end + + it 'throws away old messages after too many retries (using user-specified max)' do + now = Time.now.utc + msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry"=>3, "retry_count"=>3} + @redis.expect :zadd, 1, [ 'retry', String, String ] + handler = Sidekiq::Middleware::Server::RetryJobs.new + assert_raises RuntimeError do + handler.call('', msg, 'default') do + raise "kerblammo!" + end + end + # MiniTest can't assert that a method call did NOT happen!? + assert_raises(MockExpectationError) { @redis.verify } + end end describe 'poller' do