activejob 5.2.2.1 → 6.0.2
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 +128 -32
- data/MIT-LICENSE +1 -1
- data/README.md +17 -10
- data/lib/active_job.rb +2 -1
- data/lib/active_job/arguments.rb +40 -28
- data/lib/active_job/base.rb +3 -1
- data/lib/active_job/callbacks.rb +4 -1
- data/lib/active_job/core.rb +38 -21
- data/lib/active_job/enqueuing.rb +26 -5
- data/lib/active_job/exceptions.rb +40 -17
- data/lib/active_job/execution.rb +1 -1
- data/lib/active_job/gem_version.rb +3 -3
- data/lib/active_job/logging.rb +40 -9
- data/lib/active_job/queue_adapter.rb +2 -0
- data/lib/active_job/queue_adapters.rb +8 -10
- data/lib/active_job/queue_adapters/async_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/backburner_adapter.rb +2 -2
- data/lib/active_job/queue_adapters/inline_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +2 -2
- data/lib/active_job/queue_adapters/test_adapter.rb +22 -8
- data/lib/active_job/queue_name.rb +21 -1
- data/lib/active_job/railtie.rb +16 -1
- data/lib/active_job/serializers.rb +63 -0
- data/lib/active_job/serializers/date_serializer.rb +21 -0
- data/lib/active_job/serializers/date_time_serializer.rb +21 -0
- data/lib/active_job/serializers/duration_serializer.rb +24 -0
- data/lib/active_job/serializers/object_serializer.rb +54 -0
- data/lib/active_job/serializers/symbol_serializer.rb +21 -0
- data/lib/active_job/serializers/time_serializer.rb +21 -0
- data/lib/active_job/serializers/time_with_zone_serializer.rb +21 -0
- data/lib/active_job/test_helper.rb +278 -57
- data/lib/active_job/timezones.rb +13 -0
- data/lib/active_job/translation.rb +1 -1
- data/lib/rails/generators/job/job_generator.rb +4 -0
- metadata +22 -11
- data/lib/active_job/queue_adapters/qu_adapter.rb +0 -46
data/lib/active_job/callbacks.rb
CHANGED
@@ -29,6 +29,9 @@ module ActiveJob
|
|
29
29
|
included do
|
30
30
|
define_callbacks :perform
|
31
31
|
define_callbacks :enqueue
|
32
|
+
|
33
|
+
class_attribute :return_false_on_aborted_enqueue, instance_accessor: false, instance_predicate: false
|
34
|
+
self.return_false_on_aborted_enqueue = false
|
32
35
|
end
|
33
36
|
|
34
37
|
# These methods will be included into any Active Job object, adding
|
@@ -130,7 +133,7 @@ module ActiveJob
|
|
130
133
|
set_callback(:enqueue, :after, *filters, &blk)
|
131
134
|
end
|
132
135
|
|
133
|
-
# Defines a callback that will get called around the
|
136
|
+
# Defines a callback that will get called around the enqueuing
|
134
137
|
# of the job.
|
135
138
|
#
|
136
139
|
# class VideoProcessJob < ActiveJob::Base
|
data/lib/active_job/core.rb
CHANGED
@@ -6,32 +6,42 @@ module ActiveJob
|
|
6
6
|
module Core
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
attr_writer :serialized_arguments
|
9
|
+
# Job arguments
|
10
|
+
attr_accessor :arguments
|
11
|
+
attr_writer :serialized_arguments
|
13
12
|
|
14
|
-
|
15
|
-
|
13
|
+
# Timestamp when the job should be performed
|
14
|
+
attr_accessor :scheduled_at
|
16
15
|
|
17
|
-
|
18
|
-
|
16
|
+
# Job Identifier
|
17
|
+
attr_accessor :job_id
|
19
18
|
|
20
|
-
|
21
|
-
|
19
|
+
# Queue in which the job will reside.
|
20
|
+
attr_writer :queue_name
|
22
21
|
|
23
|
-
|
24
|
-
|
22
|
+
# Priority that the job will have (lower is more priority).
|
23
|
+
attr_writer :priority
|
25
24
|
|
26
|
-
|
27
|
-
|
25
|
+
# ID optionally provided by adapter
|
26
|
+
attr_accessor :provider_job_id
|
28
27
|
|
29
|
-
|
30
|
-
|
28
|
+
# Number of times this job has been executed (which increments on every retry, like after an exception).
|
29
|
+
attr_accessor :executions
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
# Hash that contains the number of times this job handled errors for each specific retry_on declaration.
|
32
|
+
# Keys are the string representation of the exceptions listed in the retry_on declaration,
|
33
|
+
# while its associated value holds the number of executions where the corresponding retry_on
|
34
|
+
# declaration handled one of its listed exceptions.
|
35
|
+
attr_accessor :exception_executions
|
36
|
+
|
37
|
+
# I18n.locale to be used during the job.
|
38
|
+
attr_accessor :locale
|
39
|
+
|
40
|
+
# Timezone to be used during the job.
|
41
|
+
attr_accessor :timezone
|
42
|
+
|
43
|
+
# Track when a job was enqueued
|
44
|
+
attr_accessor :enqueued_at
|
35
45
|
|
36
46
|
# These methods will be included into any Active Job object, adding
|
37
47
|
# helpers for de/serialization and creation of job instances.
|
@@ -74,10 +84,11 @@ module ActiveJob
|
|
74
84
|
@queue_name = self.class.queue_name
|
75
85
|
@priority = self.class.priority
|
76
86
|
@executions = 0
|
87
|
+
@exception_executions = {}
|
77
88
|
end
|
78
89
|
|
79
90
|
# Returns a hash with the job data that can safely be passed to the
|
80
|
-
#
|
91
|
+
# queuing adapter.
|
81
92
|
def serialize
|
82
93
|
{
|
83
94
|
"job_class" => self.class.name,
|
@@ -87,7 +98,10 @@ module ActiveJob
|
|
87
98
|
"priority" => priority,
|
88
99
|
"arguments" => serialize_arguments_if_needed(arguments),
|
89
100
|
"executions" => executions,
|
90
|
-
"
|
101
|
+
"exception_executions" => exception_executions,
|
102
|
+
"locale" => I18n.locale.to_s,
|
103
|
+
"timezone" => Time.zone.try(:name),
|
104
|
+
"enqueued_at" => Time.now.utc.iso8601
|
91
105
|
}
|
92
106
|
end
|
93
107
|
|
@@ -124,7 +138,10 @@ module ActiveJob
|
|
124
138
|
self.priority = job_data["priority"]
|
125
139
|
self.serialized_arguments = job_data["arguments"]
|
126
140
|
self.executions = job_data["executions"]
|
141
|
+
self.exception_executions = job_data["exception_executions"]
|
127
142
|
self.locale = job_data["locale"] || I18n.locale.to_s
|
143
|
+
self.timezone = job_data["timezone"] || Time.zone.try(:name)
|
144
|
+
self.enqueued_at = job_data["enqueued_at"]
|
128
145
|
end
|
129
146
|
|
130
147
|
private
|
data/lib/active_job/enqueuing.rb
CHANGED
@@ -9,10 +9,12 @@ module ActiveJob
|
|
9
9
|
|
10
10
|
# Includes the +perform_later+ method for job initialization.
|
11
11
|
module ClassMethods
|
12
|
-
# Push a job onto the queue.
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
12
|
+
# Push a job onto the queue. By default the arguments must be either String,
|
13
|
+
# Integer, Float, NilClass, TrueClass, FalseClass, BigDecimal, Symbol, Date,
|
14
|
+
# Time, DateTime, ActiveSupport::TimeWithZone, ActiveSupport::Duration,
|
15
|
+
# Hash, ActiveSupport::HashWithIndifferentAccess, Array or
|
16
|
+
# GlobalID::Identification instances, although this can be extended by adding
|
17
|
+
# custom serializers.
|
16
18
|
#
|
17
19
|
# Returns an instance of the job class queued with arguments available in
|
18
20
|
# Job#arguments.
|
@@ -46,14 +48,33 @@ module ActiveJob
|
|
46
48
|
self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
|
47
49
|
self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
|
48
50
|
self.priority = options[:priority].to_i if options[:priority]
|
51
|
+
successfully_enqueued = false
|
52
|
+
|
49
53
|
run_callbacks :enqueue do
|
50
54
|
if scheduled_at
|
51
55
|
self.class.queue_adapter.enqueue_at self, scheduled_at
|
52
56
|
else
|
53
57
|
self.class.queue_adapter.enqueue self
|
54
58
|
end
|
59
|
+
|
60
|
+
successfully_enqueued = true
|
61
|
+
end
|
62
|
+
|
63
|
+
if successfully_enqueued
|
64
|
+
self
|
65
|
+
else
|
66
|
+
if self.class.return_false_on_aborted_enqueue
|
67
|
+
false
|
68
|
+
else
|
69
|
+
ActiveSupport::Deprecation.warn(
|
70
|
+
"Rails 6.1 will return false when the enqueuing is aborted. Make sure your code doesn't depend on it" \
|
71
|
+
" returning the instance of the job and set `config.active_job.return_false_on_aborted_enqueue = true`" \
|
72
|
+
" to remove the deprecations."
|
73
|
+
)
|
74
|
+
|
75
|
+
self
|
76
|
+
end
|
55
77
|
end
|
56
|
-
self
|
57
78
|
end
|
58
79
|
end
|
59
80
|
end
|
@@ -30,28 +30,36 @@ module ActiveJob
|
|
30
30
|
# class RemoteServiceJob < ActiveJob::Base
|
31
31
|
# retry_on CustomAppException # defaults to 3s wait, 5 attempts
|
32
32
|
# retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
|
33
|
+
#
|
34
|
+
# retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
|
35
|
+
# retry_on Net::OpenTimeout, Timeout::Error, wait: :exponentially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
|
36
|
+
# # To retry at most 10 times for each individual exception:
|
37
|
+
# # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
|
38
|
+
# # retry_on Timeout::Error, wait: :exponentially_longer, attempts: 10
|
39
|
+
#
|
33
40
|
# retry_on(YetAnotherCustomAppException) do |job, error|
|
34
41
|
# ExceptionNotifier.caught(error)
|
35
42
|
# end
|
36
|
-
# retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
|
37
|
-
# retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
|
38
43
|
#
|
39
44
|
# def perform(*args)
|
40
45
|
# # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
|
41
46
|
# # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
|
42
|
-
# # Might raise Net::OpenTimeout when the remote service is down
|
47
|
+
# # Might raise Net::OpenTimeout or Timeout::Error when the remote service is down
|
43
48
|
# end
|
44
49
|
# end
|
45
|
-
def retry_on(
|
46
|
-
rescue_from
|
50
|
+
def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
|
51
|
+
rescue_from(*exceptions) do |error|
|
52
|
+
executions = executions_for(exceptions)
|
53
|
+
|
47
54
|
if executions < attempts
|
48
|
-
|
49
|
-
retry_job wait: determine_delay(wait), queue: queue, priority: priority
|
55
|
+
retry_job wait: determine_delay(seconds_or_duration_or_algorithm: wait, executions: executions), queue: queue, priority: priority, error: error
|
50
56
|
else
|
51
57
|
if block_given?
|
52
|
-
|
58
|
+
instrument :retry_stopped, error: error do
|
59
|
+
yield self, error
|
60
|
+
end
|
53
61
|
else
|
54
|
-
|
62
|
+
instrument :retry_stopped, error: error
|
55
63
|
raise error
|
56
64
|
end
|
57
65
|
end
|
@@ -76,12 +84,10 @@ module ActiveJob
|
|
76
84
|
# # Might raise CustomAppException for something domain specific
|
77
85
|
# end
|
78
86
|
# end
|
79
|
-
def discard_on(
|
80
|
-
rescue_from
|
81
|
-
|
82
|
-
yield self, error
|
83
|
-
else
|
84
|
-
logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
|
87
|
+
def discard_on(*exceptions)
|
88
|
+
rescue_from(*exceptions) do |error|
|
89
|
+
instrument :discard, error: error do
|
90
|
+
yield self, error if block_given?
|
85
91
|
end
|
86
92
|
end
|
87
93
|
end
|
@@ -109,11 +115,13 @@ module ActiveJob
|
|
109
115
|
# end
|
110
116
|
# end
|
111
117
|
def retry_job(options = {})
|
112
|
-
|
118
|
+
instrument :enqueue_retry, options.slice(:error, :wait) do
|
119
|
+
enqueue options
|
120
|
+
end
|
113
121
|
end
|
114
122
|
|
115
123
|
private
|
116
|
-
def determine_delay(seconds_or_duration_or_algorithm)
|
124
|
+
def determine_delay(seconds_or_duration_or_algorithm:, executions:)
|
117
125
|
case seconds_or_duration_or_algorithm
|
118
126
|
when :exponentially_longer
|
119
127
|
(executions**4) + 2
|
@@ -130,5 +138,20 @@ module ActiveJob
|
|
130
138
|
raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}"
|
131
139
|
end
|
132
140
|
end
|
141
|
+
|
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)
|
146
|
+
end
|
147
|
+
|
148
|
+
def executions_for(exceptions)
|
149
|
+
if exception_executions
|
150
|
+
exception_executions[exceptions.to_s] = (exception_executions[exceptions.to_s] || 0) + 1
|
151
|
+
else
|
152
|
+
# Guard against jobs that were persisted before we started having individual executions counters per retry_on
|
153
|
+
executions
|
154
|
+
end
|
155
|
+
end
|
133
156
|
end
|
134
157
|
end
|
data/lib/active_job/execution.rb
CHANGED
@@ -26,7 +26,7 @@ module ActiveJob
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
# Performs the job immediately. The job is not sent to the
|
29
|
+
# Performs the job immediately. The job is not sent to the queuing adapter
|
30
30
|
# but directly executed by blocking the execution of others until it's finished.
|
31
31
|
#
|
32
32
|
# MyJob.new(*args).perform_now
|
data/lib/active_job/logging.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/hash/transform_values"
|
4
3
|
require "active_support/core_ext/string/filters"
|
5
4
|
require "active_support/tagged_logging"
|
6
5
|
require "active_support/logger"
|
@@ -12,13 +11,13 @@ module ActiveJob
|
|
12
11
|
included do
|
13
12
|
cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
14
13
|
|
15
|
-
around_enqueue do |_, block
|
14
|
+
around_enqueue do |_, block|
|
16
15
|
tag_logger do
|
17
16
|
block.call
|
18
17
|
end
|
19
18
|
end
|
20
19
|
|
21
|
-
around_perform do |job, block
|
20
|
+
around_perform do |job, block|
|
22
21
|
tag_logger(job.class.name, job.job_id) do
|
23
22
|
payload = { adapter: job.class.queue_adapter, job: job }
|
24
23
|
ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
|
@@ -28,13 +27,13 @@ module ActiveJob
|
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
31
|
-
|
30
|
+
around_enqueue do |job, block|
|
32
31
|
if job.scheduled_at
|
33
|
-
ActiveSupport::Notifications.instrument
|
34
|
-
adapter: job.class.queue_adapter, job: job
|
32
|
+
ActiveSupport::Notifications.instrument("enqueue_at.active_job",
|
33
|
+
adapter: job.class.queue_adapter, job: job, &block)
|
35
34
|
else
|
36
|
-
ActiveSupport::Notifications.instrument
|
37
|
-
adapter: job.class.queue_adapter, job: job
|
35
|
+
ActiveSupport::Notifications.instrument("enqueue.active_job",
|
36
|
+
adapter: job.class.queue_adapter, job: job, &block)
|
38
37
|
end
|
39
38
|
end
|
40
39
|
end
|
@@ -71,7 +70,7 @@ module ActiveJob
|
|
71
70
|
def perform_start(event)
|
72
71
|
info do
|
73
72
|
job = event.payload[:job]
|
74
|
-
"Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + args_info(job)
|
73
|
+
"Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} enqueued at #{job.enqueued_at}" + args_info(job)
|
75
74
|
end
|
76
75
|
end
|
77
76
|
|
@@ -89,6 +88,38 @@ module ActiveJob
|
|
89
88
|
end
|
90
89
|
end
|
91
90
|
|
91
|
+
def enqueue_retry(event)
|
92
|
+
job = event.payload[:job]
|
93
|
+
ex = event.payload[:error]
|
94
|
+
wait = event.payload[:wait]
|
95
|
+
|
96
|
+
info do
|
97
|
+
if ex
|
98
|
+
"Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
|
99
|
+
else
|
100
|
+
"Retrying #{job.class} in #{wait.to_i} seconds."
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def retry_stopped(event)
|
106
|
+
job = event.payload[:job]
|
107
|
+
ex = event.payload[:error]
|
108
|
+
|
109
|
+
error do
|
110
|
+
"Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def discard(event)
|
115
|
+
job = event.payload[:job]
|
116
|
+
ex = event.payload[:error]
|
117
|
+
|
118
|
+
error do
|
119
|
+
"Discarded #{job.class} due to a #{ex.class}."
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
92
123
|
private
|
93
124
|
def queue_name(event)
|
94
125
|
event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
|
@@ -3,19 +3,19 @@
|
|
3
3
|
module ActiveJob
|
4
4
|
# == Active Job adapters
|
5
5
|
#
|
6
|
-
# Active Job has adapters for the following
|
6
|
+
# Active Job has adapters for the following queuing backends:
|
7
7
|
#
|
8
8
|
# * {Backburner}[https://github.com/nesquena/backburner]
|
9
9
|
# * {Delayed Job}[https://github.com/collectiveidea/delayed_job]
|
10
|
-
# * {Qu}[https://github.com/bkeepers/qu]
|
11
10
|
# * {Que}[https://github.com/chanks/que]
|
12
11
|
# * {queue_classic}[https://github.com/QueueClassic/queue_classic]
|
13
12
|
# * {Resque}[https://github.com/resque/resque]
|
14
|
-
# * {Sidekiq}[
|
13
|
+
# * {Sidekiq}[https://sidekiq.org]
|
15
14
|
# * {Sneakers}[https://github.com/jondot/sneakers]
|
16
15
|
# * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
|
17
|
-
# * {Active Job Async Job}[
|
18
|
-
# * {Active Job Inline}[
|
16
|
+
# * {Active Job Async Job}[https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html]
|
17
|
+
# * {Active Job Inline}[https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html]
|
18
|
+
# * Please Note: We are not accepting pull requests for new adapters. See the {README}[link:files/activejob/README_md.html] for more details.
|
19
19
|
#
|
20
20
|
# === Backends Features
|
21
21
|
#
|
@@ -23,7 +23,6 @@ module ActiveJob
|
|
23
23
|
# |-------------------|-------|--------|------------|------------|---------|---------|
|
24
24
|
# | Backburner | Yes | Yes | Yes | Yes | Job | Global |
|
25
25
|
# | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
|
26
|
-
# | Qu | Yes | Yes | No | No | No | Global |
|
27
26
|
# | Que | Yes | Yes | Yes | Job | No | Job |
|
28
27
|
# | queue_classic | Yes | Yes | Yes* | No | No | No |
|
29
28
|
# | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
|
@@ -53,7 +52,7 @@ module ActiveJob
|
|
53
52
|
#
|
54
53
|
# No: The adapter will run jobs at the next opportunity and cannot use perform_later.
|
55
54
|
#
|
56
|
-
# N/A: The adapter does not support
|
55
|
+
# N/A: The adapter does not support queuing.
|
57
56
|
#
|
58
57
|
# NOTE:
|
59
58
|
# queue_classic supports job scheduling since version 3.1.
|
@@ -75,7 +74,7 @@ module ActiveJob
|
|
75
74
|
#
|
76
75
|
# No: Does not allow the priority of jobs to be configured.
|
77
76
|
#
|
78
|
-
# N/A: The adapter does not support
|
77
|
+
# N/A: The adapter does not support queuing, and therefore sorting them.
|
79
78
|
#
|
80
79
|
# ==== Timeout
|
81
80
|
#
|
@@ -114,7 +113,6 @@ module ActiveJob
|
|
114
113
|
autoload :InlineAdapter
|
115
114
|
autoload :BackburnerAdapter
|
116
115
|
autoload :DelayedJobAdapter
|
117
|
-
autoload :QuAdapter
|
118
116
|
autoload :QueAdapter
|
119
117
|
autoload :QueueClassicAdapter
|
120
118
|
autoload :ResqueAdapter
|
@@ -123,7 +121,7 @@ module ActiveJob
|
|
123
121
|
autoload :SuckerPunchAdapter
|
124
122
|
autoload :TestAdapter
|
125
123
|
|
126
|
-
ADAPTER = "Adapter"
|
124
|
+
ADAPTER = "Adapter"
|
127
125
|
private_constant :ADAPTER
|
128
126
|
|
129
127
|
class << self
|