activejob 7.0.4 → 7.1.5.1
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 +4 -4
- data/CHANGELOG.md +222 -94
- data/MIT-LICENSE +1 -1
- data/README.md +4 -4
- data/lib/active_job/arguments.rb +28 -31
- data/lib/active_job/base.rb +1 -1
- data/lib/active_job/callbacks.rb +3 -5
- data/lib/active_job/configured_job.rb +4 -0
- data/lib/active_job/core.rb +26 -6
- data/lib/active_job/deprecator.rb +7 -0
- data/lib/active_job/enqueuing.rb +31 -3
- data/lib/active_job/exceptions.rb +52 -8
- data/lib/active_job/execution.rb +11 -2
- data/lib/active_job/gem_version.rb +4 -4
- data/lib/active_job/instrumentation.rb +18 -10
- data/lib/active_job/log_subscriber.rb +81 -9
- data/lib/active_job/queue_adapter.rb +13 -2
- data/lib/active_job/queue_adapters/async_adapter.rb +2 -2
- data/lib/active_job/queue_adapters/backburner_adapter.rb +7 -3
- data/lib/active_job/queue_adapters/delayed_job_adapter.rb +3 -1
- data/lib/active_job/queue_adapters/inline_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/queue_classic_adapter.rb +4 -4
- data/lib/active_job/queue_adapters/resque_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +42 -14
- data/lib/active_job/queue_adapters/sneakers_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +3 -3
- data/lib/active_job/queue_adapters/test_adapter.rb +3 -3
- data/lib/active_job/queue_adapters.rb +8 -7
- data/lib/active_job/queue_name.rb +1 -2
- data/lib/active_job/queue_priority.rb +18 -1
- data/lib/active_job/railtie.rb +26 -7
- data/lib/active_job/serializers/big_decimal_serializer.rb +22 -0
- data/lib/active_job/serializers/duration_serializer.rb +4 -2
- data/lib/active_job/serializers/time_with_zone_serializer.rb +11 -2
- data/lib/active_job/serializers.rb +7 -3
- data/lib/active_job/test_helper.rb +32 -14
- data/lib/active_job/version.rb +1 -1
- data/lib/active_job.rb +29 -4
- data/lib/rails/generators/job/USAGE +19 -0
- data/lib/rails/generators/job/job_generator.rb +6 -2
- data/lib/rails/generators/job/templates/job.rb.tt +1 -1
- metadata +14 -12
- data/lib/active_job/queue_adapters/que_adapter.rb +0 -61
data/lib/active_job/core.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveJob
|
4
|
+
# = Active Job \Core
|
5
|
+
#
|
4
6
|
# Provides general behavior that will be included into every Active Job
|
5
7
|
# object that inherits from ActiveJob::Base.
|
6
8
|
module Core
|
@@ -10,8 +12,10 @@ module ActiveJob
|
|
10
12
|
attr_accessor :arguments
|
11
13
|
attr_writer :serialized_arguments
|
12
14
|
|
13
|
-
#
|
14
|
-
|
15
|
+
# Time when the job should be performed
|
16
|
+
attr_reader :scheduled_at
|
17
|
+
|
18
|
+
attr_reader :_scheduled_at_time # :nodoc:
|
15
19
|
|
16
20
|
# Job Identifier
|
17
21
|
attr_accessor :job_id
|
@@ -92,6 +96,8 @@ module ActiveJob
|
|
92
96
|
@arguments = arguments
|
93
97
|
@job_id = SecureRandom.uuid
|
94
98
|
@queue_name = self.class.queue_name
|
99
|
+
@scheduled_at = nil
|
100
|
+
@_scheduled_at_time = nil
|
95
101
|
@priority = self.class.priority
|
96
102
|
@executions = 0
|
97
103
|
@exception_executions = {}
|
@@ -113,7 +119,8 @@ module ActiveJob
|
|
113
119
|
"exception_executions" => exception_executions,
|
114
120
|
"locale" => I18n.locale.to_s,
|
115
121
|
"timezone" => timezone,
|
116
|
-
"enqueued_at" => Time.now.utc.iso8601
|
122
|
+
"enqueued_at" => Time.now.utc.iso8601(9),
|
123
|
+
"scheduled_at" => _scheduled_at_time ? _scheduled_at_time.utc.iso8601(9) : nil,
|
117
124
|
}
|
118
125
|
end
|
119
126
|
|
@@ -153,19 +160,32 @@ module ActiveJob
|
|
153
160
|
self.exception_executions = job_data["exception_executions"]
|
154
161
|
self.locale = job_data["locale"] || I18n.locale.to_s
|
155
162
|
self.timezone = job_data["timezone"] || Time.zone&.name
|
156
|
-
self.enqueued_at = job_data["enqueued_at"]
|
163
|
+
self.enqueued_at = Time.iso8601(job_data["enqueued_at"]) if job_data["enqueued_at"]
|
164
|
+
self.scheduled_at = Time.iso8601(job_data["scheduled_at"]) if job_data["scheduled_at"]
|
157
165
|
end
|
158
166
|
|
159
167
|
# Configures the job with the given options.
|
160
168
|
def set(options = {}) # :nodoc:
|
161
|
-
self.scheduled_at = options[:wait].seconds.from_now
|
162
|
-
self.scheduled_at = options[:wait_until]
|
169
|
+
self.scheduled_at = options[:wait].seconds.from_now if options[:wait]
|
170
|
+
self.scheduled_at = options[:wait_until] if options[:wait_until]
|
163
171
|
self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
|
164
172
|
self.priority = options[:priority].to_i if options[:priority]
|
165
173
|
|
166
174
|
self
|
167
175
|
end
|
168
176
|
|
177
|
+
def scheduled_at=(value)
|
178
|
+
@_scheduled_at_time = if value&.is_a?(Numeric)
|
179
|
+
ActiveJob.deprecator.warn(<<~MSG.squish)
|
180
|
+
Assigning a numeric/epoch value to scheduled_at is deprecated. Use a Time object instead.
|
181
|
+
MSG
|
182
|
+
Time.at(value)
|
183
|
+
else
|
184
|
+
value
|
185
|
+
end
|
186
|
+
@scheduled_at = value
|
187
|
+
end
|
188
|
+
|
169
189
|
private
|
170
190
|
def serialize_arguments_if_needed(arguments)
|
171
191
|
if arguments_serialized?
|
data/lib/active_job/enqueuing.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_job/arguments"
|
4
|
-
|
5
3
|
module ActiveJob
|
6
4
|
# Provides behavior for enqueuing jobs.
|
7
5
|
|
@@ -9,6 +7,36 @@ module ActiveJob
|
|
9
7
|
# why the adapter was unexpectedly unable to enqueue a job.
|
10
8
|
class EnqueueError < StandardError; end
|
11
9
|
|
10
|
+
class << self
|
11
|
+
# Push many jobs onto the queue at once without running enqueue callbacks.
|
12
|
+
# Queue adapters may communicate the enqueue status of each job by setting
|
13
|
+
# successfully_enqueued and/or enqueue_error on the passed-in job instances.
|
14
|
+
def perform_all_later(*jobs)
|
15
|
+
jobs.flatten!
|
16
|
+
jobs.group_by(&:queue_adapter).each do |queue_adapter, adapter_jobs|
|
17
|
+
instrument_enqueue_all(queue_adapter, adapter_jobs) do
|
18
|
+
if queue_adapter.respond_to?(:enqueue_all)
|
19
|
+
queue_adapter.enqueue_all(adapter_jobs)
|
20
|
+
else
|
21
|
+
adapter_jobs.each do |job|
|
22
|
+
job.successfully_enqueued = false
|
23
|
+
if job.scheduled_at
|
24
|
+
queue_adapter.enqueue_at(job, job._scheduled_at_time.to_f)
|
25
|
+
else
|
26
|
+
queue_adapter.enqueue(job)
|
27
|
+
end
|
28
|
+
job.successfully_enqueued = true
|
29
|
+
rescue EnqueueError => e
|
30
|
+
job.enqueue_error = e
|
31
|
+
end
|
32
|
+
adapter_jobs.count(&:successfully_enqueued?)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
12
40
|
module Enqueuing
|
13
41
|
extend ActiveSupport::Concern
|
14
42
|
|
@@ -62,7 +90,7 @@ module ActiveJob
|
|
62
90
|
|
63
91
|
run_callbacks :enqueue do
|
64
92
|
if scheduled_at
|
65
|
-
queue_adapter.enqueue_at self,
|
93
|
+
queue_adapter.enqueue_at self, _scheduled_at_time.to_f
|
66
94
|
else
|
67
95
|
queue_adapter.enqueue self
|
68
96
|
end
|
@@ -9,6 +9,7 @@ module ActiveJob
|
|
9
9
|
|
10
10
|
included do
|
11
11
|
class_attribute :retry_jitter, instance_accessor: false, instance_predicate: false, default: 0.0
|
12
|
+
class_attribute :after_discard_procs, default: []
|
12
13
|
end
|
13
14
|
|
14
15
|
module ClassMethods
|
@@ -20,10 +21,13 @@ module ActiveJob
|
|
20
21
|
# You can also pass a block that'll be invoked if the retry attempts fail for custom logic rather than letting
|
21
22
|
# the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter.
|
22
23
|
#
|
24
|
+
# `retry_on` and `discard_on` handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for
|
25
|
+
# which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if any.
|
26
|
+
#
|
23
27
|
# ==== Options
|
24
28
|
# * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
|
25
29
|
# as a computing proc that takes the number of executions so far as an argument, or as a symbol reference of
|
26
|
-
# <tt>:
|
30
|
+
# <tt>:polynomially_longer</tt>, which applies the wait algorithm of <tt>((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2</tt>
|
27
31
|
# (first wait ~3s, then ~18s, then ~83s, etc)
|
28
32
|
# * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts) or a symbol reference of <tt>:unlimited</tt>
|
29
33
|
# to retry the job until it succeeds
|
@@ -39,11 +43,11 @@ module ActiveJob
|
|
39
43
|
# retry_on CustomInfrastructureException, wait: 5.minutes, attempts: :unlimited
|
40
44
|
#
|
41
45
|
# retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
|
42
|
-
# retry_on Net::OpenTimeout, Timeout::Error, wait: :
|
46
|
+
# retry_on Net::OpenTimeout, Timeout::Error, wait: :polynomially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
|
43
47
|
# # To retry at most 10 times for each individual exception:
|
44
|
-
# # retry_on Net::OpenTimeout, wait: :
|
48
|
+
# # retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10
|
45
49
|
# # retry_on Net::ReadTimeout, wait: 5.seconds, jitter: 0.30, attempts: 10
|
46
|
-
# # retry_on Timeout::Error, wait: :
|
50
|
+
# # retry_on Timeout::Error, wait: :polynomially_longer, attempts: 10
|
47
51
|
#
|
48
52
|
# retry_on(YetAnotherCustomAppException) do |job, error|
|
49
53
|
# ExceptionNotifier.caught(error)
|
@@ -56,6 +60,12 @@ module ActiveJob
|
|
56
60
|
# end
|
57
61
|
# end
|
58
62
|
def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT)
|
63
|
+
if wait == :exponentially_longer
|
64
|
+
ActiveJob.deprecator.warn(<<~MSG.squish)
|
65
|
+
`wait: :exponentially_longer` will actually wait polynomially longer and is therefore deprecated.
|
66
|
+
Prefer `wait: :polynomially_longer` to avoid confusion and keep the same behavior.
|
67
|
+
MSG
|
68
|
+
end
|
59
69
|
rescue_from(*exceptions) do |error|
|
60
70
|
executions = executions_for(exceptions)
|
61
71
|
if attempts == :unlimited || executions < attempts
|
@@ -65,8 +75,10 @@ module ActiveJob
|
|
65
75
|
instrument :retry_stopped, error: error do
|
66
76
|
yield self, error
|
67
77
|
end
|
78
|
+
run_after_discard_procs(error)
|
68
79
|
else
|
69
80
|
instrument :retry_stopped, error: error
|
81
|
+
run_after_discard_procs(error)
|
70
82
|
raise error
|
71
83
|
end
|
72
84
|
end
|
@@ -78,6 +90,9 @@ module ActiveJob
|
|
78
90
|
#
|
79
91
|
# You can also pass a block that'll be invoked. This block is yielded with the job instance as the first and the error instance as the second parameter.
|
80
92
|
#
|
93
|
+
# `retry_on` and `discard_on` handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for
|
94
|
+
# which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if any.
|
95
|
+
#
|
81
96
|
# ==== Example
|
82
97
|
#
|
83
98
|
# class SearchIndexingJob < ActiveJob::Base
|
@@ -95,14 +110,32 @@ module ActiveJob
|
|
95
110
|
rescue_from(*exceptions) do |error|
|
96
111
|
instrument :discard, error: error do
|
97
112
|
yield self, error if block_given?
|
113
|
+
run_after_discard_procs(error)
|
98
114
|
end
|
99
115
|
end
|
100
116
|
end
|
117
|
+
|
118
|
+
# A block to run when a job is about to be discarded for any reason.
|
119
|
+
#
|
120
|
+
# ==== Example
|
121
|
+
#
|
122
|
+
# class WorkJob < ActiveJob::Base
|
123
|
+
# after_discard do |job, exception|
|
124
|
+
# ExceptionNotifier.report(exception)
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# ...
|
128
|
+
#
|
129
|
+
# end
|
130
|
+
def after_discard(&blk)
|
131
|
+
self.after_discard_procs += [blk]
|
132
|
+
end
|
101
133
|
end
|
102
134
|
|
103
|
-
# Reschedules the job to be re-executed. This is useful in combination
|
104
|
-
#
|
105
|
-
# you can ask Active Job to retry
|
135
|
+
# Reschedules the job to be re-executed. This is useful in combination with
|
136
|
+
# {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from].
|
137
|
+
# When you rescue an exception from your job you can ask Active Job to retry
|
138
|
+
# performing your job.
|
106
139
|
#
|
107
140
|
# ==== Options
|
108
141
|
# * <tt>:wait</tt> - Enqueues the job with the specified delay in seconds
|
@@ -135,7 +168,8 @@ module ActiveJob
|
|
135
168
|
jitter = jitter == JITTER_DEFAULT ? self.class.retry_jitter : (jitter || 0.0)
|
136
169
|
|
137
170
|
case seconds_or_duration_or_algorithm
|
138
|
-
when :exponentially_longer
|
171
|
+
when :exponentially_longer, :polynomially_longer
|
172
|
+
# This delay uses a polynomial backoff strategy, which was previously misnamed as exponential
|
139
173
|
delay = executions**4
|
140
174
|
delay_jitter = determine_jitter_for_delay(delay, jitter)
|
141
175
|
delay + delay_jitter + 2
|
@@ -164,5 +198,15 @@ module ActiveJob
|
|
164
198
|
executions
|
165
199
|
end
|
166
200
|
end
|
201
|
+
|
202
|
+
def run_after_discard_procs(exception)
|
203
|
+
exceptions = []
|
204
|
+
after_discard_procs.each do |blk|
|
205
|
+
instance_exec(self, exception, &blk)
|
206
|
+
rescue StandardError => e
|
207
|
+
exceptions << e
|
208
|
+
end
|
209
|
+
raise exceptions.last unless exceptions.empty?
|
210
|
+
end
|
167
211
|
end
|
168
212
|
end
|
data/lib/active_job/execution.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/rescuable"
|
4
|
-
require "active_job/arguments"
|
5
4
|
|
6
5
|
module ActiveJob
|
6
|
+
# = Active Job \Execution
|
7
|
+
#
|
8
|
+
# Provides methods to execute jobs immediately, and wraps job execution so
|
9
|
+
# that exceptions configured with
|
10
|
+
# {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
|
11
|
+
# are handled.
|
7
12
|
module Execution
|
8
13
|
extend ActiveSupport::Concern
|
9
14
|
include ActiveSupport::Rescuable
|
@@ -45,7 +50,11 @@ module ActiveJob
|
|
45
50
|
|
46
51
|
_perform_job
|
47
52
|
rescue Exception => exception
|
48
|
-
rescue_with_handler(exception)
|
53
|
+
handled = rescue_with_handler(exception)
|
54
|
+
return handled if handled
|
55
|
+
|
56
|
+
run_after_discard_procs(exception)
|
57
|
+
raise
|
49
58
|
end
|
50
59
|
|
51
60
|
def perform(*)
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveJob
|
4
|
-
# Returns the currently loaded version of Active Job as a
|
4
|
+
# Returns the currently loaded version of Active Job as a +Gem::Version+.
|
5
5
|
def self.gem_version
|
6
6
|
Gem::Version.new VERSION::STRING
|
7
7
|
end
|
8
8
|
|
9
9
|
module VERSION
|
10
10
|
MAJOR = 7
|
11
|
-
MINOR =
|
12
|
-
TINY =
|
13
|
-
PRE =
|
11
|
+
MINOR = 1
|
12
|
+
TINY = 5
|
13
|
+
PRE = "1"
|
14
14
|
|
15
15
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
16
|
end
|
@@ -1,6 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveJob
|
4
|
+
class << self
|
5
|
+
private
|
6
|
+
def instrument_enqueue_all(queue_adapter, jobs)
|
7
|
+
payload = { adapter: queue_adapter, jobs: jobs }
|
8
|
+
ActiveSupport::Notifications.instrument("enqueue_all.active_job", payload) do
|
9
|
+
result = yield payload
|
10
|
+
payload[:enqueued_count] = result
|
11
|
+
result
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
4
16
|
module Instrumentation # :nodoc:
|
5
17
|
extend ActiveSupport::Concern
|
6
18
|
|
@@ -21,19 +33,15 @@ module ActiveJob
|
|
21
33
|
end
|
22
34
|
|
23
35
|
def instrument(operation, payload = {}, &block)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
if defined?(@_halted_callback_hook_called) && @_halted_callback_hook_called
|
28
|
-
event_payload[:aborted] = true
|
29
|
-
@_halted_callback_hook_called = nil
|
30
|
-
end
|
36
|
+
payload[:job] = self
|
37
|
+
payload[:adapter] = queue_adapter
|
31
38
|
|
39
|
+
ActiveSupport::Notifications.instrument("#{operation}.active_job", payload) do
|
40
|
+
value = block.call if block
|
41
|
+
payload[:aborted] = @_halted_callback_hook_called if defined?(@_halted_callback_hook_called)
|
42
|
+
@_halted_callback_hook_called = nil
|
32
43
|
value
|
33
44
|
end
|
34
|
-
|
35
|
-
ActiveSupport::Notifications.instrument \
|
36
|
-
"#{operation}.active_job", payload.merge(adapter: queue_adapter, job: self), &enhanced_block
|
37
45
|
end
|
38
46
|
|
39
47
|
def halted_callback_hook(*)
|
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/string/filters"
|
4
3
|
require "active_support/log_subscriber"
|
5
4
|
|
6
5
|
module ActiveJob
|
7
6
|
class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
|
7
|
+
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
8
|
+
|
8
9
|
def enqueue(event)
|
9
10
|
job = event.payload[:job]
|
10
|
-
ex = event.payload[:exception_object]
|
11
|
+
ex = event.payload[:exception_object] || job.enqueue_error
|
11
12
|
|
12
13
|
if ex
|
13
14
|
error do
|
@@ -23,10 +24,11 @@ module ActiveJob
|
|
23
24
|
end
|
24
25
|
end
|
25
26
|
end
|
27
|
+
subscribe_log_level :enqueue, :info
|
26
28
|
|
27
29
|
def enqueue_at(event)
|
28
30
|
job = event.payload[:job]
|
29
|
-
ex = event.payload[:exception_object]
|
31
|
+
ex = event.payload[:exception_object] || job.enqueue_error
|
30
32
|
|
31
33
|
if ex
|
32
34
|
error do
|
@@ -42,13 +44,44 @@ module ActiveJob
|
|
42
44
|
end
|
43
45
|
end
|
44
46
|
end
|
47
|
+
subscribe_log_level :enqueue_at, :info
|
48
|
+
|
49
|
+
def enqueue_all(event)
|
50
|
+
info do
|
51
|
+
jobs = event.payload[:jobs]
|
52
|
+
adapter = event.payload[:adapter]
|
53
|
+
enqueued_count = event.payload[:enqueued_count]
|
54
|
+
|
55
|
+
if enqueued_count == jobs.size
|
56
|
+
enqueued_jobs_message(adapter, jobs)
|
57
|
+
elsif jobs.any?(&:successfully_enqueued?)
|
58
|
+
enqueued_jobs = jobs.select(&:successfully_enqueued?)
|
59
|
+
|
60
|
+
failed_enqueue_count = jobs.size - enqueued_count
|
61
|
+
if failed_enqueue_count == 0
|
62
|
+
enqueued_jobs_message(adapter, enqueued_jobs)
|
63
|
+
else
|
64
|
+
"#{enqueued_jobs_message(adapter, enqueued_jobs)}. "\
|
65
|
+
"Failed enqueuing #{failed_enqueue_count} #{'job'.pluralize(failed_enqueue_count)}"
|
66
|
+
end
|
67
|
+
else
|
68
|
+
failed_enqueue_count = jobs.size - enqueued_count
|
69
|
+
"Failed enqueuing #{failed_enqueue_count} #{'job'.pluralize(failed_enqueue_count)} "\
|
70
|
+
"to #{ActiveJob.adapter_name(adapter)}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
subscribe_log_level :enqueue_all, :info
|
45
75
|
|
46
76
|
def perform_start(event)
|
47
77
|
info do
|
48
78
|
job = event.payload[:job]
|
49
|
-
|
79
|
+
enqueue_info = job.enqueued_at.present? ? " enqueued at #{job.enqueued_at.utc.iso8601(9)}" : ""
|
80
|
+
|
81
|
+
"Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + enqueue_info + args_info(job)
|
50
82
|
end
|
51
83
|
end
|
84
|
+
subscribe_log_level :perform_start, :info
|
52
85
|
|
53
86
|
def perform(event)
|
54
87
|
job = event.payload[:job]
|
@@ -67,6 +100,7 @@ module ActiveJob
|
|
67
100
|
end
|
68
101
|
end
|
69
102
|
end
|
103
|
+
subscribe_log_level :perform, :info
|
70
104
|
|
71
105
|
def enqueue_retry(event)
|
72
106
|
job = event.payload[:job]
|
@@ -75,34 +109,37 @@ module ActiveJob
|
|
75
109
|
|
76
110
|
info do
|
77
111
|
if ex
|
78
|
-
"Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
|
112
|
+
"Retrying #{job.class} (Job ID: #{job.job_id}) after #{job.executions} attempts in #{wait.to_i} seconds, due to a #{ex.class} (#{ex.message})."
|
79
113
|
else
|
80
|
-
"Retrying #{job.class} in #{wait.to_i} seconds."
|
114
|
+
"Retrying #{job.class} (Job ID: #{job.job_id}) after #{job.executions} attempts in #{wait.to_i} seconds."
|
81
115
|
end
|
82
116
|
end
|
83
117
|
end
|
118
|
+
subscribe_log_level :enqueue_retry, :info
|
84
119
|
|
85
120
|
def retry_stopped(event)
|
86
121
|
job = event.payload[:job]
|
87
122
|
ex = event.payload[:error]
|
88
123
|
|
89
124
|
error do
|
90
|
-
"Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
|
125
|
+
"Stopped retrying #{job.class} (Job ID: #{job.job_id}) due to a #{ex.class} (#{ex.message}), which reoccurred on #{job.executions} attempts."
|
91
126
|
end
|
92
127
|
end
|
128
|
+
subscribe_log_level :enqueue_retry, :error
|
93
129
|
|
94
130
|
def discard(event)
|
95
131
|
job = event.payload[:job]
|
96
132
|
ex = event.payload[:error]
|
97
133
|
|
98
134
|
error do
|
99
|
-
"Discarded #{job.class} due to a #{ex.class}."
|
135
|
+
"Discarded #{job.class} (Job ID: #{job.job_id}) due to a #{ex.class} (#{ex.message})."
|
100
136
|
end
|
101
137
|
end
|
138
|
+
subscribe_log_level :discard, :error
|
102
139
|
|
103
140
|
private
|
104
141
|
def queue_name(event)
|
105
|
-
event.payload[:adapter]
|
142
|
+
ActiveJob.adapter_name(event.payload[:adapter]) + "(#{event.payload[:job].queue_name})"
|
106
143
|
end
|
107
144
|
|
108
145
|
def args_info(job)
|
@@ -134,6 +171,41 @@ module ActiveJob
|
|
134
171
|
def logger
|
135
172
|
ActiveJob::Base.logger
|
136
173
|
end
|
174
|
+
|
175
|
+
def info(progname = nil, &block)
|
176
|
+
return unless super
|
177
|
+
|
178
|
+
if ActiveJob.verbose_enqueue_logs
|
179
|
+
log_enqueue_source
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def error(progname = nil, &block)
|
184
|
+
return unless super
|
185
|
+
|
186
|
+
if ActiveJob.verbose_enqueue_logs
|
187
|
+
log_enqueue_source
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def log_enqueue_source
|
192
|
+
source = extract_enqueue_source_location(caller)
|
193
|
+
|
194
|
+
if source
|
195
|
+
logger.info("↳ #{source}")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def extract_enqueue_source_location(locations)
|
200
|
+
backtrace_cleaner.clean(locations.lazy).first
|
201
|
+
end
|
202
|
+
|
203
|
+
def enqueued_jobs_message(adapter, enqueued_jobs)
|
204
|
+
enqueued_count = enqueued_jobs.size
|
205
|
+
job_classes_counts = enqueued_jobs.map(&:class).tally.sort_by { |_k, v| -v }
|
206
|
+
"Enqueued #{enqueued_count} #{'job'.pluralize(enqueued_count)} to #{ActiveJob.adapter_name(adapter)}"\
|
207
|
+
" (#{job_classes_counts.map { |klass, count| "#{count} #{klass}" }.join(', ')})"
|
208
|
+
end
|
137
209
|
end
|
138
210
|
end
|
139
211
|
|
@@ -3,7 +3,18 @@
|
|
3
3
|
require "active_support/core_ext/string/inflections"
|
4
4
|
|
5
5
|
module ActiveJob
|
6
|
-
|
6
|
+
class << self
|
7
|
+
def adapter_name(adapter) # :nodoc:
|
8
|
+
return adapter.queue_adapter_name if adapter.respond_to?(:queue_adapter_name)
|
9
|
+
|
10
|
+
adapter_class = adapter.is_a?(Module) ? adapter : adapter.class
|
11
|
+
"#{adapter_class.name.demodulize.delete_suffix('Adapter')}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# = Active Job Queue adapter
|
16
|
+
#
|
17
|
+
# The +ActiveJob::QueueAdapter+ module is used to load the
|
7
18
|
# correct adapter. The default queue adapter is the +:async+ queue.
|
8
19
|
module QueueAdapter # :nodoc:
|
9
20
|
extend ActiveSupport::Concern
|
@@ -41,7 +52,7 @@ module ActiveJob
|
|
41
52
|
assign_adapter(name_or_adapter.to_s, queue_adapter)
|
42
53
|
else
|
43
54
|
if queue_adapter?(name_or_adapter)
|
44
|
-
adapter_name =
|
55
|
+
adapter_name = ActiveJob.adapter_name(name_or_adapter).underscore
|
45
56
|
assign_adapter(adapter_name, name_or_adapter)
|
46
57
|
else
|
47
58
|
raise ArgumentError
|
@@ -7,7 +7,7 @@ require "concurrent/utility/processor_counter"
|
|
7
7
|
|
8
8
|
module ActiveJob
|
9
9
|
module QueueAdapters
|
10
|
-
#
|
10
|
+
# = Active Job Async adapter
|
11
11
|
#
|
12
12
|
# The Async adapter runs jobs with an in-process thread pool.
|
13
13
|
#
|
@@ -95,7 +95,7 @@ module ActiveJob
|
|
95
95
|
|
96
96
|
def enqueue_at(job, timestamp, queue_name:)
|
97
97
|
delay = timestamp - Time.current.to_f
|
98
|
-
if delay > 0
|
98
|
+
if !immediate && delay > 0
|
99
99
|
Concurrent::ScheduledTask.execute(delay, args: [job], executor: executor, &:perform)
|
100
100
|
else
|
101
101
|
enqueue(job, queue_name: queue_name)
|
@@ -4,7 +4,7 @@ require "backburner"
|
|
4
4
|
|
5
5
|
module ActiveJob
|
6
6
|
module QueueAdapters
|
7
|
-
#
|
7
|
+
# = Backburner adapter for Active Job
|
8
8
|
#
|
9
9
|
# Backburner is a beanstalkd-powered job queue that can handle a very
|
10
10
|
# high volume of jobs. You create background jobs and place them on
|
@@ -16,12 +16,16 @@ module ActiveJob
|
|
16
16
|
# Rails.application.config.active_job.queue_adapter = :backburner
|
17
17
|
class BackburnerAdapter
|
18
18
|
def enqueue(job) # :nodoc:
|
19
|
-
Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority)
|
19
|
+
response = Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority)
|
20
|
+
job.provider_job_id = response[:id] if response.is_a?(Hash)
|
21
|
+
response
|
20
22
|
end
|
21
23
|
|
22
24
|
def enqueue_at(job, timestamp) # :nodoc:
|
23
25
|
delay = timestamp - Time.current.to_f
|
24
|
-
Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, delay: delay)
|
26
|
+
response = Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, delay: delay)
|
27
|
+
job.provider_job_id = response[:id] if response.is_a?(Hash)
|
28
|
+
response
|
25
29
|
end
|
26
30
|
|
27
31
|
class JobWrapper # :nodoc:
|
@@ -5,7 +5,7 @@ require "active_support/core_ext/string/inflections"
|
|
5
5
|
|
6
6
|
module ActiveJob
|
7
7
|
module QueueAdapters
|
8
|
-
#
|
8
|
+
# = Delayed Job adapter for Active Job
|
9
9
|
#
|
10
10
|
# Delayed::Job (or DJ) encapsulates the common pattern of asynchronously
|
11
11
|
# executing longer tasks in the background. Although DJ can have many
|
@@ -50,6 +50,8 @@ module ActiveJob
|
|
50
50
|
private
|
51
51
|
def log_arguments?
|
52
52
|
job_data["job_class"].constantize.log_arguments?
|
53
|
+
rescue NameError
|
54
|
+
false
|
53
55
|
end
|
54
56
|
end
|
55
57
|
end
|
@@ -4,7 +4,7 @@ require "queue_classic"
|
|
4
4
|
|
5
5
|
module ActiveJob
|
6
6
|
module QueueAdapters
|
7
|
-
#
|
7
|
+
# = queue_classic adapter for Active Job
|
8
8
|
#
|
9
9
|
# queue_classic provides a simple interface to a PostgreSQL-backed message
|
10
10
|
# queue. queue_classic specializes in concurrent locking and minimizing
|
@@ -37,10 +37,10 @@ module ActiveJob
|
|
37
37
|
qc_job
|
38
38
|
end
|
39
39
|
|
40
|
-
# Builds a
|
40
|
+
# Builds a +QC::Queue+ object to schedule jobs on.
|
41
41
|
#
|
42
|
-
# If you have a custom
|
43
|
-
#
|
42
|
+
# If you have a custom +QC::Queue+ subclass you'll need to subclass
|
43
|
+
# +ActiveJob::QueueAdapters::QueueClassicAdapter+ and override the
|
44
44
|
# <tt>build_queue</tt> method.
|
45
45
|
def build_queue(queue_name)
|
46
46
|
QC::Queue.new(queue_name)
|
@@ -16,7 +16,7 @@ end
|
|
16
16
|
|
17
17
|
module ActiveJob
|
18
18
|
module QueueAdapters
|
19
|
-
#
|
19
|
+
# = Resque adapter for Active Job
|
20
20
|
#
|
21
21
|
# Resque (pronounced like "rescue") is a Redis-backed library for creating
|
22
22
|
# background jobs, placing those jobs on multiple queues, and processing
|