activejob 5.0.7.2 → 5.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activejob might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +24 -221
- data/MIT-LICENSE +1 -1
- data/README.md +1 -1
- data/lib/active_job.rb +5 -5
- data/lib/active_job/arguments.rb +7 -19
- data/lib/active_job/base.rb +11 -9
- data/lib/active_job/callbacks.rb +3 -3
- data/lib/active_job/configured_job.rb +1 -1
- data/lib/active_job/core.rb +14 -9
- data/lib/active_job/enqueuing.rb +7 -32
- data/lib/active_job/exceptions.rb +122 -0
- data/lib/active_job/execution.rb +5 -2
- data/lib/active_job/gem_version.rb +3 -3
- data/lib/active_job/logging.rb +56 -56
- data/lib/active_job/queue_adapter.rb +14 -26
- data/lib/active_job/queue_adapters.rb +2 -2
- data/lib/active_job/queue_adapters/async_adapter.rb +4 -4
- data/lib/active_job/queue_adapters/backburner_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/delayed_job_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/qu_adapter.rb +3 -3
- data/lib/active_job/queue_adapters/que_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 +5 -5
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +11 -11
- data/lib/active_job/queue_adapters/sneakers_adapter.rb +3 -3
- data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +3 -3
- data/lib/active_job/queue_adapters/test_adapter.rb +13 -17
- data/lib/active_job/queue_name.rb +2 -3
- data/lib/active_job/queue_priority.rb +1 -2
- data/lib/active_job/railtie.rb +4 -4
- data/lib/active_job/test_case.rb +1 -1
- data/lib/active_job/test_helper.rb +78 -39
- data/lib/active_job/version.rb +1 -1
- data/lib/rails/generators/job/job_generator.rb +12 -12
- data/lib/rails/generators/job/templates/application_job.rb +5 -0
- metadata +9 -7
data/lib/active_job/enqueuing.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "active_job/arguments"
|
2
2
|
|
3
3
|
module ActiveJob
|
4
|
-
# Provides behavior for enqueuing
|
4
|
+
# Provides behavior for enqueuing jobs.
|
5
5
|
module Enqueuing
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
@@ -18,37 +18,12 @@ module ActiveJob
|
|
18
18
|
job_or_instantiate(*args).enqueue
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
def job_or_instantiate(*args)
|
21
|
+
private
|
22
|
+
def job_or_instantiate(*args) # :doc:
|
23
23
|
args.first.is_a?(self) ? args.first : new(*args)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
# Reschedules the job to be re-executed. This is useful in combination
|
28
|
-
# with the +rescue_from+ option. When you rescue an exception from your job
|
29
|
-
# you can ask Active Job to retry performing your job.
|
30
|
-
#
|
31
|
-
# ==== Options
|
32
|
-
# * <tt>:wait</tt> - Enqueues the job with the specified delay
|
33
|
-
# * <tt>:wait_until</tt> - Enqueues the job at the time specified
|
34
|
-
# * <tt>:queue</tt> - Enqueues the job on the specified queue
|
35
|
-
# * <tt>:priority</tt> - Enqueues the job with the specified priority
|
36
|
-
#
|
37
|
-
# ==== Examples
|
38
|
-
#
|
39
|
-
# class SiteScraperJob < ActiveJob::Base
|
40
|
-
# rescue_from(ErrorLoadingSite) do
|
41
|
-
# retry_job queue: :low_priority
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# def perform(*args)
|
45
|
-
# # raise ErrorLoadingSite if cannot scrape
|
46
|
-
# end
|
47
|
-
# end
|
48
|
-
def retry_job(options={})
|
49
|
-
enqueue options
|
50
|
-
end
|
51
|
-
|
52
27
|
# Enqueues the job to be performed by the queue adapter.
|
53
28
|
#
|
54
29
|
# ==== Options
|
@@ -64,14 +39,14 @@ module ActiveJob
|
|
64
39
|
# my_job_instance.enqueue queue: :important
|
65
40
|
# my_job_instance.enqueue wait_until: Date.tomorrow.midnight
|
66
41
|
# my_job_instance.enqueue priority: 10
|
67
|
-
def enqueue(options={})
|
42
|
+
def enqueue(options = {})
|
68
43
|
self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait]
|
69
44
|
self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
|
70
45
|
self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
|
71
46
|
self.priority = options[:priority].to_i if options[:priority]
|
72
47
|
run_callbacks :enqueue do
|
73
|
-
if
|
74
|
-
self.class.queue_adapter.enqueue_at self,
|
48
|
+
if scheduled_at
|
49
|
+
self.class.queue_adapter.enqueue_at self, scheduled_at
|
75
50
|
else
|
76
51
|
self.class.queue_adapter.enqueue self
|
77
52
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require "active_support/core_ext/numeric/time"
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
# Provides behavior for retrying and discarding jobs on exceptions.
|
5
|
+
module Exceptions
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts.
|
10
|
+
# If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to
|
11
|
+
# bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a
|
12
|
+
# holding queue for inspection.
|
13
|
+
#
|
14
|
+
# You can also pass a block that'll be invoked if the retry attempts fail for custom logic rather than letting
|
15
|
+
# the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter.
|
16
|
+
#
|
17
|
+
# ==== Options
|
18
|
+
# * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
|
19
|
+
# as a computing proc that the number of executions so far as an argument, or as a symbol reference of
|
20
|
+
# <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt>
|
21
|
+
# (first wait 3s, then 18s, then 83s, etc)
|
22
|
+
# * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts)
|
23
|
+
# * <tt>:queue</tt> - Re-enqueues the job on a different queue
|
24
|
+
# * <tt>:priority</tt> - Re-enqueues the job with a different priority
|
25
|
+
#
|
26
|
+
# ==== Examples
|
27
|
+
#
|
28
|
+
# class RemoteServiceJob < ActiveJob::Base
|
29
|
+
# retry_on CustomAppException # defaults to 3s wait, 5 attempts
|
30
|
+
# retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
|
31
|
+
# retry_on(YetAnotherCustomAppException) do |job, exception|
|
32
|
+
# ExceptionNotifier.caught(exception)
|
33
|
+
# end
|
34
|
+
# retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
|
35
|
+
# retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
|
36
|
+
#
|
37
|
+
# def perform(*args)
|
38
|
+
# # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
|
39
|
+
# # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
|
40
|
+
# # Might raise Net::OpenTimeout when the remote service is down
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
def retry_on(exception, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
|
44
|
+
rescue_from exception do |error|
|
45
|
+
if executions < attempts
|
46
|
+
logger.error "Retrying #{self.class} in #{wait} seconds, due to a #{exception}. The original exception was #{error.cause.inspect}."
|
47
|
+
retry_job wait: determine_delay(wait), queue: queue, priority: priority
|
48
|
+
else
|
49
|
+
if block_given?
|
50
|
+
yield self, exception
|
51
|
+
else
|
52
|
+
logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}."
|
53
|
+
raise error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job,
|
60
|
+
# like an Active Record, is no longer available, and the job is thus no longer relevant.
|
61
|
+
#
|
62
|
+
# ==== Example
|
63
|
+
#
|
64
|
+
# class SearchIndexingJob < ActiveJob::Base
|
65
|
+
# discard_on ActiveJob::DeserializationError
|
66
|
+
#
|
67
|
+
# def perform(record)
|
68
|
+
# # Will raise ActiveJob::DeserializationError if the record can't be deserialized
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
def discard_on(exception)
|
72
|
+
rescue_from exception do |error|
|
73
|
+
logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Reschedules the job to be re-executed. This is useful in combination
|
79
|
+
# with the +rescue_from+ option. When you rescue an exception from your job
|
80
|
+
# you can ask Active Job to retry performing your job.
|
81
|
+
#
|
82
|
+
# ==== Options
|
83
|
+
# * <tt>:wait</tt> - Enqueues the job with the specified delay in seconds
|
84
|
+
# * <tt>:wait_until</tt> - Enqueues the job at the time specified
|
85
|
+
# * <tt>:queue</tt> - Enqueues the job on the specified queue
|
86
|
+
# * <tt>:priority</tt> - Enqueues the job with the specified priority
|
87
|
+
#
|
88
|
+
# ==== Examples
|
89
|
+
#
|
90
|
+
# class SiteScraperJob < ActiveJob::Base
|
91
|
+
# rescue_from(ErrorLoadingSite) do
|
92
|
+
# retry_job queue: :low_priority
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# def perform(*args)
|
96
|
+
# # raise ErrorLoadingSite if cannot scrape
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
def retry_job(options = {})
|
100
|
+
enqueue options
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
def determine_delay(seconds_or_duration_or_algorithm)
|
105
|
+
case seconds_or_duration_or_algorithm
|
106
|
+
when :exponentially_longer
|
107
|
+
(executions**4) + 2
|
108
|
+
when ActiveSupport::Duration
|
109
|
+
duration = seconds_or_duration_or_algorithm
|
110
|
+
duration.to_i
|
111
|
+
when Integer
|
112
|
+
seconds = seconds_or_duration_or_algorithm
|
113
|
+
seconds
|
114
|
+
when Proc
|
115
|
+
algorithm = seconds_or_duration_or_algorithm
|
116
|
+
algorithm.call(executions)
|
117
|
+
else
|
118
|
+
raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/active_job/execution.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "active_support/rescuable"
|
2
|
+
require "active_job/arguments"
|
3
3
|
|
4
4
|
module ActiveJob
|
5
5
|
module Execution
|
@@ -31,6 +31,9 @@ module ActiveJob
|
|
31
31
|
def perform_now
|
32
32
|
deserialize_arguments_if_needed
|
33
33
|
run_callbacks :perform do
|
34
|
+
# Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
|
35
|
+
self.executions = (executions || 0) + 1
|
36
|
+
|
34
37
|
perform(*arguments)
|
35
38
|
end
|
36
39
|
rescue => exception
|
data/lib/active_job/logging.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "active_support/core_ext/hash/transform_values"
|
2
|
+
require "active_support/core_ext/string/filters"
|
3
|
+
require "active_support/tagged_logging"
|
4
|
+
require "active_support/logger"
|
5
5
|
|
6
6
|
module ActiveJob
|
7
7
|
module Logging #:nodoc:
|
@@ -18,7 +18,7 @@ module ActiveJob
|
|
18
18
|
|
19
19
|
around_perform do |job, block, _|
|
20
20
|
tag_logger(job.class.name, job.job_id) do
|
21
|
-
payload = {adapter: job.class.queue_adapter, job: job}
|
21
|
+
payload = { adapter: job.class.queue_adapter, job: job }
|
22
22
|
ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
|
23
23
|
ActiveSupport::Notifications.instrument("perform.active_job", payload) do
|
24
24
|
block.call
|
@@ -41,7 +41,7 @@ module ActiveJob
|
|
41
41
|
def tag_logger(*tags)
|
42
42
|
if logger.respond_to?(:tagged)
|
43
43
|
tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
|
44
|
-
logger.tagged(*tags){ yield }
|
44
|
+
logger.tagged(*tags) { yield }
|
45
45
|
else
|
46
46
|
yield
|
47
47
|
end
|
@@ -51,70 +51,70 @@ module ActiveJob
|
|
51
51
|
logger.formatter.current_tags.include?("ActiveJob")
|
52
52
|
end
|
53
53
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
|
55
|
+
def enqueue(event)
|
56
|
+
info do
|
57
|
+
job = event.payload[:job]
|
58
|
+
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
|
59
|
+
end
|
59
60
|
end
|
60
|
-
end
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
def enqueue_at(event)
|
63
|
+
info do
|
64
|
+
job = event.payload[:job]
|
65
|
+
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
|
66
|
+
end
|
66
67
|
end
|
67
|
-
end
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
def perform_start(event)
|
70
|
+
info do
|
71
|
+
job = event.payload[:job]
|
72
|
+
"Performing #{job.class.name} from #{queue_name(event)}" + args_info(job)
|
73
|
+
end
|
73
74
|
end
|
74
|
-
end
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
def perform(event)
|
77
|
+
info do
|
78
|
+
job = event.payload[:job]
|
79
|
+
"Performed #{job.class.name} from #{queue_name(event)} in #{event.duration.round(2)}ms"
|
80
|
+
end
|
80
81
|
end
|
81
|
-
end
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
private
|
84
|
+
def queue_name(event)
|
85
|
+
event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
|
86
|
+
end
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
88
|
+
def args_info(job)
|
89
|
+
if job.arguments.any?
|
90
|
+
" with arguments: " +
|
91
|
+
job.arguments.map { |arg| format(arg).inspect }.join(", ")
|
92
|
+
else
|
93
|
+
""
|
94
|
+
end
|
94
95
|
end
|
95
|
-
end
|
96
96
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
97
|
+
def format(arg)
|
98
|
+
case arg
|
99
|
+
when Hash
|
100
|
+
arg.transform_values { |value| format(value) }
|
101
|
+
when Array
|
102
|
+
arg.map { |value| format(value) }
|
103
|
+
when GlobalID::Identification
|
104
|
+
arg.to_global_id rescue arg
|
105
|
+
else
|
106
|
+
arg
|
107
|
+
end
|
107
108
|
end
|
108
|
-
end
|
109
109
|
|
110
|
-
|
111
|
-
|
112
|
-
|
110
|
+
def scheduled_at(event)
|
111
|
+
Time.at(event.payload[:job].scheduled_at).utc
|
112
|
+
end
|
113
113
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
114
|
+
def logger
|
115
|
+
ActiveJob::Base.logger
|
116
|
+
end
|
117
|
+
end
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
@@ -1,6 +1,4 @@
|
|
1
|
-
require
|
2
|
-
require 'active_support/core_ext/class/attribute'
|
3
|
-
require 'active_support/core_ext/string/inflections'
|
1
|
+
require "active_support/core_ext/string/inflections"
|
4
2
|
|
5
3
|
module ActiveJob
|
6
4
|
# The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
|
@@ -30,34 +28,24 @@ module ActiveJob
|
|
30
28
|
|
31
29
|
private
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
else
|
38
|
-
if queue_adapter?(name_or_adapter_or_class)
|
39
|
-
name_or_adapter_or_class
|
40
|
-
elsif queue_adapter_class?(name_or_adapter_or_class)
|
41
|
-
ActiveSupport::Deprecation.warn "Passing an adapter class is deprecated " \
|
42
|
-
"and will be removed in Rails 5.1. Please pass an adapter name " \
|
43
|
-
"(.queue_adapter = :#{name_or_adapter_or_class.name.demodulize.remove('Adapter').underscore}) " \
|
44
|
-
"or an instance (.queue_adapter = #{name_or_adapter_or_class.name}.new) instead."
|
45
|
-
name_or_adapter_or_class.new
|
31
|
+
def interpret_adapter(name_or_adapter_or_class)
|
32
|
+
case name_or_adapter_or_class
|
33
|
+
when Symbol, String
|
34
|
+
ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new
|
46
35
|
else
|
47
|
-
|
36
|
+
if queue_adapter?(name_or_adapter_or_class)
|
37
|
+
name_or_adapter_or_class
|
38
|
+
else
|
39
|
+
raise ArgumentError
|
40
|
+
end
|
48
41
|
end
|
49
42
|
end
|
50
|
-
end
|
51
|
-
|
52
|
-
QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze
|
53
43
|
|
54
|
-
|
55
|
-
QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) }
|
56
|
-
end
|
44
|
+
QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze
|
57
45
|
|
58
|
-
|
59
|
-
|
60
|
-
|
46
|
+
def queue_adapter?(object)
|
47
|
+
QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) }
|
48
|
+
end
|
61
49
|
end
|
62
50
|
end
|
63
51
|
end
|
@@ -8,7 +8,7 @@ module ActiveJob
|
|
8
8
|
# * {Qu}[https://github.com/bkeepers/qu]
|
9
9
|
# * {Que}[https://github.com/chanks/que]
|
10
10
|
# * {queue_classic}[https://github.com/QueueClassic/queue_classic]
|
11
|
-
# * {Resque
|
11
|
+
# * {Resque}[https://github.com/resque/resque]
|
12
12
|
# * {Sidekiq}[http://sidekiq.org]
|
13
13
|
# * {Sneakers}[https://github.com/jondot/sneakers]
|
14
14
|
# * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
|
@@ -121,7 +121,7 @@ module ActiveJob
|
|
121
121
|
autoload :SuckerPunchAdapter
|
122
122
|
autoload :TestAdapter
|
123
123
|
|
124
|
-
ADAPTER =
|
124
|
+
ADAPTER = "Adapter".freeze
|
125
125
|
private_constant :ADAPTER
|
126
126
|
|
127
127
|
class << self
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "securerandom"
|
2
|
+
require "concurrent/scheduled_task"
|
3
|
+
require "concurrent/executor/thread_pool_executor"
|
4
|
+
require "concurrent/utility/processor_counter"
|
5
5
|
|
6
6
|
module ActiveJob
|
7
7
|
module QueueAdapters
|