activejob 6.0.6.1 → 6.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: adbbee2d0fe2c43330baf21875d3a39f5e58171124de1a8495728d3b5c281d9c
4
- data.tar.gz: ebdd2fff8081906fe5caa78e3a671bc1e0b834ac0e1eb7389e87fd5c0dbead4c
3
+ metadata.gz: dc9b631f7b43e77aa14e8fb65525b51ad82f92f5b0046d15d44c2e5f2429a2aa
4
+ data.tar.gz: a2e8afb6e5295337e82c0f0c3de726cbe11dcedba0f2f34c6c4323170a74dc13
5
5
  SHA512:
6
- metadata.gz: de7357ed0fd2885973dfb4a88acb0efb650eab45cf204f6b5dec81c3b53eb34e162cb06d7ad295a72dec0ced6b0464a79ab622d66e42f3388e65671a6d91cc02
7
- data.tar.gz: c5b62b04096aeea7ebff62756c253894de1fc89c2e7ce577f8b76df93860a901f3538f6a39213c3967e51fecb8c3fd727f4de1a28ab11df408d70c78afda6c83
6
+ metadata.gz: b30c316861f1ae07c363df9184ef9a306d796756429d11978166b3864263cc72ff521c6d27093e17f0e2e850b6921c3dd39702646d6f5b7a5a198221d1e68f4f
7
+ data.tar.gz: 99ed04c52a31748e94edd687fe93a48de660e44d29b5645597a278a97450405f57c28b92d69b98cf0f3ded97711952a58b700b9f90e2ad8dac5148b843920b17
data/CHANGELOG.md CHANGED
@@ -1,293 +1,130 @@
1
- ## Rails 6.0.6.1 (January 17, 2023) ##
1
+ ## Rails 6.1.0.rc1 (November 02, 2020) ##
2
2
 
3
- * No changes.
3
+ * Recover nano precision when serializing `Time`, `TimeWithZone` and `DateTime` objects.
4
4
 
5
+ *Alan Tan*
5
6
 
6
- ## Rails 6.0.6 (September 09, 2022) ##
7
+ * Deprecate `config.active_job.return_false_on_aborted_enqueue`.
7
8
 
8
- * No changes.
9
+ *Rafael Mendonça França*
9
10
 
11
+ * Return `false` when enqueuing a job is aborted.
10
12
 
11
- ## Rails 6.0.5.1 (July 12, 2022) ##
12
-
13
- * No changes.
14
-
15
-
16
- ## Rails 6.0.5 (May 09, 2022) ##
17
-
18
- * No changes.
19
-
20
-
21
- ## Rails 6.0.4.8 (April 26, 2022) ##
22
-
23
- * No changes.
24
-
25
-
26
- ## Rails 6.0.4.7 (March 08, 2022) ##
27
-
28
- * No changes.
29
-
30
-
31
- ## Rails 6.0.4.6 (February 11, 2022) ##
32
-
33
- * No changes.
34
-
35
-
36
- ## Rails 6.0.4.5 (February 11, 2022) ##
37
-
38
- * No changes.
39
-
40
-
41
- ## Rails 6.0.4.4 (December 15, 2021) ##
42
-
43
- * No changes.
44
-
45
-
46
- ## Rails 6.0.4.3 (December 14, 2021) ##
47
-
48
- * No changes.
49
-
50
-
51
- ## Rails 6.0.4.2 (December 14, 2021) ##
52
-
53
- * No changes.
54
-
55
-
56
- ## Rails 6.0.4.1 (August 19, 2021) ##
57
-
58
- * No changes.
59
-
60
-
61
- ## Rails 6.0.4 (June 15, 2021) ##
62
-
63
- * No changes.
64
-
65
-
66
- ## Rails 6.0.3.7 (May 05, 2021) ##
67
-
68
- * No changes.
69
-
70
-
71
- ## Rails 6.0.3.6 (March 26, 2021) ##
72
-
73
- * No changes.
74
-
75
-
76
- ## Rails 6.0.3.5 (February 10, 2021) ##
77
-
78
- * No changes.
79
-
80
-
81
- ## Rails 6.0.3.4 (October 07, 2020) ##
82
-
83
- * No changes.
84
-
85
-
86
- ## Rails 6.0.3.3 (September 09, 2020) ##
87
-
88
- * No changes.
89
-
90
-
91
- ## Rails 6.0.3.2 (June 17, 2020) ##
92
-
93
- * No changes.
94
-
95
-
96
- ## Rails 6.0.3.1 (May 18, 2020) ##
97
-
98
- * No changes.
99
-
100
-
101
- ## Rails 6.0.3 (May 06, 2020) ##
13
+ *Rafael Mendonça França*
102
14
 
