module ActiveJob::Exceptions::ClassMethods
def after_discard(&blk)
...
end
ExceptionNotifier.report(exception)
after_discard do |job, exception|
class WorkJob < ActiveJob::Base
==== Example
A block to run when a job is about to be discarded for any reason.
def after_discard(&blk) self.after_discard_procs += [blk] end
def discard_on(*exceptions)
end
# Might raise CustomAppException for something domain specific
# Will raise ActiveJob::DeserializationError if the record can't be deserialized
def perform(record)
end
ExceptionNotifier.caught(error)
discard_on(CustomAppException) do |job, error|
discard_on ActiveJob::DeserializationError
class SearchIndexingJob < ActiveJob::Base
==== Example
which exception.is_a?(klass) holds true is the one invoked, if any.
+retry_on+ and +discard_on+ handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for
You can also pass a block that'll be invoked. This block is yielded with the job instance as the first and the error instance as the second parameter.
like an Active Record, is no longer available, and the job is thus no longer relevant.
Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job,
def discard_on(*exceptions) rescue_from(*exceptions) do |error| instrument :discard, error: error do yield self, error if block_given? run_after_discard_procs(error) end end end
def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT)
end
# Might raise Net::OpenTimeout or Timeout::Error when the remote service is down
# Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
# Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
def perform(*args)
end
ExceptionNotifier.caught(error)
retry_on(YetAnotherCustomAppException) do |job, error|
# retry_on Timeout::Error, wait: :polynomially_longer, attempts: 10
# retry_on Net::ReadTimeout, wait: 5.seconds, jitter: 0.30, attempts: 10
# retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10
# To retry at most 10 times for each individual exception:
retry_on Net::OpenTimeout, Timeout::Error, wait: :polynomially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
retry_on CustomInfrastructureException, wait: 5.minutes, attempts: :unlimited
retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
retry_on CustomAppException # defaults to ~3s wait, 5 attempts
class RemoteServiceJob < ActiveJob::Base
==== Examples
* :jitter - A random delay of wait time used when calculating backoff. The default is 15% (0.15) which represents the upper bound of possible wait time (expressed as a percentage)
* :priority - Re-enqueues the job with a different priority
* :queue - Re-enqueues the job on a different queue
to retry the job until it succeeds. The number of attempts includes the original job execution.
* :attempts - Enqueues the job the specified number of times (default: 5 attempts) or a symbol reference of :unlimited
(first wait ~3s, then ~18s, then ~83s, etc)
:polynomially_longer, which applies the wait algorithm of ((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2
as a computing proc that takes the number of executions so far as an argument, or as a symbol reference of
* :wait - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
==== Options
which exception.is_a?(klass) holds true is the one invoked, if any.
+retry_on+ and +discard_on+ handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for
the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter.
You can also pass a block that'll be invoked if the retry attempts fail for custom logic rather than letting
holding queue for inspection.
bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a
If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to
Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts.
def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT) rescue_from(*exceptions) do |error| executions = executions_for(exceptions) if attempts == :unlimited || executions < attempts retry_job wait: determine_delay(seconds_or_duration_or_algorithm: wait, executions: executions, jitter: jitter), queue: queue, priority: priority, error: error else if block_given? instrument :retry_stopped, error: error do yield self, error end run_after_discard_procs(error) else instrument :retry_stopped, error: error run_after_discard_procs(error) raise error end end end end