Module: ActiveJob::Exceptions::ClassMethods
- Defined in:
- lib/active_job/exceptions.rb
Instance Method Summary collapse
-
#after_discard(&blk) ⇒ Object
A block to run when a job is about to be discarded for any reason.
-
#discard_on(*exceptions) ⇒ Object
Discard the job with no attempts to retry, if the exception is raised.
-
#retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT) ⇒ Object
Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts.
Instance Method Details
#after_discard(&blk) ⇒ Object
A block to run when a job is about to be discarded for any reason.
Example
class WorkJob < ActiveJob::Base
after_discard do |job, exception|
ExceptionNotifier.report(exception)
end
...
end
130 131 132 |
# File 'lib/active_job/exceptions.rb', line 130 def after_discard(&blk) self.after_discard_procs += [blk] end |
#discard_on(*exceptions) ⇒ Object
Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job, like an Active Record, is no longer available, and the job is thus no longer relevant.
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.
‘retry_on` and `discard_on` handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for which exception.is_a?(klass)
holds true is the one invoked, if any.
Example
class SearchIndexingJob < ActiveJob::Base
discard_on ActiveJob::DeserializationError
discard_on(CustomAppException) do |job, error|
ExceptionNotifier.caught(error)
end
def perform(record)
# Will raise ActiveJob::DeserializationError if the record can't be deserialized
# Might raise CustomAppException for something domain specific
end
end
109 110 111 112 113 114 115 116 |
# File 'lib/active_job/exceptions.rb', line 109 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 |
#retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT) ⇒ Object
Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts. 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.
You can also pass a block that’ll be invoked if the retry attempts fail for custom logic rather than letting the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter.
‘retry_on` and `discard_on` handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for which exception.is_a?(klass)
holds true is the one invoked, if any.
Options
-
:wait
- Re-enqueues the job with a delay specified either in seconds (default: 3 seconds), as a computing proc that takes the number of executions so far as an argument, or as a symbol reference of:polynomially_longer
, which applies the wait algorithm of((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2
(first wait ~3s, then ~18s, then ~83s, etc) -
:attempts
- Re-enqueues the job the specified number of times (default: 5 attempts) or a symbol reference of:unlimited
to retry the job until it succeeds -
:queue
- Re-enqueues the job on a different queue -
:priority
- Re-enqueues the job with a different priority -
: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)
Examples
class RemoteServiceJob < ActiveJob::Base
retry_on CustomAppException # defaults to ~3s wait, 5 attempts
retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
retry_on CustomInfrastructureException, wait: 5.minutes, attempts: :unlimited
retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
retry_on Net::OpenTimeout, Timeout::Error, wait: :polynomially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
# To retry at most 10 times for each individual exception:
# retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10
# retry_on Net::ReadTimeout, wait: 5.seconds, jitter: 0.30, attempts: 10
# retry_on Timeout::Error, wait: :polynomially_longer, attempts: 10
retry_on(YetAnotherCustomAppException) do |job, error|
ExceptionNotifier.caught(error)
end
def perform(*args)
# Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
# Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
# Might raise Net::OpenTimeout or Timeout::Error when the remote service is down
end
end
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/active_job/exceptions.rb', line 62 def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT) if wait == :exponentially_longer ActiveJob.deprecator.warn(<<~MSG.squish) `wait: :exponentially_longer` will actually wait polynomially longer and is therefore deprecated. Prefer `wait: :polynomially_longer` to avoid confusion and keep the same behavior. MSG end 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 |