103
15
  * While using `perform_enqueued_jobs` test helper enqueued jobs must be stored for the later check with
104
16
  `assert_enqueued_with`.
105
17
 
106
18
  *Dmitry Polushkin*
107
19
 
108
- * Add queue name support to Que adapter
109
-
110
- *Brad Nauta*, *Wojciech Wnętrzak*
111
-
112
-
113
- ## Rails 6.0.2.2 (March 19, 2020) ##
20
+ * `ActiveJob::TestCase#perform_enqueued_jobs` without a block removes performed jobs from the queue.
114
21
 
115
- * No changes.
22
+ That way the helper can be called multiple times and not perform a job invocation multiple times.
116
23
 
24
+ ```ruby
25
+ def test_jobs
26
+ HelloJob.perform_later("rafael")
27
+ perform_enqueued_jobs # only runs with "rafael"
28
+ HelloJob.perform_later("david")
29
+ perform_enqueued_jobs # only runs with "david"
30
+ end
31
+ ```
117
32
 
118
- ## Rails 6.0.2.1 (December 18, 2019) ##
119
-
120
- * No changes.
121
-
122
-
123
- ## Rails 6.0.2 (December 13, 2019) ##
124
-
125
- * Allow Sidekiq access to the underlying job class.
126
-
127
- By having access to the Active Job class, Sidekiq can get access to any `sidekiq_options` which
128
- have been set on that Active Job type and serialize those options into Redis.
129
-
130
- https://github.com/mperham/sidekiq/blob/master/Changes.md#60
131
-
132
- *Mike Perham*
133
-
134
-
135
- ## Rails 6.0.1 (November 5, 2019) ##
136
-
137
- * No changes.
138
-
139
-
140
- ## Rails 6.0.0 (August 16, 2019) ##
141
-
142
- * `assert_enqueued_with` and `assert_performed_with` can now test jobs with relative delay.
143
-
144
- *Vlado Cingel*
145
-
146
-
147
- ## Rails 6.0.0.rc2 (July 22, 2019) ##
148
-
149
- * No changes.
150
-
151
-
152
- ## Rails 6.0.0.rc1 (April 24, 2019) ##
153
-
154
- * Use individual execution counters when calculating retry delay.
155
-
156
- *Patrik Bóna*
157
-
158
- * Make job argument assertions with `Time`, `ActiveSupport::TimeWithZone`, and `DateTime` work by dropping microseconds. Microsecond precision is lost during serialization.
159
-
160
- *Gannon McGibbon*
161
-
162
-
163
- ## Rails 6.0.0.beta3 (March 11, 2019) ##
164
-
165
- * No changes.
166
-
167
-
168
- ## Rails 6.0.0.beta2 (February 25, 2019) ##
169
-
170
- * No changes.
33
+ *Étienne Barrié*
171
34
 
35
+ * `ActiveJob::TestCase#perform_enqueued_jobs` will no longer perform retries:
172
36
 
173
- ## Rails 6.0.0.beta1 (January 18, 2019) ##
37
+ When calling `perform_enqueued_jobs` without a block, the adapter will
38
+ now perform jobs that are **already** in the queue. Jobs that will end up in
39
+ the queue afterwards won't be performed.
174
40
 
