Go back Web Development

Sidekiq retry on specific failed exception



Unfortunatelly sidekiq/job_retry.rb doesn't support anything like ActiveJob's   retry_on therefore you need to write your own:

# app/workers/sync_worker.rb
class SyncWorker
  include Sidekiq::Worker
  sidekiq_options queue: :foo

  def perform(model_id, next_param = nil)
    model = Model.find(model_id)

    PullDataService.new(model, next_param).call
  rescue PullDataService::TooManyRequests
    self.class.perform_in(rand(4..20).minutes, group_id, next_param, retry_count + 1)
  end
end

Now please note that this version will create infinite loop in case service keep on raising PullDataService::TooManyRequests (and no Sidekiq's retry: limits don't apply here)

If you want a version that would stop retry on several attempts: 

# app/workers/sync_worker.rb
class SyncWorker
  include Sidekiq::Worker
  sidekiq_options queue: :foo

  def perform(model_id, next_param = nil, retry_count = 0)
    model = Model.find(model_id)

    PullDataService.new(model, next_param).call
  rescue PullDataService::TooManyRequests => e
    if retry_count < 10
      self.class.perform_in(rand(4..20).minutes, group_id, next_param, retry_count + 1)
    else
      raise e
    end
  end
end

# spec/workers/sync_worker_spec.rb

require "rails_helper"

describe SyncWorker do
  subject(:worker) { described_class.new }
  let(:model) { create(:model) }
  let(:service_double) { instance_double(PullDataService) }

  before {  expect(PullDataService).to receive(:new).with(model, nil).and_return(service_double) }

  it "job perform calls service that succeed should not reschedule anything" do
    expect(service_double).to receive(:call).with(no_args)
    expect(described_class).not_to receive(:perform_in)
    worker.perform(model.id)
  end

  it "job perform calls service that failed TooManyRequests should reschedule" do
    expect(service_double).to receive(:call).with(no_args).and_raise(PullDataService::TooManyRequests)
    expect(described_class).to receive(:perform_in).with(be_kind_of(Integer), model.id, nil, 1)
    worker.perform(group.id)
  end

  it "rescheduled job beyond retry limit perform calls service that failed TooManyRequests should not reschedule" do
    expect(service_double).to receive(:call).with(no_args).and_raise(PullDataService::TooManyRequests)
    expect(described_class).not_to receive(:perform_in)
    expect { worker.perform(model.id, nil, 10) }.to raise_error(PullDataService::TooManyRequests)
  end
  # ...