activejob 7.0.8.7 → 7.2.2.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 +40 -229
- data/MIT-LICENSE +1 -1
- data/README.md +2 -2
- data/lib/active_job/arguments.rb +18 -32
- data/lib/active_job/base.rb +1 -1
- data/lib/active_job/callbacks.rb +3 -8
- data/lib/active_job/configured_job.rb +4 -0
- data/lib/active_job/core.rb +11 -6
- data/lib/active_job/deprecator.rb +7 -0
- data/lib/active_job/enqueue_after_transaction_commit.rb +28 -0
- data/lib/active_job/enqueuing.rb +71 -12
- data/lib/active_job/exceptions.rb +44 -7
- data/lib/active_job/execution.rb +5 -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 +80 -8
- data/lib/active_job/logging.rb +16 -2
- data/lib/active_job/queue_adapter.rb +18 -6
- data/lib/active_job/queue_adapters/abstract_adapter.rb +27 -0
- data/lib/active_job/queue_adapters/async_adapter.rb +3 -3
- data/lib/active_job/queue_adapters/backburner_adapter.rb +8 -4
- data/lib/active_job/queue_adapters/delayed_job_adapter.rb +10 -2
- data/lib/active_job/queue_adapters/inline_adapter.rb +6 -2
- data/lib/active_job/queue_adapters/queue_classic_adapter.rb +13 -5
- data/lib/active_job/queue_adapters/resque_adapter.rb +2 -2
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +43 -15
- data/lib/active_job/queue_adapters/sneakers_adapter.rb +2 -2
- data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +4 -4
- data/lib/active_job/queue_adapters/test_adapter.rb +13 -5
- data/lib/active_job/queue_adapters.rb +9 -7
- data/lib/active_job/queue_priority.rb +18 -1
- data/lib/active_job/railtie.rb +38 -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/object_serializer.rb +2 -0
- 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 +60 -19
- data/lib/active_job/version.rb +1 -1
- data/lib/active_job.rb +34 -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 +12 -8
- data/lib/active_job/queue_adapters/que_adapter.rb +0 -61
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,9 +7,53 @@ 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.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
|
|
43
|
+
included do
|
44
|
+
##
|
45
|
+
# :singleton-method:
|
46
|
+
#
|
47
|
+
# Defines if enqueueing this job from inside an Active Record transaction
|
48
|
+
# automatically defers the enqueue to after the transaction commits.
|
49
|
+
#
|
50
|
+
# It can be set on a per job basis:
|
51
|
+
# - `:always` forces the job to be deferred.
|
52
|
+
# - `:never` forces the job to be queued immediately.
|
53
|
+
# - `:default` lets the queue adapter define the behavior (recommended).
|
54
|
+
class_attribute :enqueue_after_transaction_commit, instance_accessor: false, instance_predicate: false, default: :never
|
55
|
+
end
|
56
|
+
|
15
57
|
# Includes the +perform_later+ method for job initialization.
|
16
58
|
module ClassMethods
|
17
59
|
# Push a job onto the queue. By default the arguments must be either String,
|
@@ -22,9 +64,21 @@ module ActiveJob
|
|
22
64
|
# custom serializers.
|
23
65
|
#
|
24
66
|
# Returns an instance of the job class queued with arguments available in
|
25
|
-
# Job#arguments or false if the enqueue did not succeed.
|
67
|
+
# Job#arguments or +false+ if the enqueue did not succeed.
|
26
68
|
#
|
27
69
|
# After the attempted enqueue, the job will be yielded to an optional block.
|
70
|
+
#
|
71
|
+
# If Active Job is used conjointly with Active Record, and #perform_later is called
|
72
|
+
# inside an Active Record transaction, then the enqueue is implicitly deferred to after
|
73
|
+
# the transaction is committed, or dropped if it's rolled back. In such case #perform_later
|
74
|
+
# will return the job instance like if it was successfully enqueued, but will still return
|
75
|
+
# +false+ if a callback prevented the job from being enqueued.
|
76
|
+
#
|
77
|
+
# This behavior can be changed on a per job basis:
|
78
|
+
#
|
79
|
+
# class NotificationJob < ApplicationJob
|
80
|
+
# self.enqueue_after_transaction_commit = false
|
81
|
+
# end
|
28
82
|
def perform_later(...)
|
29
83
|
job = job_or_instantiate(...)
|
30
84
|
enqueue_result = job.enqueue
|
@@ -35,7 +89,7 @@ module ActiveJob
|
|
35
89
|
end
|
36
90
|
|
37
91
|
private
|
38
|
-
def job_or_instantiate(*args) # :doc:
|
92
|
+
def job_or_instantiate(*args, &_) # :doc:
|
39
93
|
args.first.is_a?(self) ? args.first : new(*args)
|
40
94
|
end
|
41
95
|
ruby2_keywords(:job_or_instantiate)
|
@@ -61,8 +115,20 @@ module ActiveJob
|
|
61
115
|
self.successfully_enqueued = false
|
62
116
|
|
63
117
|
run_callbacks :enqueue do
|
118
|
+
raw_enqueue
|
119
|
+
end
|
120
|
+
|
121
|
+
if successfully_enqueued?
|
122
|
+
self
|
123
|
+
else
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
def raw_enqueue
|
64
130
|
if scheduled_at
|
65
|
-
queue_adapter.enqueue_at self, scheduled_at
|
131
|
+
queue_adapter.enqueue_at self, scheduled_at.to_f
|
66
132
|
else
|
67
133
|
queue_adapter.enqueue self
|
68
134
|
end
|
@@ -71,12 +137,5 @@ module ActiveJob
|
|
71
137
|
rescue EnqueueError => e
|
72
138
|
self.enqueue_error = e
|
73
139
|
end
|
74
|
-
|
75
|
-
if successfully_enqueued?
|
76
|
-
self
|
77
|
-
else
|
78
|
-
false
|
79
|
-
end
|
80
|
-
end
|
81
140
|
end
|
82
141
|
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,13 +21,16 @@ 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
|
-
# * <tt>:attempts</tt> -
|
29
|
-
# to retry the job until it succeeds
|
32
|
+
# * <tt>:attempts</tt> - Enqueues the job the specified number of times (default: 5 attempts) or a symbol reference of <tt>:unlimited</tt>
|
33
|
+
# to retry the job until it succeeds. The number of attempts includes the original job execution.
|
30
34
|
# * <tt>:queue</tt> - Re-enqueues the job on a different queue
|
31
35
|
# * <tt>:priority</tt> - Re-enqueues the job with a different priority
|
32
36
|
# * <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)
|
@@ -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)
|
@@ -65,8 +69,10 @@ module ActiveJob
|
|
65
69
|
instrument :retry_stopped, error: error do
|
66
70
|
yield self, error
|
67
71
|
end
|
72
|
+
run_after_discard_procs(error)
|
68
73
|
else
|
69
74
|
instrument :retry_stopped, error: error
|
75
|
+
run_after_discard_procs(error)
|
70
76
|
raise error
|
71
77
|
end
|
72
78
|
end
|
@@ -78,6 +84,9 @@ module ActiveJob
|
|
78
84
|
#
|
79
85
|
# 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
86
|
#
|
87
|
+
# +retry_on+ and +discard_on+ handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for
|
88
|
+
# which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if any.
|
89
|
+
#
|
81
90
|
# ==== Example
|
82
91
|
#
|
83
92
|
# class SearchIndexingJob < ActiveJob::Base
|
@@ -95,9 +104,26 @@ module ActiveJob
|
|
95
104
|
rescue_from(*exceptions) do |error|
|
96
105
|
instrument :discard, error: error do
|
97
106
|
yield self, error if block_given?
|
107
|
+
run_after_discard_procs(error)
|
98
108
|
end
|
99
109
|
end
|
100
110
|
end
|
111
|
+
|
112
|
+
# A block to run when a job is about to be discarded for any reason.
|
113
|
+
#
|
114
|
+
# ==== Example
|
115
|
+
#
|
116
|
+
# class WorkJob < ActiveJob::Base
|
117
|
+
# after_discard do |job, exception|
|
118
|
+
# ExceptionNotifier.report(exception)
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# ...
|
122
|
+
#
|
123
|
+
# end
|
124
|
+
def after_discard(&blk)
|
125
|
+
self.after_discard_procs += [blk]
|
126
|
+
end
|
101
127
|
end
|
102
128
|
|
103
129
|
# Reschedules the job to be re-executed. This is useful in combination with
|
@@ -136,7 +162,8 @@ module ActiveJob
|
|
136
162
|
jitter = jitter == JITTER_DEFAULT ? self.class.retry_jitter : (jitter || 0.0)
|
137
163
|
|
138
164
|
case seconds_or_duration_or_algorithm
|
139
|
-
when
|
165
|
+
when :polynomially_longer
|
166
|
+
# This delay uses a polynomial backoff strategy, which was previously misnamed as exponential
|
140
167
|
delay = executions**4
|
141
168
|
delay_jitter = determine_jitter_for_delay(delay, jitter)
|
142
169
|
delay + delay_jitter + 2
|
@@ -165,5 +192,15 @@ module ActiveJob
|
|
165
192
|
executions
|
166
193
|
end
|
167
194
|
end
|
195
|
+
|
196
|
+
def run_after_discard_procs(exception)
|
197
|
+
exceptions = []
|
198
|
+
after_discard_procs.each do |blk|
|
199
|
+
instance_exec(self, exception, &blk)
|
200
|
+
rescue StandardError => e
|
201
|
+
exceptions << e
|
202
|
+
end
|
203
|
+
raise exceptions.last unless exceptions.empty?
|
204
|
+
end
|
168
205
|
end
|
169
206
|
end
|
data/lib/active_job/execution.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
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
|
7
6
|
# = Active Job \Execution
|
@@ -51,7 +50,11 @@ module ActiveJob
|
|
51
50
|
|
52
51
|
_perform_job
|
53
52
|
rescue Exception => exception
|
54
|
-
rescue_with_handler(exception)
|
53
|
+
handled = rescue_with_handler(exception)
|
54
|
+
return handled if handled
|
55
|
+
|
56
|
+
run_after_discard_procs(exception)
|
57
|
+
raise
|
55
58
|
end
|
56
59
|
|
57
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 = 2
|
12
|
+
TINY = 2
|
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,10 +1,11 @@
|
|
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
11
|
ex = event.payload[:exception_object] || job.enqueue_error
|
@@ -23,6 +24,7 @@ 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]
|
@@ -38,17 +40,48 @@ module ActiveJob
|
|
38
40
|
end
|
39
41
|
else
|
40
42
|
info do
|
41
|
-
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)
|
43
|
+
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
|
44
|
+
end
|
45
|
+
end
|
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)}"
|
42
71
|
end
|
43
72
|
end
|
44
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 :retry_stopped, :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
|
|
data/lib/active_job/logging.rb
CHANGED
@@ -4,17 +4,31 @@ require "active_support/tagged_logging"
|
|
4
4
|
require "active_support/logger"
|
5
5
|
|
6
6
|
module ActiveJob
|
7
|
-
module Logging
|
7
|
+
module Logging
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
included do
|
11
|
+
##
|
12
|
+
# Accepts a logger conforming to the interface of Log4r or the default
|
13
|
+
# Ruby +Logger+ class. You can retrieve this logger by calling +logger+ on
|
14
|
+
# either an Active Job job class or an Active Job job instance.
|
11
15
|
cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
16
|
+
|
17
|
+
##
|
18
|
+
# Configures whether a job's arguments should be logged. This can be
|
19
|
+
# useful when a job's arguments may be sensitive and so should not be
|
20
|
+
# logged.
|
21
|
+
#
|
22
|
+
# The value defaults to +true+, but this can be configured with
|
23
|
+
# +config.active_job.log_arguments+. Additionally, individual jobs can
|
24
|
+
# also configure a value, which will apply to themselves and any
|
25
|
+
# subclasses.
|
12
26
|
class_attribute :log_arguments, instance_accessor: false, default: true
|
13
27
|
|
14
28
|
around_enqueue(prepend: true) { |_, block| tag_logger(&block) }
|
15
29
|
end
|
16
30
|
|
17
|
-
def perform_now
|
31
|
+
def perform_now # :nodoc:
|
18
32
|
tag_logger(self.class.name, self.job_id) { super }
|
19
33
|
end
|
20
34
|
|
@@ -3,8 +3,20 @@
|
|
3
3
|
require "active_support/core_ext/string/inflections"
|
4
4
|
|
5
5
|
module ActiveJob
|
6
|
-
|
7
|
-
|
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
|
18
|
+
# correct adapter. The default queue adapter is +:async+,
|
19
|
+
# which loads the ActiveJob::QueueAdapters::AsyncAdapter.
|
8
20
|
module QueueAdapter # :nodoc:
|
9
21
|
extend ActiveSupport::Concern
|
10
22
|
|
@@ -13,21 +25,21 @@ module ActiveJob
|
|
13
25
|
class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
|
14
26
|
|
15
27
|
delegate :queue_adapter, to: :class
|
16
|
-
|
17
|
-
self.queue_adapter = :async
|
18
28
|
end
|
19
29
|
|
20
30
|
# Includes the setter method for changing the active queue adapter.
|
21
31
|
module ClassMethods
|
22
32
|
# Returns the backend queue provider. The default queue adapter
|
23
|
-
# is
|
33
|
+
# is +:async+. See QueueAdapters for more information.
|
24
34
|
def queue_adapter
|
35
|
+
self.queue_adapter = :async if _queue_adapter.nil?
|
25
36
|
_queue_adapter
|
26
37
|
end
|
27
38
|
|
28
39
|
# Returns string denoting the name of the configured queue adapter.
|
29
40
|
# By default returns <tt>"async"</tt>.
|
30
41
|
def queue_adapter_name
|
42
|
+
self.queue_adapter = :async if _queue_adapter_name.nil?
|
31
43
|
_queue_adapter_name
|
32
44
|
end
|
33
45
|
|
@@ -41,7 +53,7 @@ module ActiveJob
|
|
41
53
|
assign_adapter(name_or_adapter.to_s, queue_adapter)
|
42
54
|
else
|
43
55
|
if queue_adapter?(name_or_adapter)
|
44
|
-
adapter_name =
|
56
|
+
adapter_name = ActiveJob.adapter_name(name_or_adapter).underscore
|
45
57
|
assign_adapter(adapter_name, name_or_adapter)
|
46
58
|
else
|
47
59
|
raise ArgumentError
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module QueueAdapters
|
5
|
+
# = Active Job Abstract Adapter
|
6
|
+
#
|
7
|
+
# Active Job supports multiple job queue systems. ActiveJob::QueueAdapters::AbstractAdapter
|
8
|
+
# forms the abstraction layer which makes this possible.
|
9
|
+
class AbstractAdapter
|
10
|
+
# Defines whether enqueuing should happen implicitly to after commit when called
|
11
|
+
# from inside a transaction. Most adapters should return true, but some adapters
|
12
|
+
# that use the same database as Active Record and are transaction aware can return
|
13
|
+
# false to continue enqueuing jobs as part of the transaction.
|
14
|
+
def enqueue_after_transaction_commit?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def enqueue(job)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def enqueue_at(job, timestamp)
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -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
|
#
|
@@ -30,7 +30,7 @@ module ActiveJob
|
|
30
30
|
# The adapter uses a {Concurrent Ruby}[https://github.com/ruby-concurrency/concurrent-ruby] thread pool to schedule and execute
|
31
31
|
# jobs. Since jobs share a single thread pool, long-running jobs will block
|
32
32
|
# short-lived jobs. Fine for dev/test; bad for production.
|
33
|
-
class AsyncAdapter
|
33
|
+
class AsyncAdapter < AbstractAdapter
|
34
34
|
# See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadPoolExecutor.html] for executor options.
|
35
35
|
def initialize(**executor_options)
|
36
36
|
@scheduler = Scheduler.new(**executor_options)
|
@@ -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
|
@@ -14,14 +14,18 @@ module ActiveJob
|
|
14
14
|
# To use Backburner set the queue_adapter config to +:backburner+.
|
15
15
|
#
|
16
16
|
# Rails.application.config.active_job.queue_adapter = :backburner
|
17
|
-
class BackburnerAdapter
|
17
|
+
class BackburnerAdapter < AbstractAdapter
|
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:
|