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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -229
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +2 -2
  5. data/lib/active_job/arguments.rb +18 -32
  6. data/lib/active_job/base.rb +1 -1
  7. data/lib/active_job/callbacks.rb +3 -8
  8. data/lib/active_job/configured_job.rb +4 -0
  9. data/lib/active_job/core.rb +11 -6
  10. data/lib/active_job/deprecator.rb +7 -0
  11. data/lib/active_job/enqueue_after_transaction_commit.rb +28 -0
  12. data/lib/active_job/enqueuing.rb +71 -12
  13. data/lib/active_job/exceptions.rb +44 -7
  14. data/lib/active_job/execution.rb +5 -2
  15. data/lib/active_job/gem_version.rb +4 -4
  16. data/lib/active_job/instrumentation.rb +18 -10
  17. data/lib/active_job/log_subscriber.rb +80 -8
  18. data/lib/active_job/logging.rb +16 -2
  19. data/lib/active_job/queue_adapter.rb +18 -6
  20. data/lib/active_job/queue_adapters/abstract_adapter.rb +27 -0
  21. data/lib/active_job/queue_adapters/async_adapter.rb +3 -3
  22. data/lib/active_job/queue_adapters/backburner_adapter.rb +8 -4
  23. data/lib/active_job/queue_adapters/delayed_job_adapter.rb +10 -2
  24. data/lib/active_job/queue_adapters/inline_adapter.rb +6 -2
  25. data/lib/active_job/queue_adapters/queue_classic_adapter.rb +13 -5
  26. data/lib/active_job/queue_adapters/resque_adapter.rb +2 -2
  27. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +43 -15
  28. data/lib/active_job/queue_adapters/sneakers_adapter.rb +2 -2
  29. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +4 -4
  30. data/lib/active_job/queue_adapters/test_adapter.rb +13 -5
  31. data/lib/active_job/queue_adapters.rb +9 -7
  32. data/lib/active_job/queue_priority.rb +18 -1
  33. data/lib/active_job/railtie.rb +38 -7
  34. data/lib/active_job/serializers/big_decimal_serializer.rb +22 -0
  35. data/lib/active_job/serializers/duration_serializer.rb +4 -2
  36. data/lib/active_job/serializers/object_serializer.rb +2 -0
  37. data/lib/active_job/serializers/time_with_zone_serializer.rb +11 -2
  38. data/lib/active_job/serializers.rb +7 -3
  39. data/lib/active_job/test_helper.rb +60 -19
  40. data/lib/active_job/version.rb +1 -1
  41. data/lib/active_job.rb +34 -4
  42. data/lib/rails/generators/job/USAGE +19 -0
  43. data/lib/rails/generators/job/job_generator.rb +6 -2
  44. data/lib/rails/generators/job/templates/job.rb.tt +1 -1
  45. metadata +12 -8
  46. data/lib/active_job/queue_adapters/que_adapter.rb +0 -61
@@ -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>:exponentially_longer</tt>, which applies the wait algorithm of <tt>((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2</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> - Re-enqueues the job the specified number of times (default: 5 attempts) or a symbol reference of <tt>:unlimited</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: :exponentially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
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: :exponentially_longer, attempts: 10
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: :exponentially_longer, attempts: 10
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 :exponentially_longer
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
@@ -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) || raise
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 <tt>Gem::Version</tt>.
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 = 0
12
- TINY = 8
13
- PRE = "7"
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
- enhanced_block = ->(event_payload) do
25
- value = block.call if block
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).to_default_s}" + args_info(job)
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
- "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} enqueued at #{job.enqueued_at}" + args_info(job)
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].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
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
 
@@ -4,17 +4,31 @@ require "active_support/tagged_logging"
4
4
  require "active_support/logger"
5
5
 
6
6
  module ActiveJob
7
- module Logging # :nodoc:
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
- # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
7
- # correct adapter. The default queue adapter is the +:async+ queue.
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 the +:async+ queue. See QueueAdapters for more information.
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 = "#{name_or_adapter.class.name.demodulize.remove('Adapter').underscore}"
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
- # == Active Job Async adapter
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
- # == Backburner adapter for Active Job
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: