22

From the Rails API, I found ActiveJob can retry_job interval:

my_job_instance.enqueue
my_job_instance.enqueue wait: 5.minutes
my_job_instance.enqueue queue: :important
my_job_instance.enqueue wait_until: Date.tomorrow.midnight

http://api.rubyonrails.org/classes/ActiveJob/Enqueuing.html

But if I want to set retry count, such as Sidekiq's:

include Sidekiq::Worker
sidekiq_options :retry => 5

https://github.com/mperham/sidekiq/wiki/Error-Handling

How to do in this sample code?

class SiteScrapperJob < ActiveJob::Base
  rescue_from(ErrorLoadingSite) do
    retry_job queue: :low_priority
  end

  def perform(*args)
    # raise ErrorLoadingSite if cannot scrape
  end
end

Now I added this to my job class:

Sidekiq.default_worker_options = { retry: 5 }

But it seems not very good.

scho
  • 3,275
  • 6
  • 19
  • 30

6 Answers6

26

As of Sidekiq 6.0.4 you can use sidekiq_options in an ActiveJob to set the retry option.

Mike Perham
  • 21,300
  • 6
  • 59
  • 61
19

You also might be interested in this solution which uses serialize and deserialize api to store the number of attempts.

class DeliverWebhookJob < ActiveJob::Base
  def serialize
    super.merge('attempt_number' => (@attempt_number || 0) + 1)
  end

  def deserialize(job_data)
    super
    @attempt_number = job_data['attempt_number']
  end

  rescue_from(ErrorLoadingSite) do |exception|
    retry_job(wait: 10) if @attempt_number < 5
  end

  def perform(*args)
    # raise ErrorLoadingSite if cannot scrape
  end
end

Take it from here.

Vini Oliveira
  • 365
  • 5
  • 9
16

Since Rails 5.1, there is a built-in way to do this using the retry_on method. It's a general ActiveJob method, so it will work with any queuing backend, not just Sidekiq.

For example, for your specific job you could do:

class SiteScraperJob < ActiveJob::Base
  retry_on ErrorLoadingSite, queue: :low_priority, attempts: 5

  def perform(*args)
    # raise ErrorLoadingSite if cannot scrape
  end
end

You can also set a constant wait interval or an exponential wait strategy, as explained in the docs.

hattila91
  • 673
  • 7
  • 16
  • ActiveJob became surprisingly handy in rails 5. Thank you for this answer! – Evgeniya Manolova May 20 '18 at 21:57
  • 1
    From the documentation, "If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a holding queue for inspection.". This means that it is not possible to use it to limit number of retries for a job unless you disable retries for sidekiq altogether and use only `retry_on`. – Pulkit Goyal May 22 '20 at 06:51
  • @PulkitGoyal That's a great point! So as I understand, this job could be retried up to 30 times (5 times by ActiveJob and a further 25 times by Sidekiq using its default settings)? That would make this really confusing IMO and so it's probably best to stick with the Sidekiq specific settings then. – hattila91 May 22 '20 at 10:11
4

There is a activejob-retry gem which does the job

class SiteScrapperJob < ActiveJob::Base
  include ActiveJob::Retry.new(limit: 5, strategy: :exponential)

  def perform(*args)
    # raise ErrorLoadingSite if cannot scrape
  end
end

Another option is to use sidekiq middleware:

First define job_options class-method which will be available in the subclasses:

class ApplicationJob < ActiveJob::Base
  def self.job_options(options)
    @job_options = options
  end

  def self.get_job_options
    @job_options || {}
  end
end

Add middleware which reads job_options from the jobs's class and writes them to the job item for sidekiq:

module Sidekiq
 class JobOptionsMiddleware

   def call(job_wrapper, item, queue, redis_pool)
     job = item['args'][0]['job_class'].constantize

     job.get_job_options
       .each{ |option, value| item[option] = value if item[option].nil? }

     yield
   end

 end

 # in sidekiq initializer

 Sidekiq.configure_client do |config|
   config.client_middleware do |chain|
     chain.add Sidekiq::JobOptionsMiddleware
   end
 end

And finally

 class SiteScrapperJob < ApplicationJob
   job_options retry: 5

   def perform
     # your code
   end
 end
Hirurg103
  • 4,783
  • 2
  • 34
  • 50
2

See here the defaults for Sidekiq. The attribute retry "accepts" a boolean value and not a number as you assumed.

From the merge of active_job into Rails this other file one can see that once again retry doesn't accept the number of retries.

What the documentation says then is that per job you can define if the job retries or not.

I also tried to find if the config/sidekiq.yml file can receive this number, and seems like it can't.

Finally,

If you don't fix the bug within 25 retries (about 21 days), Sidekiq will stop retrying and move your job to the Dead Job Queue. You can fix the bug and retry the job manually anytime within the next 6 months using the Web UI.

Nuno Costa
  • 1,210
  • 11
  • 12
  • You're incorrect. 'retry' can be a number. https://github.com/mperham/sidekiq/wiki/Error-Handling#configuration – Mike Perham Oct 15 '15 at 23:38
  • 1
    Your question's title says "with ActiveJob". The URL you sent is not from ActiveJob. The owner of the repo (@MikePerham) replied as well saying you can't – Nuno Costa Oct 16 '15 at 00:29
  • Thank you. I have add `Sidekiq.default_worker_options = { retry: 5 }` into my class. But seems work worse(retry much times one day!). The default retry times is 25. Then can't I customize it? – scho Oct 16 '15 at 01:13
  • See [here](https://github.com/mperham/sidekiq/blob/7a727c61cfd3f174d32e3a231b14833b61b897fe/lib/sidekiq/middleware/server/retry_jobs.rb#L23:L32) what the `RetryJobs` is. The code you added should be added to the `application.rb` and not to your ActiveJob subclass. In case this doesn't work the way you want, just move all your jobs to app/workers/ (renaming the filename suffix to _worker.rb) and go the standard way (à lá Rails 3) – Nuno Costa Oct 16 '15 at 01:33
  • Thank you. Then if I don't want to retry, how to do? – scho Oct 16 '15 at 07:39
  • If you don't want to retry, either you don't have a job (lol) or you rescue the exception inside the `perform` function – Nuno Costa Oct 16 '15 at 10:20
1

FYI this problem has been fixed in the release of Sidekiq 6.0. You can have a look at the changelog here: https://github.com/mperham/sidekiq/blob/master/Changes.md

But basically you can pass the same options by calling sidekiq_options with your options hash. Thanks Mike Perham.

paascal
  • 314
  • 5
  • 8