diff options
Diffstat (limited to 'activejob')
-rw-r--r-- | activejob/CHANGELOG.md | 2 | ||||
-rw-r--r-- | activejob/MIT-LICENSE | 2 | ||||
-rw-r--r-- | activejob/lib/active_job.rb | 2 | ||||
-rw-r--r-- | activejob/lib/active_job/core.rb | 2 | ||||
-rw-r--r-- | activejob/lib/active_job/exceptions.rb | 15 | ||||
-rw-r--r-- | activejob/lib/active_job/gem_version.rb | 2 | ||||
-rw-r--r-- | activejob/test/cases/exceptions_test.rb | 192 | ||||
-rw-r--r-- | activejob/test/jobs/retry_job.rb | 22 |
8 files changed, 128 insertions, 111 deletions
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 31253855d7..0ed937f4cc 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,5 @@ +## Rails 6.0.0.beta1 (January 18, 2019) ## + * Return false instead of the job instance when `enqueue` is aborted. This will be the behavior in Rails 6.1 but it can be controlled now with diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE index 274211f710..aedc21bca2 100644 --- a/activejob/MIT-LICENSE +++ b/activejob/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2018 David Heinemeier Hansson +Copyright (c) 2014-2019 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb index 01fab4d918..5f20ef9b9f 100644 --- a/activejob/lib/active_job.rb +++ b/activejob/lib/active_job.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true #-- -# Copyright (c) 2014-2018 David Heinemeier Hansson +# Copyright (c) 2014-2019 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb index 4ab62f89b0..487cdd6d38 100644 --- a/activejob/lib/active_job/core.rb +++ b/activejob/lib/active_job/core.rb @@ -81,7 +81,7 @@ module ActiveJob @queue_name = self.class.queue_name @priority = self.class.priority @executions = 0 - @exception_executions = Hash.new(0) + @exception_executions = {} end # Returns a hash with the job data that can safely be passed to the diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb index 53984a4e49..9e00942a1c 100644 --- a/activejob/lib/active_job/exceptions.rb +++ b/activejob/lib/active_job/exceptions.rb @@ -30,21 +30,28 @@ module ActiveJob # class RemoteServiceJob < ActiveJob::Base # retry_on CustomAppException # defaults to 3s wait, 5 attempts # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 } + # + # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 + # retry_on Net::OpenTimeout, Timeout::Error, wait: :exponentially_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: :exponentially_longer, attempts: 10 + # # retry_on Timeout::Error, wait: :exponentially_longer, attempts: 10 + # # retry_on(YetAnotherCustomAppException) do |job, error| # ExceptionNotifier.caught(error) # end - # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 - # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10 # # 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 when the remote service is down + # # Might raise Net::OpenTimeout or Timeout::Error when the remote service is down # end # end def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil) rescue_from(*exceptions) do |error| - exception_executions[exceptions.to_s] += 1 + # Guard against jobs that were persisted before we started having individual executions counters per retry_on + self.exception_executions ||= {} + self.exception_executions[exceptions.to_s] = (exception_executions[exceptions.to_s] || 0) + 1 if exception_executions[exceptions.to_s] < attempts retry_job wait: determine_delay(wait), queue: queue, priority: priority, error: error diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb index 770f70dc5e..2b894397e3 100644 --- a/activejob/lib/active_job/gem_version.rb +++ b/activejob/lib/active_job/gem_version.rb @@ -10,7 +10,7 @@ module ActiveJob MAJOR = 6 MINOR = 0 TINY = 0 - PRE = "alpha" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb index 67327e0bbd..c88162bf58 100644 --- a/activejob/test/cases/exceptions_test.rb +++ b/activejob/test/cases/exceptions_test.rb @@ -4,157 +4,167 @@ require "helper" require "jobs/retry_job" require "models/person" -class ExceptionsTest < ActiveJob::TestCase +class ExceptionsTest < ActiveSupport::TestCase setup do JobBuffer.clear - skip if ActiveJob::Base.queue_adapter.is_a?(ActiveJob::QueueAdapters::InlineAdapter) + skip if adapter_skips_scheduling?(ActiveJob::Base.queue_adapter) end test "successfully retry job throwing exception against defaults" do - perform_enqueued_jobs do - RetryJob.perform_later "DefaultsError", 5 + RetryJob.perform_later "DefaultsError", 5 - assert_equal [ - "Raised DefaultsError for the 1st time", - "Raised DefaultsError for the 2nd time", - "Raised DefaultsError for the 3rd time", - "Raised DefaultsError for the 4th time", - "Successfully completed job" ], JobBuffer.values - end + assert_equal [ + "Raised DefaultsError for the 1st time", + "Raised DefaultsError for the 2nd time", + "Raised DefaultsError for the 3rd time", + "Raised DefaultsError for the 4th time", + "Successfully completed job" ], JobBuffer.values end test "successfully retry job throwing exception against higher limit" do - perform_enqueued_jobs do - RetryJob.perform_later "ShortWaitTenAttemptsError", 9 - assert_equal 9, JobBuffer.values.count - end + RetryJob.perform_later "ShortWaitTenAttemptsError", 9 + assert_equal 9, JobBuffer.values.count end - test "keeps the same attempts counter when several exceptions are listed in the same declaration" do + test "keeps the same attempts counter for several exceptions listed in the same retry_on declaration" do exceptions_to_raise = %w(FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo SecondRetryableErrorOfTwo SecondRetryableErrorOfTwo) assert_raises SecondRetryableErrorOfTwo do - perform_enqueued_jobs do - ExceptionRetryJob.perform_later(exceptions_to_raise) - end + RetryJob.perform_later(exceptions_to_raise, 5) + + assert_equal [ + "Raised FirstRetryableErrorOfTwo for the 1st time", + "Raised FirstRetryableErrorOfTwo for the 2nd time", + "Raised FirstRetryableErrorOfTwo for the 3rd time", + "Raised SecondRetryableErrorOfTwo for the 4th time", + "Raised SecondRetryableErrorOfTwo for the 5th time", + ], JobBuffer.values end end - test "keeps a separate attempts counter for each individual declaration" do - exceptions_to_raise = %w(FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo - DefaultsError DefaultsError) + test "keeps a separate attempts counter for each individual retry_on declaration" do + exceptions_to_raise = %w(DefaultsError DefaultsError DefaultsError DefaultsError + FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo) assert_nothing_raised do - perform_enqueued_jobs do - ExceptionRetryJob.perform_later(exceptions_to_raise) - end + RetryJob.perform_later(exceptions_to_raise, 10) + + assert_equal [ + "Raised DefaultsError for the 1st time", + "Raised DefaultsError for the 2nd time", + "Raised DefaultsError for the 3rd time", + "Raised DefaultsError for the 4th time", + "Raised FirstRetryableErrorOfTwo for the 5th time", + "Raised FirstRetryableErrorOfTwo for the 6th time", + "Raised FirstRetryableErrorOfTwo for the 7th time", + "Successfully completed job" + ], JobBuffer.values end end test "failed retry job when exception kept occurring against defaults" do - perform_enqueued_jobs do - RetryJob.perform_later "DefaultsError", 6 - assert_equal "Raised DefaultsError for the 5th time", JobBuffer.last_value - rescue DefaultsError - pass - end + RetryJob.perform_later "DefaultsError", 6 + assert_equal "Raised DefaultsError for the 5th time", JobBuffer.last_value + rescue DefaultsError + pass end test "failed retry job when exception kept occurring against higher limit" do - perform_enqueued_jobs do - RetryJob.perform_later "ShortWaitTenAttemptsError", 11 - assert_equal "Raised ShortWaitTenAttemptsError for the 10th time", JobBuffer.last_value - rescue ShortWaitTenAttemptsError - pass - end + RetryJob.perform_later "ShortWaitTenAttemptsError", 11 + assert_equal "Raised ShortWaitTenAttemptsError for the 10th time", JobBuffer.last_value + rescue ShortWaitTenAttemptsError + pass end test "discard job" do - perform_enqueued_jobs do - RetryJob.perform_later "DiscardableError", 2 - assert_equal "Raised DiscardableError for the 1st time", JobBuffer.last_value - end + RetryJob.perform_later "DiscardableError", 2 + assert_equal "Raised DiscardableError for the 1st time", JobBuffer.last_value end test "custom handling of discarded job" do - perform_enqueued_jobs do - RetryJob.perform_later "CustomDiscardableError", 2 - assert_equal "Dealt with a job that was discarded in a custom way. Message: CustomDiscardableError", JobBuffer.last_value - end + RetryJob.perform_later "CustomDiscardableError", 2 + assert_equal "Dealt with a job that was discarded in a custom way. Message: CustomDiscardableError", JobBuffer.last_value end test "custom handling of job that exceeds retry attempts" do - perform_enqueued_jobs do - RetryJob.perform_later "CustomCatchError", 6 - assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts. Message: CustomCatchError", JobBuffer.last_value - end + RetryJob.perform_later "CustomCatchError", 6 + assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts. Message: CustomCatchError", JobBuffer.last_value end test "long wait job" do travel_to Time.now - perform_enqueued_jobs do - assert_performed_with at: (Time.now + 3600.seconds).to_i do - RetryJob.perform_later "LongWaitError", 5 - end - end + RetryJob.perform_later "LongWaitError", 2, :log_scheduled_at + + assert_equal [ + "Raised LongWaitError for the 1st time", + "Next execution scheduled at #{(Time.now + 3600.seconds).to_f}", + "Successfully completed job" + ], JobBuffer.values end test "exponentially retrying job" do travel_to Time.now - perform_enqueued_jobs do - assert_performed_with at: (Time.now + 3.seconds).to_i do - assert_performed_with at: (Time.now + 18.seconds).to_i do - assert_performed_with at: (Time.now + 83.seconds).to_i do - assert_performed_with at: (Time.now + 258.seconds).to_i do - RetryJob.perform_later "ExponentialWaitTenAttemptsError", 5 - end - end - end - end - end + RetryJob.perform_later "ExponentialWaitTenAttemptsError", 5, :log_scheduled_at + + assert_equal [ + "Raised ExponentialWaitTenAttemptsError for the 1st time", + "Next execution scheduled at #{(Time.now + 3.seconds).to_f}", + "Raised ExponentialWaitTenAttemptsError for the 2nd time", + "Next execution scheduled at #{(Time.now + 18.seconds).to_f}", + "Raised ExponentialWaitTenAttemptsError for the 3rd time", + "Next execution scheduled at #{(Time.now + 83.seconds).to_f}", + "Raised ExponentialWaitTenAttemptsError for the 4th time", + "Next execution scheduled at #{(Time.now + 258.seconds).to_f}", + "Successfully completed job" + ], JobBuffer.values end test "custom wait retrying job" do travel_to Time.now - perform_enqueued_jobs do - assert_performed_with at: (Time.now + 2.seconds).to_i do - assert_performed_with at: (Time.now + 4.seconds).to_i do - assert_performed_with at: (Time.now + 6.seconds).to_i do - assert_performed_with at: (Time.now + 8.seconds).to_i do - RetryJob.perform_later "CustomWaitTenAttemptsError", 5 - end - end - end - end - end + RetryJob.perform_later "CustomWaitTenAttemptsError", 5, :log_scheduled_at + + assert_equal [ + "Raised CustomWaitTenAttemptsError for the 1st time", + "Next execution scheduled at #{(Time.now + 2.seconds).to_f}", + "Raised CustomWaitTenAttemptsError for the 2nd time", + "Next execution scheduled at #{(Time.now + 4.seconds).to_f}", + "Raised CustomWaitTenAttemptsError for the 3rd time", + "Next execution scheduled at #{(Time.now + 6.seconds).to_f}", + "Raised CustomWaitTenAttemptsError for the 4th time", + "Next execution scheduled at #{(Time.now + 8.seconds).to_f}", + "Successfully completed job" + ], JobBuffer.values end test "successfully retry job throwing one of two retryable exceptions" do - perform_enqueued_jobs do - RetryJob.perform_later "SecondRetryableErrorOfTwo", 3 + RetryJob.perform_later "SecondRetryableErrorOfTwo", 3 - assert_equal [ - "Raised SecondRetryableErrorOfTwo for the 1st time", - "Raised SecondRetryableErrorOfTwo for the 2nd time", - "Successfully completed job" ], JobBuffer.values - end + assert_equal [ + "Raised SecondRetryableErrorOfTwo for the 1st time", + "Raised SecondRetryableErrorOfTwo for the 2nd time", + "Successfully completed job" ], JobBuffer.values end test "discard job throwing one of two discardable exceptions" do - perform_enqueued_jobs do - RetryJob.perform_later "SecondDiscardableErrorOfTwo", 2 - assert_equal [ "Raised SecondDiscardableErrorOfTwo for the 1st time" ], JobBuffer.values - end + RetryJob.perform_later "SecondDiscardableErrorOfTwo", 2 + assert_equal [ "Raised SecondDiscardableErrorOfTwo for the 1st time" ], JobBuffer.values end test "successfully retry job throwing DeserializationError" do - perform_enqueued_jobs do - RetryJob.perform_later Person.new(404), 5 - assert_equal ["Raised ActiveJob::DeserializationError for the 5 time"], JobBuffer.values - end + RetryJob.perform_later Person.new(404), 5 + assert_equal ["Raised ActiveJob::DeserializationError for the 5 time"], JobBuffer.values end + + private + def adapter_skips_scheduling?(queue_adapter) + [ + ActiveJob::QueueAdapters::InlineAdapter, + ActiveJob::QueueAdapters::AsyncAdapter, + ActiveJob::QueueAdapters::SneakersAdapter + ].include?(queue_adapter.class) + end end diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb index 2d19d4c41e..112d672006 100644 --- a/activejob/test/jobs/retry_job.rb +++ b/activejob/test/jobs/retry_job.rb @@ -18,7 +18,7 @@ class CustomDiscardableError < StandardError; end class RetryJob < ActiveJob::Base retry_on DefaultsError - retry_on FirstRetryableErrorOfTwo, SecondRetryableErrorOfTwo + retry_on FirstRetryableErrorOfTwo, SecondRetryableErrorOfTwo, attempts: 4 retry_on LongWaitError, wait: 1.hour, attempts: 10 retry_on ShortWaitTenAttemptsError, wait: 1.second, attempts: 10 retry_on ExponentialWaitTenAttemptsError, wait: :exponentially_longer, attempts: 10 @@ -30,8 +30,15 @@ class RetryJob < ActiveJob::Base discard_on FirstDiscardableErrorOfTwo, SecondDiscardableErrorOfTwo discard_on(CustomDiscardableError) { |job, error| JobBuffer.add("Dealt with a job that was discarded in a custom way. Message: #{error.message}") } - def perform(raising, attempts) - if executions < attempts + before_enqueue do |job| + if job.arguments.include?(:log_scheduled_at) && job.scheduled_at + JobBuffer.add("Next execution scheduled at #{job.scheduled_at}") + end + end + + def perform(raising, attempts, *) + raising = raising.shift if raising.is_a?(Array) + if raising && executions < attempts JobBuffer.add("Raised #{raising} for the #{executions.ordinalize} time") raise raising.constantize else @@ -39,12 +46,3 @@ class RetryJob < ActiveJob::Base end end end - -class ExceptionRetryJob < ActiveJob::Base - retry_on FirstRetryableErrorOfTwo, SecondRetryableErrorOfTwo, attempts: 4 - retry_on DefaultsError - - def perform(exceptions) - raise exceptions.shift.constantize.new unless exceptions.empty? - end -end |