175
- * Return false instead of the job instance when `enqueue` is aborted.
41
+ This change only affects `perform_enqueued_jobs` when no block is given.
176
42
 
177
- This will be the behavior in Rails 6.1 but it can be controlled now with
178
- `config.active_job.return_false_on_aborted_enqueue`.
43
+ *Edouard Chin*
179
44
 
180
- *Kir Shatrov*
45
+ * Add queue name support to Que adapter.
181
46
 
182
- * Keep executions for each specific declaration
47
+ *Brad Nauta*, *Wojciech Wnętrzak*
183
48
 
184
- Each `retry_on` declaration has now its own specific executions counter. Before it was
185
- shared between all executions of a job.
49
+ * Don't run `after_enqueue` and `after_perform` callbacks if the callback chain is halted.
186
50
 
187
- *Alberto Almagro*
51
+ class MyJob < ApplicationJob
52
+ before_enqueue { throw(:abort) }
53
+ after_enqueue { # won't enter here anymore }
54
+ end
188
55
 
189
- * Allow all assertion helpers that have a `only` and `except` keyword to accept
190
- Procs.
56
+ `after_enqueue` and `after_perform` callbacks will no longer run if the callback chain is halted.
57
+ This behaviour is a breaking change and won't take effect until Rails 6.2.
58
+ To enable this behaviour in your app right now, you can add in your app's configuration file
59
+ `config.active_job.skip_after_callbacks_if_terminated = true`.
191
60
 
192
61
  *Edouard Chin*
193
62
 
194
- * Restore `HashWithIndifferentAccess` support to `ActiveJob::Arguments.deserialize`.
63
+ * Fix enqueuing and performing incorrect logging message.
195
64
 
196
- *Gannon McGibbon*
65
+ Jobs will no longer always log "Enqueued MyJob" or "Performed MyJob" when they actually didn't get enqueued/performed.
197
66
 
198
- * Include deserialized arguments in job instances returned from
199
- `assert_enqueued_with` and `assert_performed_with`
67
+ ```ruby
68
+ class MyJob < ApplicationJob
69
+ before_enqueue { throw(:abort) }
70
+ end
200
71
 
201
- *Alan Wu*
72
+ MyJob.perform_later # Will no longer log "Enqueued MyJob" since job wasn't even enqueued through adapter.
73
+ ```
202
74
 
203
- * Allow `assert_enqueued_with`/`assert_performed_with` methods to accept
204
- a proc for the `args` argument. This is useful to check if only a subset of arguments
205
- matches your expectations.
75
+ A new message will be logged in case a job couldn't be enqueued, either because the callback chain was halted or
76
+ because an exception happened during enqueing. (i.e. Redis is down when you try to enqueue your job)
206
77
 
207
78
  *Edouard Chin*
208
79
 
209
- * `ActionDispatch::IntegrationTest` includes `ActiveJob::TestHelper` module by default.
80
+ * Add an option to disable logging of the job arguments when enqueuing and executing the job.
210
81
 
211
- *Ricardo Díaz*
82
+ class SensitiveJob < ApplicationJob
83
+ self.log_arguments = false
212
84
 
213
- * Added `enqueue_retry.active_job`, `retry_stopped.active_job`, and `discard.active_job` hooks.
85
+ def perform(my_sensitive_argument)
86
+ end
87
+ end
214
88
 
215
- *steves*
89
+ When dealing with sensitive arguments as password and tokens it is now possible to configure the job
90
+ to not put the sensitive argument in the logs.
216
91
 
217
- * Allow `assert_performed_with` to be called without a block.
92
+ *Rafael Mendonça França*
218
93
 
219
- *bogdanvlviv*
94
+ * Changes in `queue_name_prefix` of a job no longer affects all other jobs.
220
95
 
221
- * Execution of `assert_performed_jobs`, and `assert_no_performed_jobs`
222
- without a block should respect passed `:except`, `:only`, and `:queue` options.
96
+ Fixes #37084.
223
97
 
224
- *bogdanvlviv*
98
+ *Lucas Mansur*
225
99
 
226
- * Allow `:queue` option to job assertions and helpers.
227
-
228
- *bogdanvlviv*
229
-
230
- * Allow `perform_enqueued_jobs` to be called without a block.
231
-
232
- Performs all of the jobs that have been enqueued up to this point in the test.
100
+ * Allow `Class` and `Module` instances to be serialized.
233
101
 
234
102
  *Kevin Deisz*
235
103
 
236
- * Move `enqueue`/`enqueue_at` notifications to an around callback.
237
-
238
- Improves timing accuracy over the old after callback by including
239
- time spent writing to the adapter's IO implementation.
240
-
241
- *Zach Kemp*
242
-
243
- * Allow call `assert_enqueued_with` with no block.
244
-
245
- Example:
246
- ```
247
- def test_assert_enqueued_with
248
- MyJob.perform_later(1,2,3)
249
- assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low')
250
-
251
- MyJob.set(wait_until: Date.tomorrow.noon).perform_later
252
- assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon)
253
- end
254
- ```
255
-
256
- *bogdanvlviv*
257
-
258
- * Allow passing multiple exceptions to `retry_on`, and `discard_on`.
259
-
260
- *George Claghorn*
104
+ * Log potential matches in `assert_enqueued_with` and `assert_performed_with`.
261
105
 
262
- * Pass the error instance as the second parameter of block executed by `discard_on`.
106
+ *Gareth du Plooy*
263
107
 
264
- Fixes #32853.
108
+ * Add `at` argument to the `perform_enqueued_jobs` test helper.
265
109
 
266
- *Yuji Yaginuma*
110
+ *John Crepezzi*, *Eileen Uchitelle*
267
111
 
268
- * Remove support for Qu gem.
269
-
270
- Reasons are that the Qu gem wasn't compatible since Rails 5.1,
271
- gem development was stopped in 2014 and maintainers have
272
- confirmed its demise. See issue #32273
273
-
274
- *Alberto Almagro*
275
-
276
- * Add support for timezones to Active Job.
277
-
278
- Record what was the current timezone in effect when the job was
279
- enqueued and then restore when the job is executed in same way
280
- that the current locale is recorded and restored.
112
+ * `assert_enqueued_with` and `assert_performed_with` can now test jobs with relative delay.
281
113
 
282
- *Andrew White*
114
+ *Vlado Cingel*
283
115
 
284
- * Rails 6 requires Ruby 2.5.0 or newer.
116
+ * Add jitter to `ActiveJob::Exceptions.retry_on`.
285
117
 
286
- *Jeremy Daer*, *Kasper Timm Hansen*
118
+ `ActiveJob::Exceptions.retry_on` now uses a random amount of jitter in order to
119
+ prevent the [thundering herd effect](https://en.wikipedia.org/wiki/Thundering_herd_problem). Defaults to
120
+ 15% (represented as 0.15) but overridable via the `:jitter` option when using `retry_on`.
121
+ Jitter is applied when an `Integer`, `ActiveSupport::Duration` or `:exponentially_longer`, is passed to the `wait` argument in `retry_on`.
287
122
 
288
- * Add support to define custom argument serializers.
123
+ ```ruby
124
+ retry_on(MyError, wait: :exponentially_longer, jitter: 0.30)
125
+ ```
289
126
 
290
- *Evgenii Pecherkin*, *Rafael Mendonça França*
127
+ *Anthony Ross*
291
128
 
292
129
 
293
- Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activejob/CHANGELOG.md) for previous changes.
130
+ Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activejob/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2019 David Heinemeier Hansson
1
+ Copyright (c) 2014-2020 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -95,9 +95,6 @@ their gem, or as a stand-alone gem. For discussion about this see the
95
95
  following PRs: [23311](https://github.com/rails/rails/issues/23311#issuecomment-176275718),
96
96
  [21406](https://github.com/rails/rails/pull/21406#issuecomment-138813484), and [#32285](https://github.com/rails/rails/pull/32285).
97
97
 
98
- ## Auxiliary gems
99
-
100
- * [activejob-stats](https://github.com/seuros/activejob-stats)
101
98
 
102
99
  ## Download and installation
103
100
 
@@ -109,7 +106,8 @@ The latest version of Active Job can be installed with RubyGems:
109
106
 
110
107
  Source code can be downloaded as part of the Rails project on GitHub:
111
108
 
112
- * https://github.com/rails/rails/tree/main/activejob
109
+ * https://github.com/rails/rails/tree/master/activejob
110
+
113
111
 
114
112
  ## License
115
113
 
@@ -8,7 +8,9 @@ require "active_job/enqueuing"
8
8
  require "active_job/execution"
9
9
  require "active_job/callbacks"
10
10
  require "active_job/exceptions"
11
+ require "active_job/log_subscriber"
11
12
  require "active_job/logging"
13
+ require "active_job/instrumentation"
12
14
  require "active_job/timezones"
13
15
  require "active_job/translation"
14
16
 
@@ -68,6 +70,7 @@ module ActiveJob #:nodoc:
68
70
  include Callbacks
69
71
  include Exceptions
70
72
  include Logging
73
+ include Instrumentation
71
74
  include Timezones
72
75
  include Translation
73
76
 
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/callbacks"
4
+ require "active_support/core_ext/object/with_options"
5
+ require "active_support/core_ext/module/attribute_accessors"
4
6
 
5
7
  module ActiveJob
6
8
  # = Active Job Callbacks
@@ -27,16 +29,25 @@ module ActiveJob
27
29
  end
28
30
 
29
31
  included do
30
- define_callbacks :perform
31
- define_callbacks :enqueue
32
+ class_attribute :return_false_on_aborted_enqueue, instance_accessor: false, instance_predicate: false, default: false
33
+ singleton_class.deprecate :return_false_on_aborted_enqueue, :return_false_on_aborted_enqueue=
34
+ cattr_accessor :skip_after_callbacks_if_terminated, instance_accessor: false, default: false
32
35
 
33
- class_attribute :return_false_on_aborted_enqueue, instance_accessor: false, instance_predicate: false
34
- self.return_false_on_aborted_enqueue = false
36
+ with_options(skip_after_callbacks_if_terminated: skip_after_callbacks_if_terminated) do
37
+ define_callbacks :perform
38
+ define_callbacks :enqueue
39
+ end
35
40
  end
36
41
 
37
42
  # These methods will be included into any Active Job object, adding
38
43
  # callbacks for +perform+ and +enqueue+ methods.
39
44
  module ClassMethods
45
+ def inherited(klass)
46
+ klass.get_callbacks(:enqueue).config[:skip_after_callbacks_if_terminated] = skip_after_callbacks_if_terminated
47
+ klass.get_callbacks(:perform).config[:skip_after_callbacks_if_terminated] = skip_after_callbacks_if_terminated
48
+ super
49
+ end
50
+
40
51
  # Defines a callback that will get called right before the
41
52
  # job's perform method is executed.
42
53
  #
@@ -91,6 +102,19 @@ module ActiveJob
91
102
  # end
92
103
  # end
93
104
  #
105
+ # You can access the return value of the job only if the execution wasn't halted.
106
+ #
107
+ # class VideoProcessJob < ActiveJob::Base
108
+ # around_perform do |job, block|
109
+ # value = block.call
110
+ # puts value # => "Hello World!"
111
+ # end
112
+ #
113
+ # def perform
114
+ # "Hello World!"
115
+ # end
116
+ # end
117
+ #
94
118
  def around_perform(*filters, &blk)
95
119
  set_callback(:perform, :around, *filters, &blk)
96
120
  end
@@ -154,5 +178,21 @@ module ActiveJob
154
178
  set_callback(:enqueue, :around, *filters, &blk)
155
179
  end
156
180
  end
181
+
182
+ private
183
+ def halted_callback_hook(_filter, name) # :nodoc:
184
+ return super unless %i(enqueue perform).include?(name.to_sym)
185
+ callbacks = public_send("_#{name}_callbacks")
186
+
187
+ if !self.class.skip_after_callbacks_if_terminated && callbacks.any? { |c| c.kind == :after }
188
+ ActiveSupport::Deprecation.warn(<<~EOM)
189
+ In Rails 6.2, `after_enqueue`/`after_perform` callbacks no longer run if `before_enqueue`/`before_perform` respectively halts with `throw :abort`.
190
+ To enable this behavior, uncomment the `config.active_job.skip_after_callbacks_if_terminated` config
191
+ in the new 6.1 framework defaults initializer.
192
+ EOM
193
+ end
194
+
195
+ super
196
+ end
157
197
  end
158
198
  end
@@ -85,7 +85,7 @@ module ActiveJob
85
85
  @priority = self.class.priority
86
86
  @executions = 0
87
87
  @exception_executions = {}
88
- @timezone = Time.zone.try(:name)
88
+ @timezone = Time.zone&.name
89
89
  end
90
90
  ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
91
91
 
@@ -142,7 +142,7 @@ module ActiveJob
142
142
  self.executions = job_data["executions"]
143
143
  self.exception_executions = job_data["exception_executions"]
144
144
  self.locale = job_data["locale"] || I18n.locale.to_s
145
- self.timezone = job_data["timezone"] || Time.zone.try(:name)
145
+ self.timezone = job_data["timezone"] || Time.zone&.name
146
146
  self.enqueued_at = job_data["enqueued_at"]
147
147
  end
148
148
 
@@ -54,9 +54,9 @@ module ActiveJob
54
54
 
55
55
  run_callbacks :enqueue do
56
56
  if scheduled_at
57
- self.class.queue_adapter.enqueue_at self, scheduled_at
57
+ queue_adapter.enqueue_at self, scheduled_at
58
58
  else
59
- self.class.queue_adapter.enqueue self
59
+ queue_adapter.enqueue self
60
60
  end
61
61
 
62
62
  successfully_enqueued = true
@@ -65,17 +65,7 @@ module ActiveJob
65
65
  if successfully_enqueued
66
66
  self
67
67
  else
68
- if self.class.return_false_on_aborted_enqueue
69
- false
70
- else
71
- ActiveSupport::Deprecation.warn(
72
- "Rails 6.1 will return false when the enqueuing is aborted. Make sure your code doesn't depend on it" \
73
- " returning the instance of the job and set `config.active_job.return_false_on_aborted_enqueue = true`" \
74
- " to remove the deprecations."
75
- )
76
-
77
- self
78
- end
68
+ false
79
69
  end
80
70
  end
81
71
  end
@@ -7,6 +7,10 @@ module ActiveJob
7
7
  module Exceptions
8
8
  extend ActiveSupport::Concern
9
9
 
10
+ included do
11
+ class_attribute :retry_jitter, instance_accessor: false, instance_predicate: false, default: 0.0
12
+ end
13
+
10
14
  module ClassMethods
11
15
  # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts.
12
16
  # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to
@@ -18,23 +22,25 @@ module ActiveJob
18
22
  #
19
23
  # ==== Options
20
24
  # * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
21
- # as a computing proc that the number of executions so far as an argument, or as a symbol reference of
22
- # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt>
23
- # (first wait 3s, then 18s, then 83s, etc)
25
+ # as a computing proc that takes the number of executions so far as an argument, or as a symbol reference of
26
+ # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2</tt>
27
+ # (first wait ~3s, then ~18s, then ~83s, etc)
24
28
  # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts)
25
29
  # * <tt>:queue</tt> - Re-enqueues the job on a different queue
26
30
  # * <tt>:priority</tt> - Re-enqueues the job with a different priority
31
+ # * <tt>:jitter</tt> - 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)
27
32
  #
28
33
  # ==== Examples
29
34
  #
30
35
  # class RemoteServiceJob < ActiveJob::Base
31
- # retry_on CustomAppException # defaults to 3s wait, 5 attempts
36
+ # retry_on CustomAppException # defaults to ~3s wait, 5 attempts
32
37
  # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
33
38
  #
34
39
  # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
35
40
  # retry_on Net::OpenTimeout, Timeout::Error, wait: :exponentially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
36
41
  # # To retry at most 10 times for each individual exception:
37
42
  # # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
43
+ # # retry_on Net::ReadTimeout, wait: 5.seconds, jitter: 0.30, attempts: 10
38
44
  # # retry_on Timeout::Error, wait: :exponentially_longer, attempts: 10
39
45
  #
40
46
  # retry_on(YetAnotherCustomAppException) do |job, error|
@@ -47,12 +53,11 @@ module ActiveJob
47
53
  # # Might raise Net::OpenTimeout or Timeout::Error when the remote service is down
48
54
  # end
49
55
  # end
50
- def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
56
+ def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT)
51
57
  rescue_from(*exceptions) do |error|
52
58
  executions = executions_for(exceptions)
53
-
54
59
  if executions < attempts
55
- retry_job wait: determine_delay(seconds_or_duration_or_algorithm: wait, executions: executions), queue: queue, priority: priority, error: error
60
+ retry_job wait: determine_delay(seconds_or_duration_or_algorithm: wait, executions: executions, jitter: jitter), queue: queue, priority: priority, error: error
56
61
  else
57
62
  if block_given?
58
63
  instrument :retry_stopped, error: error do
@@ -115,22 +120,27 @@ module ActiveJob
115
120
  # end
116
121
  # end
117
122
  def retry_job(options = {})
118
- instrument :enqueue_retry, **options.slice(:error, :wait) do
123
+ instrument :enqueue_retry, options.slice(:error, :wait) do
119
124
  enqueue options
120
125
  end
121
126
  end
122
127
 
123
128
  private
124
- def determine_delay(seconds_or_duration_or_algorithm:, executions:)
129
+ JITTER_DEFAULT = Object.new
130
+ private_constant :JITTER_DEFAULT
131
+
132
+ def determine_delay(seconds_or_duration_or_algorithm:, executions:, jitter: JITTER_DEFAULT)
133
+ jitter = jitter == JITTER_DEFAULT ? self.class.retry_jitter : (jitter || 0.0)
134
+
125
135
  case seconds_or_duration_or_algorithm
126
136
  when :exponentially_longer
127
- (executions**4) + 2
128
- when ActiveSupport::Duration
129
- duration = seconds_or_duration_or_algorithm
130
- duration.to_i
131
- when Integer
132
- seconds = seconds_or_duration_or_algorithm
133
- seconds
137
+ delay = executions**4
138
+ delay_jitter = determine_jitter_for_delay(delay, jitter)
139
+ delay + delay_jitter + 2
140
+ when ActiveSupport::Duration, Integer
141
+ delay = seconds_or_duration_or_algorithm.to_i
142
+ delay_jitter = determine_jitter_for_delay(delay, jitter)
143
+ delay + delay_jitter
134
144
  when Proc
135
145
  algorithm = seconds_or_duration_or_algorithm
136
146
  algorithm.call(executions)
@@ -139,10 +149,9 @@ module ActiveJob
139
149
  end
140
150
  end
141
151
 
142
- def instrument(name, error: nil, wait: nil, &block)
143
- payload = { job: self, adapter: self.class.queue_adapter, error: error, wait: wait }
144
-
145
- ActiveSupport::Notifications.instrument("#{name}.active_job", payload, &block)
152
+ def determine_jitter_for_delay(delay, jitter)
153
+ return 0.0 if jitter.zero?
154
+ Kernel.rand * delay * jitter
146
155
  end
147
156
 
148
157
  def executions_for(exceptions)