diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb index f60240fe54e734db1fb1eb5f69192909154a280e..c7d033035904df45e3d1b8d8fe7271628a3f53a6 100644 --- a/activejob/lib/active_job/exceptions.rb +++ b/activejob/lib/active_job/exceptions.rb @@ -7,6 +7,11 @@ module ActiveJob module Exceptions extend ActiveSupport::Concern + included do + class_attribute :default_retry_jitter, instance_accessor: false, instance_predicate: false + self.default_retry_jitter = 0.15 + end + module ClassMethods # 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 @@ -49,7 +54,7 @@ module ClassMethods # # 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, jitter: 0.15) + def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: nil) rescue_from(*exceptions) do |error| executions = executions_for(exceptions) if executions < attempts @@ -122,7 +127,8 @@ def retry_job(options = {}) end private - def determine_delay(seconds_or_duration_or_algorithm:, executions:, jitter:) + def determine_delay(seconds_or_duration_or_algorithm:, executions:, jitter: nil) + jitter ||= self.class.default_retry_jitter case seconds_or_duration_or_algorithm when :exponentially_longer ((executions**4) + (Kernel.rand((executions**4) * jitter))) + 2 diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb index 277016c9d26de62ff32d323ad5d285c2fcd7b961..96f3babaddd9f6a0a4778b05a67b4b80897360af 100644 --- a/activejob/test/cases/exceptions_test.rb +++ b/activejob/test/cases/exceptions_test.rb @@ -129,6 +129,31 @@ class ExceptionsTest < ActiveSupport::TestCase end end + test "retry jitter uses value from ActiveJob::Base.default_retry_jitter by default" do + old_jitter = ActiveJob::Base.default_retry_jitter + ActiveJob::Base.default_retry_jitter = 4.0 + + travel_to Time.now + + Kernel.stub(:rand, ->(arg) { arg }) do + RetryJob.perform_later "ExponentialWaitTenAttemptsError", 5, :log_scheduled_at + + assert_equal [ + "Raised ExponentialWaitTenAttemptsError for the 1st time", + "Next execution scheduled at #{(Time.now + 7.seconds).to_f}", + "Raised ExponentialWaitTenAttemptsError for the 2nd time", + "Next execution scheduled at #{(Time.now + 82.seconds).to_f}", + "Raised ExponentialWaitTenAttemptsError for the 3rd time", + "Next execution scheduled at #{(Time.now + 407.seconds).to_f}", + "Raised ExponentialWaitTenAttemptsError for the 4th time", + "Next execution scheduled at #{(Time.now + 1282.seconds).to_f}", + "Successfully completed job" + ], JobBuffer.values + end + ensure + ActiveJob::Base.default_retry_jitter = old_jitter + end + test "custom wait retrying job" do travel_to Time.now diff --git a/guides/source/configuring.md b/guides/source/configuring.md index c262a49c3048fd1cf5292a79f7f0cb612abb47cd..e4656cae2c21cd9421c84780a1ba3ee871976596 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -807,6 +807,8 @@ There are a few configuration options available in Active Support: * `config.active_job.log_arguments` controls if the arguments of a job are logged. Defaults to `true`. +* `config.active_job.default_retry_jitter` controls the amount of "jitter" (random variation) applied to the delay time calculated when retrying failed jobs. Defaults to `0.15`. + ### Configuring Action Cable * `config.action_cable.url` accepts a string for the URL for where diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index d57a86006dd5ed2d9a4ca3c832eea2b9a46b08f4..317bdea388019767ad797a00f8ba0fe6b5b57274 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -156,6 +156,10 @@ def load_defaults(target_version) when "6.1" load_defaults "6.0" + if respond_to?(:active_job) + active_job.default_retry_jitter = 0.15 + end + if respond_to?(:active_record) active_record.has_many_inversing = true end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 7084185a3357ed5abd27f5638770564c0ba45103..aee9fb7ae46230b5f1ed03ffb5cadc58c13dc031 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -2262,6 +2262,20 @@ class ::DummySerializer < ActiveJob::Serializers::ObjectSerializer; end end end + test "ActiveJob::Base.default_retry_jitter is 0.15 by default" do + app "development" + + assert_equal 0.15, ActiveJob::Base.default_retry_jitter + end + + test "ActiveJob::Base.default_retry_jitter can be set by config" do + app "development" + + Rails.application.config.active_job.default_retry_jitter = 0.22 + + assert_equal 0.22, ActiveJob::Base.default_retry_jitter + end + test "ActiveJob::Base.return_false_on_aborted_enqueue is true by default" do app "development"