activejob 5.2.4.4 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +91 -50
- data/MIT-LICENSE +1 -1
- data/README.md +18 -13
- data/lib/active_job.rb +2 -1
- data/lib/active_job/arguments.rb +80 -30
- data/lib/active_job/base.rb +6 -1
- data/lib/active_job/callbacks.rb +46 -3
- data/lib/active_job/configured_job.rb +2 -0
- data/lib/active_job/core.rb +40 -21
- data/lib/active_job/enqueuing.rb +20 -7
- data/lib/active_job/exceptions.rb +60 -28
- data/lib/active_job/execution.rb +11 -2
- data/lib/active_job/gem_version.rb +4 -4
- data/lib/active_job/instrumentation.rb +40 -0
- data/lib/active_job/log_subscriber.rb +140 -0
- data/lib/active_job/logging.rb +3 -101
- data/lib/active_job/queue_adapter.rb +5 -0
- data/lib/active_job/queue_adapters.rb +13 -11
- 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/que_adapter.rb +2 -2
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +2 -2
- data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/test_adapter.rb +32 -14
- data/lib/active_job/queue_name.rb +23 -3
- data/lib/active_job/railtie.rb +20 -1
- data/lib/active_job/serializers.rb +66 -0
- data/lib/active_job/serializers/date_serializer.rb +20 -0
- data/lib/active_job/serializers/date_time_serializer.rb +16 -0
- data/lib/active_job/serializers/duration_serializer.rb +23 -0
- data/lib/active_job/serializers/module_serializer.rb +20 -0
- data/lib/active_job/serializers/object_serializer.rb +53 -0
- data/lib/active_job/serializers/symbol_serializer.rb +20 -0
- data/lib/active_job/serializers/time_object_serializer.rb +13 -0
- data/lib/active_job/serializers/time_serializer.rb +16 -0
- data/lib/active_job/serializers/time_with_zone_serializer.rb +16 -0
- data/lib/active_job/test_helper.rb +316 -68
- 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 +29 -14
- data/lib/active_job/queue_adapters/qu_adapter.rb +0 -46
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Instrumentation #:nodoc:
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
around_enqueue do |_, block|
|
9
|
+
scheduled_at ? instrument(:enqueue_at, &block) : instrument(:enqueue, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
around_perform do |_, block|
|
13
|
+
instrument :perform_start
|
14
|
+
instrument :perform, &block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def instrument(operation, payload = {}, &block)
|
20
|
+
enhanced_block = ->(event_payload) do
|
21
|
+
value = block.call if block
|
22
|
+
|
23
|
+
if defined?(@_halted_callback_hook_called) && @_halted_callback_hook_called
|
24
|
+
event_payload[:aborted] = true
|
25
|
+
@_halted_callback_hook_called = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
value
|
29
|
+
end
|
30
|
+
|
31
|
+
ActiveSupport::Notifications.instrument \
|
32
|
+
"#{operation}.active_job", payload.merge(adapter: queue_adapter, job: self), &enhanced_block
|
33
|
+
end
|
34
|
+
|
35
|
+
def halted_callback_hook(*)
|
36
|
+
super
|
37
|
+
@_halted_callback_hook_called = true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/filters"
|
4
|
+
require "active_support/log_subscriber"
|
5
|
+
|
6
|
+
module ActiveJob
|
7
|
+
class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
|
8
|
+
def enqueue(event)
|
9
|
+
job = event.payload[:job]
|
10
|
+
ex = event.payload[:exception_object]
|
11
|
+
|
12
|
+
if ex
|
13
|
+
error do
|
14
|
+
"Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
|
15
|
+
end
|
16
|
+
elsif event.payload[:aborted]
|
17
|
+
info do
|
18
|
+
"Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
|
19
|
+
end
|
20
|
+
else
|
21
|
+
info do
|
22
|
+
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def enqueue_at(event)
|
28
|
+
job = event.payload[:job]
|
29
|
+
ex = event.payload[:exception_object]
|
30
|
+
|
31
|
+
if ex
|
32
|
+
error do
|
33
|
+
"Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
|
34
|
+
end
|
35
|
+
elsif event.payload[:aborted]
|
36
|
+
info do
|
37
|
+
"Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
|
38
|
+
end
|
39
|
+
else
|
40
|
+
info do
|
41
|
+
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def perform_start(event)
|
47
|
+
info do
|
48
|
+
job = event.payload[:job]
|
49
|
+
"Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} enqueued at #{job.enqueued_at}" + args_info(job)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def perform(event)
|
54
|
+
job = event.payload[:job]
|
55
|
+
ex = event.payload[:exception_object]
|
56
|
+
if ex
|
57
|
+
error do
|
58
|
+
"Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
|
59
|
+
end
|
60
|
+
elsif event.payload[:aborted]
|
61
|
+
error do
|
62
|
+
"Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: a before_perform callback halted the job execution"
|
63
|
+
end
|
64
|
+
else
|
65
|
+
info do
|
66
|
+
"Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def enqueue_retry(event)
|
72
|
+
job = event.payload[:job]
|
73
|
+
ex = event.payload[:error]
|
74
|
+
wait = event.payload[:wait]
|
75
|
+
|
76
|
+
info do
|
77
|
+
if ex
|
78
|
+
"Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
|
79
|
+
else
|
80
|
+
"Retrying #{job.class} in #{wait.to_i} seconds."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def retry_stopped(event)
|
86
|
+
job = event.payload[:job]
|
87
|
+
ex = event.payload[:error]
|
88
|
+
|
89
|
+
error do
|
90
|
+
"Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def discard(event)
|
95
|
+
job = event.payload[:job]
|
96
|
+
ex = event.payload[:error]
|
97
|
+
|
98
|
+
error do
|
99
|
+
"Discarded #{job.class} due to a #{ex.class}."
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
def queue_name(event)
|
105
|
+
event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
|
106
|
+
end
|
107
|
+
|
108
|
+
def args_info(job)
|
109
|
+
if job.class.log_arguments? && job.arguments.any?
|
110
|
+
" with arguments: " +
|
111
|
+
job.arguments.map { |arg| format(arg).inspect }.join(", ")
|
112
|
+
else
|
113
|
+
""
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def format(arg)
|
118
|
+
case arg
|
119
|
+
when Hash
|
120
|
+
arg.transform_values { |value| format(value) }
|
121
|
+
when Array
|
122
|
+
arg.map { |value| format(value) }
|
123
|
+
when GlobalID::Identification
|
124
|
+
arg.to_global_id rescue arg
|
125
|
+
else
|
126
|
+
arg
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def scheduled_at(event)
|
131
|
+
Time.at(event.payload[:job].scheduled_at).utc
|
132
|
+
end
|
133
|
+
|
134
|
+
def logger
|
135
|
+
ActiveJob::Base.logger
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
ActiveJob::LogSubscriber.attach_to :active_job
|
data/lib/active_job/logging.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/hash/transform_values"
|
4
|
-
require "active_support/core_ext/string/filters"
|
5
3
|
require "active_support/tagged_logging"
|
6
4
|
require "active_support/logger"
|
7
5
|
|
@@ -11,32 +9,10 @@ module ActiveJob
|
|
11
9
|
|
12
10
|
included do
|
13
11
|
cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
12
|
+
class_attribute :log_arguments, instance_accessor: false, default: true
|
14
13
|
|
15
|
-
around_enqueue
|
16
|
-
|
17
|
-
block.call
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
around_perform do |job, block, _|
|
22
|
-
tag_logger(job.class.name, job.job_id) do
|
23
|
-
payload = { adapter: job.class.queue_adapter, job: job }
|
24
|
-
ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
|
25
|
-
ActiveSupport::Notifications.instrument("perform.active_job", payload) do
|
26
|
-
block.call
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
after_enqueue do |job|
|
32
|
-
if job.scheduled_at
|
33
|
-
ActiveSupport::Notifications.instrument "enqueue_at.active_job",
|
34
|
-
adapter: job.class.queue_adapter, job: job
|
35
|
-
else
|
36
|
-
ActiveSupport::Notifications.instrument "enqueue.active_job",
|
37
|
-
adapter: job.class.queue_adapter, job: job
|
38
|
-
end
|
39
|
-
end
|
14
|
+
around_enqueue { |_, block| tag_logger(&block) }
|
15
|
+
around_perform { |job, block| tag_logger(job.class.name, job.job_id, &block) }
|
40
16
|
end
|
41
17
|
|
42
18
|
private
|
@@ -52,79 +28,5 @@ module ActiveJob
|
|
52
28
|
def logger_tagged_by_active_job?
|
53
29
|
logger.formatter.current_tags.include?("ActiveJob")
|
54
30
|
end
|
55
|
-
|
56
|
-
class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
|
57
|
-
def enqueue(event)
|
58
|
-
info do
|
59
|
-
job = event.payload[:job]
|
60
|
-
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def enqueue_at(event)
|
65
|
-
info do
|
66
|
-
job = event.payload[:job]
|
67
|
-
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def perform_start(event)
|
72
|
-
info do
|
73
|
-
job = event.payload[:job]
|
74
|
-
"Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + args_info(job)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def perform(event)
|
79
|
-
job = event.payload[:job]
|
80
|
-
ex = event.payload[:exception_object]
|
81
|
-
if ex
|
82
|
-
error do
|
83
|
-
"Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
|
84
|
-
end
|
85
|
-
else
|
86
|
-
info do
|
87
|
-
"Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
private
|
93
|
-
def queue_name(event)
|
94
|
-
event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
|
95
|
-
end
|
96
|
-
|
97
|
-
def args_info(job)
|
98
|
-
if job.arguments.any?
|
99
|
-
" with arguments: " +
|
100
|
-
job.arguments.map { |arg| format(arg).inspect }.join(", ")
|
101
|
-
else
|
102
|
-
""
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def format(arg)
|
107
|
-
case arg
|
108
|
-
when Hash
|
109
|
-
arg.transform_values { |value| format(value) }
|
110
|
-
when Array
|
111
|
-
arg.map { |value| format(value) }
|
112
|
-
when GlobalID::Identification
|
113
|
-
arg.to_global_id rescue arg
|
114
|
-
else
|
115
|
-
arg
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def scheduled_at(event)
|
120
|
-
Time.at(event.payload[:job].scheduled_at).utc
|
121
|
-
end
|
122
|
-
|
123
|
-
def logger
|
124
|
-
ActiveJob::Base.logger
|
125
|
-
end
|
126
|
-
end
|
127
31
|
end
|
128
32
|
end
|
129
|
-
|
130
|
-
ActiveJob::Logging::LogSubscriber.attach_to :active_job
|
@@ -11,6 +11,9 @@ module ActiveJob
|
|
11
11
|
included do
|
12
12
|
class_attribute :_queue_adapter_name, instance_accessor: false, instance_predicate: false
|
13
13
|
class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
|
14
|
+
|
15
|
+
delegate :queue_adapter, to: :class
|
16
|
+
|
14
17
|
self.queue_adapter = :async
|
15
18
|
end
|
16
19
|
|
@@ -22,6 +25,8 @@ module ActiveJob
|
|
22
25
|
_queue_adapter
|
23
26
|
end
|
24
27
|
|
28
|
+
# Returns string denoting the name of the configured queue adapter.
|
29
|
+
# By default returns +"async"+.
|
25
30
|
def queue_adapter_name
|
26
31
|
_queue_adapter_name
|
27
32
|
end
|
@@ -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.
|
@@ -73,9 +72,9 @@ module ActiveJob
|
|
73
72
|
# Yes: Allows the priority to be set on the job object, at the queue level or
|
74
73
|
# as default configuration option.
|
75
74
|
#
|
76
|
-
# No:
|
75
|
+
# No: The adapter 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
|
#
|
@@ -87,6 +86,8 @@ module ActiveJob
|
|
87
86
|
#
|
88
87
|
# Global: The adapter is configured that all jobs have a maximum run time.
|
89
88
|
#
|
89
|
+
# No: The adapter does not allow the timeout of jobs to be configured.
|
90
|
+
#
|
90
91
|
# N/A: This adapter does not run in a separate process, and therefore timeout
|
91
92
|
# is unsupported.
|
92
93
|
#
|
@@ -100,6 +101,8 @@ module ActiveJob
|
|
100
101
|
#
|
101
102
|
# Global: The adapter has a global number of retries.
|
102
103
|
#
|
104
|
+
# No: The adapter does not allow the number of retries to be configured.
|
105
|
+
#
|
103
106
|
# N/A: The adapter does not run in a separate process, and therefore doesn't
|
104
107
|
# support retries.
|
105
108
|
#
|
@@ -114,7 +117,6 @@ module ActiveJob
|
|
114
117
|
autoload :InlineAdapter
|
115
118
|
autoload :BackburnerAdapter
|
116
119
|
autoload :DelayedJobAdapter
|
117
|
-
autoload :QuAdapter
|
118
120
|
autoload :QueAdapter
|
119
121
|
autoload :QueueClassicAdapter
|
120
122
|
autoload :ResqueAdapter
|
@@ -123,7 +125,7 @@ module ActiveJob
|
|
123
125
|
autoload :SuckerPunchAdapter
|
124
126
|
autoload :TestAdapter
|
125
127
|
|
126
|
-
ADAPTER = "Adapter"
|
128
|
+
ADAPTER = "Adapter"
|
127
129
|
private_constant :ADAPTER
|
128
130
|
|
129
131
|
class << self
|
@@ -31,7 +31,7 @@ module ActiveJob
|
|
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
33
|
class AsyncAdapter
|
34
|
-
# See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
|
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)
|
37
37
|
end
|
@@ -16,12 +16,12 @@ 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
|
19
|
+
Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority)
|
20
20
|
end
|
21
21
|
|
22
22
|
def enqueue_at(job, timestamp) #:nodoc:
|
23
23
|
delay = timestamp - Time.current.to_f
|
24
|
-
Backburner::Worker.enqueue
|
24
|
+
Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, delay: delay)
|
25
25
|
end
|
26
26
|
|
27
27
|
class JobWrapper #:nodoc:
|