mirror of https://github.com/rails/rails
This method lets job authors define a block which will be run when a job is about to be discarded.
This has utility for gems/modules included on jobs, which can tie into this behaviour and run something when a job fails. after_discard respects the existing retry behaviour, but will run even if a retried exception is handled in a block.
This commit is contained in:
parent
e49fcfa0dc
commit
659d41110f
|
@ -1,3 +1,24 @@
|
|||
* Add `after_discard` method
|
||||
|
||||
This method lets job authors define a block which will be run when a job is about to be discarded. For example:
|
||||
|
||||
```ruby
|
||||
class AfterDiscardJob < ActiveJob::Base
|
||||
after_discard do |job, exception|
|
||||
Rails.logger.info("#{job.class} raised an exception: #{exception}")
|
||||
end
|
||||
|
||||
def perform
|
||||
raise StandardError
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The above job will run the block passed to `after_discard` after the job is discarded. The exception will
|
||||
still be raised after the block has been run.
|
||||
|
||||
*Rob Cardy*
|
||||
|
||||
* Allow queue adapters to provide a custom name by implementing `queue_adapter_name`
|
||||
|
||||
*Sander Verdonschot*
|
||||
|
|
|
@ -9,6 +9,7 @@ module ActiveJob
|
|||
|
||||
included do
|
||||
class_attribute :retry_jitter, instance_accessor: false, instance_predicate: false, default: 0.0
|
||||
class_attribute :after_discard_procs, default: []
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
@ -65,8 +66,10 @@ module ActiveJob
|
|||
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
|
||||
|
@ -95,9 +98,26 @@ module ActiveJob
|
|||
rescue_from(*exceptions) do |error|
|
||||
instrument :discard, error: error do
|
||||
yield self, error if block_given?
|
||||
run_after_discard_procs(error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
def after_discard(&blk)
|
||||
self.after_discard_procs += [blk]
|
||||
end
|
||||
end
|
||||
|
||||
# Reschedules the job to be re-executed. This is useful in combination
|
||||
|
@ -164,5 +184,15 @@ module ActiveJob
|
|||
executions
|
||||
end
|
||||
end
|
||||
|
||||
def run_after_discard_procs(exception)
|
||||
exceptions = []
|
||||
after_discard_procs.each do |blk|
|
||||
instance_exec(self, exception, &blk)
|
||||
rescue StandardError => e
|
||||
exceptions << e
|
||||
end
|
||||
raise exceptions.last unless exceptions.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,7 +45,11 @@ module ActiveJob
|
|||
|
||||
_perform_job
|
||||
rescue Exception => exception
|
||||
rescue_with_handler(exception) || raise
|
||||
handled = rescue_with_handler(exception)
|
||||
return handled if handled
|
||||
|
||||
run_after_discard_procs(exception)
|
||||
raise
|
||||
end
|
||||
|
||||
def perform(*)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require "helper"
|
||||
require "jobs/retry_job"
|
||||
require "jobs/after_discard_retry_job"
|
||||
require "models/person"
|
||||
require "minitest/mock"
|
||||
|
||||
|
@ -332,6 +333,43 @@ class ExceptionsTest < ActiveSupport::TestCase
|
|||
assert_equal ["Raised DefaultsError for the 5th time"], JobBuffer.values
|
||||
end
|
||||
|
||||
test "#after_discard block is run when an unhandled error is raised" do
|
||||
assert_raises(AfterDiscardRetryJob::UnhandledError) do
|
||||
AfterDiscardRetryJob.perform_later("AfterDiscardRetryJob::UnhandledError", 2)
|
||||
end
|
||||
|
||||
assert_equal "Ran after_discard for job. Message: AfterDiscardRetryJob::UnhandledError", JobBuffer.last_value
|
||||
end
|
||||
|
||||
test "#after_discard block is run when #retry_on is passed a block" do
|
||||
AfterDiscardRetryJob.perform_later("AfterDiscardRetryJob::CustomCatchError", 6)
|
||||
|
||||
assert_equal "Ran after_discard for job. Message: AfterDiscardRetryJob::CustomCatchError", JobBuffer.last_value
|
||||
end
|
||||
|
||||
test "#after_discard block is only run once when an error class and its superclass are handled by separate #retry_on calls" do
|
||||
assert_raises(AfterDiscardRetryJob::ChildAfterDiscardError) do
|
||||
AfterDiscardRetryJob.perform_later("AfterDiscardRetryJob::ChildAfterDiscardError", 6)
|
||||
end
|
||||
assert_equal ["Raised AfterDiscardRetryJob::ChildAfterDiscardError for the 5th time", "Ran after_discard for job. Message: AfterDiscardRetryJob::ChildAfterDiscardError"], JobBuffer.values.last(2)
|
||||
end
|
||||
|
||||
test "#after_discard is run when a job is discarded via #discard_on" do
|
||||
AfterDiscardRetryJob.perform_later("AfterDiscardRetryJob::DiscardableError", 2)
|
||||
|
||||
assert_equal "Ran after_discard for job. Message: AfterDiscardRetryJob::DiscardableError", JobBuffer.last_value
|
||||
end
|
||||
|
||||
test "#after_discard is run when a job is discarded via #discard_on with a block passed to #discard_on" do
|
||||
AfterDiscardRetryJob.perform_later("AfterDiscardRetryJob::CustomDiscardableError", 2)
|
||||
|
||||
expected_array = [
|
||||
"Dealt with a job that was discarded in a custom way. Message: AfterDiscardRetryJob::CustomDiscardableError",
|
||||
"Ran after_discard for job. Message: AfterDiscardRetryJob::CustomDiscardableError"
|
||||
]
|
||||
assert_equal expected_array, JobBuffer.values.last(2)
|
||||
end
|
||||
|
||||
private
|
||||
def adapter_skips_scheduling?(queue_adapter)
|
||||
[
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../support/job_buffer"
|
||||
require "active_support/core_ext/integer/inflections"
|
||||
|
||||
class AfterDiscardRetryJob < ActiveJob::Base
|
||||
class UnhandledError < StandardError; end
|
||||
class DefaultsError < StandardError; end
|
||||
class CustomCatchError < StandardError; end
|
||||
class DiscardableError < StandardError; end
|
||||
class CustomDiscardableError < StandardError; end
|
||||
class AfterDiscardError < StandardError; end
|
||||
class ChildAfterDiscardError < AfterDiscardError; end
|
||||
|
||||
retry_on DefaultsError
|
||||
retry_on(CustomCatchError) { |job, error| JobBuffer.add("Dealt with a job that failed to retry in a custom way after #{job.arguments.second} attempts. Message: #{error.message}") }
|
||||
retry_on(AfterDiscardError)
|
||||
retry_on(ChildAfterDiscardError)
|
||||
|
||||
discard_on DiscardableError
|
||||
discard_on(CustomDiscardableError) { |_job, error| JobBuffer.add("Dealt with a job that was discarded in a custom way. Message: #{error.message}") }
|
||||
|
||||
after_discard { |_job, error| JobBuffer.add("Ran after_discard for job. Message: #{error.message}") }
|
||||
|
||||
def perform(raising, attempts)
|
||||
if executions < attempts
|
||||
JobBuffer.add("Raised #{raising} for the #{executions.ordinalize} time")
|
||||
raise raising.constantize
|
||||
else
|
||||
JobBuffer.add("Successfully completed job")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue