Sidekiq: retry once before erroring

Here's how you can silence/mute the initial Sidekiq error, but still report any errors after the first try.

This reduces noise in the error tracker and makes any errors more actionable.

def perform
  # Do work
rescue SomeError => e
  retry_once_before_raising_error(e)
end

Read on to implement retry_once_before_raising_error yourself.

Step 1: Adding retry_count to your jobs


To implement our error silencing logic, we need to access the retry count inside our Sidekiq jobs. We'll start by modifying the BaseJob class to include a retry_count attribute. Add the following code to your BaseJob class:

attr_writer :retry_count

def retry_count
  @retry_count ||= 0
end

This allows us to set and retrieve the retry count within job instances.

Step 2: Sidekiq middleware

Next, we'll create a Sidekiq middleware that sets the retry count for each job. Create a new file called retry_count.rb and add the following code:

module SidekiqMiddleware
  class RetryCount
    def call(worker, job, _queue)
      if worker.respond_to?(:retry_count)
        worker.retry_count = job.fetch("retry_count", 0)
      end

      yield
    end
  end
end

This middleware makes retry_count available to you inside of the job.

Step 3: Configuring Sidekiq to use the middleware


To activate the Sidekiq middleware, you need to add it to your Sidekiq configuration. Open your Sidekiq configuration file (e.g., sidekiq.rb) and add the following code:

config.server_middleware do |chain|
  chain.add(SidekiqMiddleware::RetryCount)
end

Step 4: Handling retry Logic and error muting


Now that we have access to the retry count and the middleware set up, we can implement our retry logic and error muting. In the BaseJob class add the following private method:

class RetryError < RuntimeError; end

def retry_once_before_raising_error(exception)
  if retry_count < 1
    raise RetryError, exception.message
  else
    raise exception
  end
end

This method checks the retry_count and raises a custom RetryError if the count is less than 1 (indicating the first retry). If the count is 1 or higher, it raises the original exception.

Step 5: Excluding RetryErrors in Sentry configuration


To silence the RetryError exceptions in Sentry, we need to configure Sentry to exclude them. Locate your Sentry error handling configuration and add the following line:

config.excluded_exceptions += ["BaseJob::RetryError"]

This configuration instructs Sentry to ignore any exceptions of the BaseJob::RetryErrortype.

Step 6: Implementing retry logic in your job


Finally, you can apply the retry logic and error muting in your job code. In the perform method of your Sidekiq job, add the following code:

def perform
  # Your job code

rescue SomeNoisyError => exception
  retry_once_before_raising_error(exception)
end

In this example, we rescue a specific SomeNoisyError and call the retry_once_before_raising_error method we defined earlier. This method handles the retry logic based on the retry count and raises the RetryError when appropriate.

Additional Integration Examples:


This works for any bug tracking service, here are a couple of examples:

Bugsnag:

  • Documentation: Bugsnag Error Handling
  • Example configuration: Bugsnag.notify(exception) unless exception.is_a?(BaseJob::RetryError)

Rollbar:

  • Documentation: Rollbar Error Handling
  • Example configuration: Rollbar.error(exception) unless exception.is_a?(BaseJob::RetryError)