activejob 6.0.5 → 6.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46aa56a721f78240c64da5760a9527a91ff4b3045d81969a0a2340ada6b95f8d
4
- data.tar.gz: 715702a0ceab6578d327d7caca1c36fd4ba22617fca2e316e26d500c578a0d49
3
+ metadata.gz: dc9b631f7b43e77aa14e8fb65525b51ad82f92f5b0046d15d44c2e5f2429a2aa
4
+ data.tar.gz: a2e8afb6e5295337e82c0f0c3de726cbe11dcedba0f2f34c6c4323170a74dc13
5
5
  SHA512:
6
- metadata.gz: fd0774fc4293ebc79a8b22ae34bc048d52bdd85fc994dd2ed80752fae8224ce8bd910fdcd1bffa92d39287b61d20d015eadb96ce03dfa478ba822d2f20664f6e
7
- data.tar.gz: 5f73f97903ce7bb6695b6be2bf412e9b4629d8547540a8869f2bceb6d7b6428caab7438a6c6c9055fbccedb1347fc644932ec5208064be469742dafc1a1f3e06
6
+ metadata.gz: b30c316861f1ae07c363df9184ef9a306d796756429d11978166b3864263cc72ff521c6d27093e17f0e2e850b6921c3dd39702646d6f5b7a5a198221d1e68f4f
7
+ data.tar.gz: 99ed04c52a31748e94edd687fe93a48de660e44d29b5645597a278a97450405f57c28b92d69b98cf0f3ded97711952a58b700b9f90e2ad8dac5148b843920b17
data/CHANGELOG.md CHANGED
@@ -1,278 +1,130 @@
1
- ## Rails 6.0.5 (May 09, 2022) ##
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.4.8 (April 26, 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.4.7 (March 08, 2022) ##
12
-
13
- * No changes.
14
-
15
-
16
- ## Rails 6.0.4.6 (February 11, 2022) ##
17
-
18
- * No changes.
19
-
20
-
21
- ## Rails 6.0.4.5 (February 11, 2022) ##
22
-
23
- * No changes.
24
-
25
-
26
- ## Rails 6.0.4.4 (December 15, 2021) ##
27
-
28
- * No changes.
29
-
30
-
31
- ## Rails 6.0.4.3 (December 14, 2021) ##
32
-
33
- * No changes.
34
-
35
-
36
- ## Rails 6.0.4.2 (December 14, 2021) ##
37
-
38
- * No changes.
39
-
40
-
41
- ## Rails 6.0.4.1 (August 19, 2021) ##
42
-
43
- * No changes.
44
-
45
-
46
- ## Rails 6.0.4 (June 15, 2021) ##
47
-
48
- * No changes.
49
-
50
-
51
- ## Rails 6.0.3.7 (May 05, 2021) ##
52
-
53
- * No changes.
54
-
55
-
56
- ## Rails 6.0.3.6 (March 26, 2021) ##
57
-
58
- * No changes.
59
-
60
-
61
- ## Rails 6.0.3.5 (February 10, 2021) ##
62
-
63
- * No changes.
64
-
65
-
66
- ## Rails 6.0.3.4 (October 07, 2020) ##
67
-
68
- * No changes.
69
-
70
-
71
- ## Rails 6.0.3.3 (September 09, 2020) ##
72
-
73
- * No changes.
74
-
75
-
76
- ## Rails 6.0.3.2 (June 17, 2020) ##
77
-
78
- * No changes.
79
-
80
-
81
- ## Rails 6.0.3.1 (May 18, 2020) ##
82
-
83
- * No changes.
84
-
85
-
86
- ## Rails 6.0.3 (May 06, 2020) ##
13
+ *Rafael Mendonça França*
87
14
 
88
15
  * While using `perform_enqueued_jobs` test helper enqueued jobs must be stored for the later check with
89
16
  `assert_enqueued_with`.
90
17
 
91
18
  *Dmitry Polushkin*
92
19
 
93
- * Add queue name support to Que adapter
94
-
95
- *Brad Nauta*, *Wojciech Wnętrzak*
96
-
97
-
98
- ## Rails 6.0.2.2 (March 19, 2020) ##
99
-
100
- * No changes.
101
-
102
-
103
- ## Rails 6.0.2.1 (December 18, 2019) ##
104
-
105
- * No changes.
20
+ * `ActiveJob::TestCase#perform_enqueued_jobs` without a block removes performed jobs from the queue.
106
21
 
22
+ That way the helper can be called multiple times and not perform a job invocation multiple times.
107
23
 
108
- ## Rails 6.0.2 (December 13, 2019) ##
109
-
110
- * Allow Sidekiq access to the underlying job class.
111
-
112
- By having access to the Active Job class, Sidekiq can get access to any `sidekiq_options` which
113
- have been set on that Active Job type and serialize those options into Redis.
114
-
115
- https://github.com/mperham/sidekiq/blob/master/Changes.md#60
116
-
117
- *Mike Perham*
118
-
119
-
120
- ## Rails 6.0.1 (November 5, 2019) ##
121
-
122
- * No changes.
123
-
124
-
125
- ## Rails 6.0.0 (August 16, 2019) ##
126
-
127
- * `assert_enqueued_with` and `assert_performed_with` can now test jobs with relative delay.
128
-
129
- *Vlado Cingel*
130
-
131
-
132
- ## Rails 6.0.0.rc2 (July 22, 2019) ##
133
-
134
- * No changes.
135
-
136
-
137
- ## Rails 6.0.0.rc1 (April 24, 2019) ##
138
-
139
- * Use individual execution counters when calculating retry delay.
140
-
141
- *Patrik Bóna*
142
-
143
- * Make job argument assertions with `Time`, `ActiveSupport::TimeWithZone`, and `DateTime` work by dropping microseconds. Microsecond precision is lost during serialization.
144
-
145
- *Gannon McGibbon*
146
-
147
-
148
- ## Rails 6.0.0.beta3 (March 11, 2019) ##
149
-
150
- * No changes.
151
-
152
-
153
- ## Rails 6.0.0.beta2 (February 25, 2019) ##
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
+ ```
154
32
 
155
- * No changes.
33
+ *Étienne Barrié*
156
34
 
35
+ * `ActiveJob::TestCase#perform_enqueued_jobs` will no longer perform retries:
157
36
 
158
- ## 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.
159
40
 
160
- * Return false instead of the job instance when `enqueue` is aborted.
41
+ This change only affects `perform_enqueued_jobs` when no block is given.
161
42
 
162
- This will be the behavior in Rails 6.1 but it can be controlled now with
163
- `config.active_job.return_false_on_aborted_enqueue`.
43
+ *Edouard Chin*
164
44
 
165
- *Kir Shatrov*
45
+ * Add queue name support to Que adapter.
166
46
 
167
- * Keep executions for each specific declaration
47
+ *Brad Nauta*, *Wojciech Wnętrzak*
168
48
 
169
- Each `retry_on` declaration has now its own specific executions counter. Before it was
170
- shared between all executions of a job.
49
+ * Don't run `after_enqueue` and `after_perform` callbacks if the callback chain is halted.
171
50
 
172
- *Alberto Almagro*
51
+ class MyJob < ApplicationJob
52
+ before_enqueue { throw(:abort) }
53
+ after_enqueue { # won't enter here anymore }
54
+ end
173
55
 
174
- * Allow all assertion helpers that have a `only` and `except` keyword to accept
175
- 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`.
176
60
 
177
61
  *Edouard Chin*
178
62
 
179
- * Restore `HashWithIndifferentAccess` support to `ActiveJob::Arguments.deserialize`.
63
+ * Fix enqueuing and performing incorrect logging message.
180
64
 
181
- *Gannon McGibbon*
65
+ Jobs will no longer always log "Enqueued MyJob" or "Performed MyJob" when they actually didn't get enqueued/performed.
182
66
 
183
- * Include deserialized arguments in job instances returned from
184
- `assert_enqueued_with` and `assert_performed_with`
67
+ ```ruby
68
+ class MyJob < ApplicationJob
69
+ before_enqueue { throw(:abort) }
70
+ end
185
71
 
186
- *Alan Wu*
72
+ MyJob.perform_later # Will no longer log "Enqueued MyJob" since job wasn't even enqueued through adapter.
73
+ ```
187
74
 
188
- * Allow `assert_enqueued_with`/`assert_performed_with` methods to accept
189
- a proc for the `args` argument. This is useful to check if only a subset of arguments
190
- 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)
191
77
 
192
78
  *Edouard Chin*
193
79
 
194
- * `ActionDispatch::IntegrationTest` includes `ActiveJob::TestHelper` module by default.
195
-
196
- *Ricardo Díaz*
197
-
198
- * Added `enqueue_retry.active_job`, `retry_stopped.active_job`, and `discard.active_job` hooks.
80
+ * Add an option to disable logging of the job arguments when enqueuing and executing the job.
199
81
 
200
- *steves*
82
+ class SensitiveJob < ApplicationJob
83
+ self.log_arguments = false
201
84
 
202
- * Allow `assert_performed_with` to be called without a block.
85
+ def perform(my_sensitive_argument)
86
+ end
87
+ end
203
88
 
204
- *bogdanvlviv*
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.
205
91
 
206
- * Execution of `assert_performed_jobs`, and `assert_no_performed_jobs`
207
- without a block should respect passed `:except`, `:only`, and `:queue` options.
92
+ *Rafael Mendonça França*
208
93
 
209
- *bogdanvlviv*
94
+ * Changes in `queue_name_prefix` of a job no longer affects all other jobs.
210
95
 
211
- * Allow `:queue` option to job assertions and helpers.
96
+ Fixes #37084.
212
97
 
213
- *bogdanvlviv*
98
+ *Lucas Mansur*
214
99
 
215
- * Allow `perform_enqueued_jobs` to be called without a block.
216
-
217
- 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.
218
101
 
219
102
  *Kevin Deisz*
220
103
 
221
- * Move `enqueue`/`enqueue_at` notifications to an around callback.
222
-
223
- Improves timing accuracy over the old after callback by including
224
- time spent writing to the adapter's IO implementation.
225
-
226
- *Zach Kemp*
227
-
228
- * Allow call `assert_enqueued_with` with no block.
229
-
230
- Example:
231
- ```
232
- def test_assert_enqueued_with
233
- MyJob.perform_later(1,2,3)
234
- assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low')
235
-
236
- MyJob.set(wait_until: Date.tomorrow.noon).perform_later
237
- assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon)
238
- end
239
- ```
240
-
241
- *bogdanvlviv*
242
-
243
- * Allow passing multiple exceptions to `retry_on`, and `discard_on`.
244
-
245
- *George Claghorn*
246
-
247
- * Pass the error instance as the second parameter of block executed by `discard_on`.
104
+ * Log potential matches in `assert_enqueued_with` and `assert_performed_with`.
248
105
 
249
- Fixes #32853.
106
+ *Gareth du Plooy*
250
107
 
251
- *Yuji Yaginuma*
108
+ * Add `at` argument to the `perform_enqueued_jobs` test helper.
252
109
 
253
- * Remove support for Qu gem.
110
+ *John Crepezzi*, *Eileen Uchitelle*
254
111
 
255
- Reasons are that the Qu gem wasn't compatible since Rails 5.1,
256
- gem development was stopped in 2014 and maintainers have
257
- confirmed its demise. See issue #32273
258
-
259
- *Alberto Almagro*
260
-
261
- * Add support for timezones to Active Job.
262
-
263
- Record what was the current timezone in effect when the job was
264
- enqueued and then restore when the job is executed in same way
265
- that the current locale is recorded and restored.
112
+ * `assert_enqueued_with` and `assert_performed_with` can now test jobs with relative delay.
266
113
 
267
- *Andrew White*
114
+ *Vlado Cingel*
268
115
 
269
- * Rails 6 requires Ruby 2.5.0 or newer.
116
+ * Add jitter to `ActiveJob::Exceptions.retry_on`.
270
117
 
271
- *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`.
272
122
 
273
- * Add support to define custom argument serializers.
123
+ ```ruby
124
+ retry_on(MyError, wait: :exponentially_longer, jitter: 0.30)
125
+ ```
274
126
 
275
- *Evgenii Pecherkin*, *Rafael Mendonça França*
127
+ *Anthony Ross*
276
128
 
277
129
 
278
- 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)
@@ -29,13 +29,21 @@ module ActiveJob
29
29
 
30
30
  # Performs the job immediately. The job is not sent to the queuing adapter
31
31
  # but directly executed by blocking the execution of others until it's finished.
32
+ # `perform_now` returns the value of your job's `perform` method.
32
33
  #
33
- # MyJob.new(*args).perform_now
34
+ # class MyJob < ActiveJob::Base
35
+ # def perform
36
+ # "Hello World!"
37
+ # end
38
+ # end
39
+ #
40
+ # puts MyJob.new(*args).perform_now # => "Hello World!"
34
41
  def perform_now
35
42
  # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
36
43
  self.executions = (executions || 0) + 1
37
44
 
38
45
  deserialize_arguments_if_needed
46
+
39
47
  run_callbacks :perform do
40
48
  perform(*arguments)
41
49
  end