2012-03-17 21:22:56 -04:00
|
|
|
require 'helper'
|
2012-05-25 23:21:42 -04:00
|
|
|
require 'sidekiq/scheduled'
|
2012-03-17 21:22:56 -04:00
|
|
|
require 'sidekiq/middleware/server/retry_jobs'
|
|
|
|
|
2013-05-12 17:25:30 -04:00
|
|
|
class TestRetry < Minitest::Test
|
2012-03-17 21:22:56 -04:00
|
|
|
describe 'middleware' do
|
|
|
|
before do
|
2013-05-12 17:25:30 -04:00
|
|
|
@redis = Minitest::Mock.new
|
2012-03-17 21:22:56 -04:00
|
|
|
# Ugh, this is terrible.
|
|
|
|
Sidekiq.instance_variable_set(:@redis, @redis)
|
|
|
|
|
|
|
|
def @redis.with; yield self; end
|
|
|
|
end
|
|
|
|
|
2013-06-25 11:07:45 -04:00
|
|
|
let(:worker) do
|
|
|
|
Class.new do
|
|
|
|
include ::Sidekiq::Worker
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-04-01 22:53:45 -04:00
|
|
|
it 'allows disabling retry' do
|
|
|
|
msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => false }
|
|
|
|
msg2 = msg.dup
|
|
|
|
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
|
|
|
assert_raises RuntimeError do
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg2, 'default') do
|
2012-04-01 22:53:45 -04:00
|
|
|
raise "kerblammo!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
assert_equal msg, msg2
|
|
|
|
end
|
|
|
|
|
2012-11-10 00:18:02 -05:00
|
|
|
it 'allows a numeric retry' do
|
|
|
|
@redis.expect :zadd, 1, ['retry', String, String]
|
|
|
|
msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => 2 }
|
|
|
|
msg2 = msg.dup
|
|
|
|
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
|
|
|
assert_raises RuntimeError do
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg2, 'default') do
|
2012-11-10 00:18:02 -05:00
|
|
|
raise "kerblammo!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
msg2.delete('failed_at')
|
|
|
|
assert_equal({"class"=>"Bob", "args"=>[1, 2, "foo"], "retry"=>2, "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "retry_count"=>0}, msg2)
|
|
|
|
@redis.verify
|
|
|
|
end
|
|
|
|
|
2012-04-27 23:25:46 -04:00
|
|
|
it 'saves backtraces' do
|
|
|
|
@redis.expect :zadd, 1, ['retry', String, String]
|
|
|
|
msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'backtrace' => true }
|
|
|
|
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
|
|
|
c = nil
|
|
|
|
assert_raises RuntimeError do
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg, 'default') do
|
2012-04-27 23:25:46 -04:00
|
|
|
c = caller(0); raise "kerblammo!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
assert msg["error_backtrace"]
|
2012-11-01 19:08:27 -04:00
|
|
|
assert_equal c[0], msg["error_backtrace"][0]
|
2012-11-10 00:18:02 -05:00
|
|
|
@redis.verify
|
2012-04-27 23:25:46 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'saves partial backtraces' do
|
|
|
|
@redis.expect :zadd, 1, ['retry', String, String]
|
|
|
|
msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'backtrace' => 3 }
|
|
|
|
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
|
|
|
c = nil
|
|
|
|
assert_raises RuntimeError do
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg, 'default') do
|
2012-04-27 23:25:46 -04:00
|
|
|
c = caller(0)[0..3]; raise "kerblammo!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
assert msg["error_backtrace"]
|
|
|
|
assert_equal c, msg["error_backtrace"]
|
|
|
|
end
|
|
|
|
|
2012-03-17 21:22:56 -04:00
|
|
|
it 'handles a new failed message' do
|
2012-03-18 02:04:31 -04:00
|
|
|
@redis.expect :zadd, 1, ['retry', String, String]
|
2012-04-01 22:53:45 -04:00
|
|
|
msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }
|
2012-03-17 21:22:56 -04:00
|
|
|
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
|
|
|
assert_raises RuntimeError do
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg, 'default') do
|
2012-03-17 21:22:56 -04:00
|
|
|
raise "kerblammo!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
assert_equal 'default', msg["queue"]
|
|
|
|
assert_equal 'kerblammo!', msg["error_message"]
|
|
|
|
assert_equal 'RuntimeError', msg["error_class"]
|
|
|
|
assert_equal 0, msg["retry_count"]
|
2012-04-27 23:25:46 -04:00
|
|
|
refute msg["error_backtrace"]
|
2012-03-17 21:22:56 -04:00
|
|
|
assert msg["failed_at"]
|
|
|
|
@redis.verify
|
|
|
|
end
|
|
|
|
|
2013-01-15 20:28:52 -05:00
|
|
|
it 'allows a retry queue' do
|
|
|
|
@redis.expect :zadd, 1, ['retry', String, String]
|
|
|
|
msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'retry_queue' => 'retry' }
|
|
|
|
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
|
|
|
assert_raises RuntimeError do
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg, 'default') do
|
2013-01-15 20:28:52 -05:00
|
|
|
raise "kerblammo!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
assert_equal 'retry', msg["queue"]
|
|
|
|
assert_equal 'kerblammo!', msg["error_message"]
|
|
|
|
assert_equal 'RuntimeError', msg["error_class"]
|
|
|
|
assert_equal 0, msg["retry_count"]
|
|
|
|
refute msg["error_backtrace"]
|
|
|
|
assert msg["failed_at"]
|
|
|
|
@redis.verify
|
|
|
|
end
|
|
|
|
|
2012-03-17 21:22:56 -04:00
|
|
|
it 'handles a recurring failed message' do
|
2012-03-18 02:04:31 -04:00
|
|
|
@redis.expect :zadd, 1, ['retry', String, String]
|
2012-03-17 21:22:56 -04:00
|
|
|
now = Time.now.utc
|
2012-04-01 22:53:45 -04:00
|
|
|
msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], 'retry' => true, "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>10}
|
2012-03-17 21:22:56 -04:00
|
|
|
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
|
|
|
assert_raises RuntimeError do
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg, 'default') do
|
2012-03-17 21:22:56 -04:00
|
|
|
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
|
2012-03-18 02:04:31 -04:00
|
|
|
|
2012-10-17 18:51:26 -04:00
|
|
|
it 'handles a recurring failed message before reaching user-specifed max' do
|
|
|
|
@redis.expect :zadd, 1, ['retry', String, String]
|
|
|
|
now = Time.now.utc
|
2012-11-10 00:18:02 -05:00
|
|
|
msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], 'retry' => 10, "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>8}
|
2012-10-17 18:51:26 -04:00
|
|
|
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
|
|
|
assert_raises RuntimeError do
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg, 'default') do
|
2012-10-17 18:51:26 -04:00
|
|
|
raise "kerblammo!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
assert_equal 'default', msg["queue"]
|
|
|
|
assert_equal 'kerblammo!', msg["error_message"]
|
|
|
|
assert_equal 'RuntimeError', msg["error_class"]
|
2012-11-10 00:18:02 -05:00
|
|
|
assert_equal 9, msg["retry_count"]
|
2012-10-17 18:51:26 -04:00
|
|
|
assert msg["failed_at"]
|
|
|
|
@redis.verify
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'throws away old messages after too many retries (using the default)' do
|
2012-03-18 02:04:31 -04:00
|
|
|
now = Time.now.utc
|
2012-10-16 09:55:04 -04:00
|
|
|
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 ]
|
2012-03-18 02:04:31 -04:00
|
|
|
handler = Sidekiq::Middleware::Server::RetryJobs.new
|
|
|
|
assert_raises RuntimeError do
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg, 'default') do
|
2012-03-18 02:04:31 -04:00
|
|
|
raise "kerblammo!"
|
|
|
|
end
|
|
|
|
end
|
2013-05-12 17:25:30 -04:00
|
|
|
# Minitest can't assert that a method call did NOT happen!?
|
2012-10-16 09:55:04 -04:00
|
|
|
assert_raises(MockExpectationError) { @redis.verify }
|
2012-03-18 02:04:31 -04:00
|
|
|
end
|
2012-10-17 18:51:26 -04:00
|
|
|
|
|
|
|
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
|
2013-06-25 11:07:45 -04:00
|
|
|
handler.call(worker, msg, 'default') do
|
2012-10-17 18:51:26 -04:00
|
|
|
raise "kerblammo!"
|
|
|
|
end
|
|
|
|
end
|
2013-05-12 17:25:30 -04:00
|
|
|
# Minitest can't assert that a method call did NOT happen!?
|
2012-10-17 18:51:26 -04:00
|
|
|
assert_raises(MockExpectationError) { @redis.verify }
|
|
|
|
end
|
2013-03-18 17:20:28 -04:00
|
|
|
|
2013-03-21 14:16:07 -04:00
|
|
|
describe "retry exhaustion" do
|
|
|
|
let(:handler){ Sidekiq::Middleware::Server::RetryJobs.new }
|
2013-06-25 11:07:45 -04:00
|
|
|
let(:worker) { Minitest::Mock.new }
|
2013-03-21 14:16:07 -04:00
|
|
|
let(:msg){ {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>Time.now.utc, "retry"=>3, "retry_count"=>3} }
|
|
|
|
|
2013-06-26 13:48:24 -04:00
|
|
|
describe "worker method" do
|
2013-06-27 11:58:23 -04:00
|
|
|
let(:worker) do
|
|
|
|
klass = Class.new do
|
|
|
|
include Sidekiq::Worker
|
|
|
|
|
|
|
|
def self.name; "Worker"; end
|
|
|
|
|
|
|
|
def retries_exhausted(*args)
|
|
|
|
args << "retried_method"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-26 13:48:24 -04:00
|
|
|
it 'calls worker.retries_exhausted after too many retries' do
|
2013-06-27 11:58:23 -04:00
|
|
|
assert_equal [1,2, "foo", "retried_method"], handler.retries_exhausted(worker.new, msg)
|
2013-06-26 13:48:24 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "worker block" do
|
|
|
|
let(:worker) do
|
|
|
|
Class.new do
|
|
|
|
include Sidekiq::Worker
|
|
|
|
|
|
|
|
sidekiq_retries_exhausted { |*args| args << "retried" }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'calls worker sidekiq_retries_exhausted_block after too many retries' do
|
2013-06-27 11:58:23 -04:00
|
|
|
assert_equal [1,2, "foo", "retried"], handler.retries_exhausted(worker.new, msg)
|
2013-06-26 13:48:24 -04:00
|
|
|
end
|
2013-03-21 14:16:07 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'handles and logs retries_exhausted failures gracefully (drops them)' do
|
|
|
|
def worker.retries_exhausted(*args)
|
|
|
|
raise 'bam!'
|
|
|
|
end
|
|
|
|
|
|
|
|
e = task_misbehaving_worker
|
|
|
|
assert_equal e.message, "kerblammo!"
|
2013-03-21 17:37:41 -04:00
|
|
|
worker.verify
|
2013-03-21 14:16:07 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def task_misbehaving_worker
|
|
|
|
assert_raises RuntimeError do
|
2013-03-21 17:37:41 -04:00
|
|
|
handler.call(worker, msg, 'default') do
|
2013-03-21 14:16:07 -04:00
|
|
|
raise 'kerblammo!'
|
|
|
|
end
|
2013-03-18 17:20:28 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2013-06-25 11:07:45 -04:00
|
|
|
|
|
|
|
describe "custom retry delay" do
|
2013-06-25 12:35:11 -04:00
|
|
|
before do
|
|
|
|
@old_logger = Sidekiq.logger
|
|
|
|
@tmp_log_path = '/tmp/sidekiq-retries.log'
|
|
|
|
Sidekiq.logger = Logger.new(@tmp_log_path)
|
|
|
|
end
|
|
|
|
|
|
|
|
after do
|
|
|
|
Sidekiq.logger = @old_logger
|
|
|
|
Sidekiq.options.delete(:logfile)
|
|
|
|
File.unlink @tmp_log_path if File.exists?(@tmp_log_path)
|
|
|
|
end
|
2013-06-25 11:07:45 -04:00
|
|
|
|
|
|
|
let(:custom_worker) do
|
|
|
|
Class.new do
|
|
|
|
include ::Sidekiq::Worker
|
|
|
|
|
|
|
|
sidekiq_retry_in do |count|
|
|
|
|
count * 2
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-25 12:35:11 -04:00
|
|
|
let(:error_worker) do
|
|
|
|
Class.new do
|
|
|
|
include ::Sidekiq::Worker
|
|
|
|
|
|
|
|
sidekiq_retry_in do |count|
|
|
|
|
count / 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-25 14:11:19 -04:00
|
|
|
let(:handler) { Sidekiq::Middleware::Server::RetryJobs.new }
|
2013-06-25 11:07:45 -04:00
|
|
|
|
|
|
|
it "retries with a default delay" do
|
2013-06-26 00:10:46 -04:00
|
|
|
refute_equal 4, handler.delay_for(worker, 2)
|
2013-06-25 11:07:45 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "retries with a custom delay" do
|
2013-06-26 00:10:46 -04:00
|
|
|
assert_equal 4, handler.delay_for(custom_worker, 2)
|
2013-06-25 11:07:45 -04:00
|
|
|
end
|
2013-06-25 12:35:11 -04:00
|
|
|
|
|
|
|
it "falls back to the default retry on exception" do
|
2013-06-26 00:10:46 -04:00
|
|
|
refute_equal 4, handler.delay_for(error_worker, 2)
|
2013-06-25 12:35:11 -04:00
|
|
|
assert_match(/Failure scheduling retry using the defined `sidekiq_retry_in`/,
|
|
|
|
File.read(@tmp_log_path), 'Log entry missing for sidekiq_retry_in')
|
|
|
|
end
|
2013-06-25 11:07:45 -04:00
|
|
|
end
|
2012-03-17 21:22:56 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|