activejob 7.0.8 → 7.2.0

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 +35 -214
  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 +3 -3
  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 +14 -10
  46. data/lib/active_job/queue_adapters/que_adapter.rb +0 -61
@@ -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,15 +1,15 @@
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
11
+ MINOR = 2
12
+ TINY = 0
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -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 :enqueue_retry, :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:
@@ -5,7 +5,7 @@ require "active_support/core_ext/string/inflections"
5
5
 
6
6
  module ActiveJob
7
7
  module QueueAdapters
8
- # == Delayed Job adapter for Active Job
8
+ # = Delayed Job adapter for Active Job
9
9
  #
10
10
  # Delayed::Job (or DJ) encapsulates the common pattern of asynchronously
11
11
  # executing longer tasks in the background. Although DJ can have many
@@ -15,7 +15,15 @@ module ActiveJob
15
15
  # To use Delayed Job, set the queue_adapter config to +:delayed_job+.
16
16
  #
17
17
  # Rails.application.config.active_job.queue_adapter = :delayed_job
18
- class DelayedJobAdapter
18
+ class DelayedJobAdapter < AbstractAdapter
19
+ def initialize(enqueue_after_transaction_commit: false)
20
+ @enqueue_after_transaction_commit = enqueue_after_transaction_commit
21
+ end
22
+
23
+ def enqueue_after_transaction_commit? # :nodoc:
24
+ @enqueue_after_transaction_commit
25
+ end
26
+
19
27
  def enqueue(job) # :nodoc:
20
28
  delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, priority: job.priority)
21
29
  job.provider_job_id = delayed_job.id
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module QueueAdapters
5
- # == Active Job Inline adapter
5
+ # = Active Job Inline adapter
6
6
  #
7
7
  # When enqueuing jobs with the Inline adapter the job will be executed
8
8
  # immediately.
@@ -10,7 +10,11 @@ module ActiveJob
10
10
  # To use the Inline set the queue_adapter config to +:inline+.
11
11
  #
12
12
  # Rails.application.config.active_job.queue_adapter = :inline
13
- class InlineAdapter
13
+ class InlineAdapter < AbstractAdapter
14
+ def enqueue_after_transaction_commit? # :nodoc:
15
+ false
16
+ end
17
+
14
18
  def enqueue(job) # :nodoc:
15
19
  Base.execute(job.serialize)
16
20
  end
@@ -4,7 +4,7 @@ require "queue_classic"
4
4
 
5
5
  module ActiveJob
6
6
  module QueueAdapters
7
- # == queue_classic adapter for Active Job
7
+ # = queue_classic adapter for Active Job
8
8
  #
9
9
  # queue_classic provides a simple interface to a PostgreSQL-backed message
10
10
  # queue. queue_classic specializes in concurrent locking and minimizing
@@ -18,7 +18,15 @@ module ActiveJob
18
18
  # To use queue_classic set the queue_adapter config to +:queue_classic+.
19
19
  #
20
20
  # Rails.application.config.active_job.queue_adapter = :queue_classic
21
- class QueueClassicAdapter
21
+ class QueueClassicAdapter < AbstractAdapter
22
+ def initialize(enqueue_after_transaction_commit: false)
23
+ @enqueue_after_transaction_commit = enqueue_after_transaction_commit
24
+ end
25
+
26
+ def enqueue_after_transaction_commit? # :nodoc:
27
+ @enqueue_after_transaction_commit
28
+ end
29
+
22
30
  def enqueue(job) # :nodoc:
23
31
  qc_job = build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
24
32
  job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash)
@@ -37,10 +45,10 @@ module ActiveJob
37
45
  qc_job
38
46
  end
39
47
 
40
- # Builds a <tt>QC::Queue</tt> object to schedule jobs on.
48
+ # Builds a +QC::Queue+ object to schedule jobs on.
41
49
  #
42
- # If you have a custom <tt>QC::Queue</tt> subclass you'll need to subclass
43
- # <tt>ActiveJob::QueueAdapters::QueueClassicAdapter</tt> and override the
50
+ # If you have a custom +QC::Queue+ subclass you'll need to subclass
51
+ # +ActiveJob::QueueAdapters::QueueClassicAdapter+ and override the
44
52
  # <tt>build_queue</tt> method.
45
53
  def build_queue(queue_name)
46
54
  QC::Queue.new(queue_name)
@@ -16,7 +16,7 @@ end
16
16
 
17
17
  module ActiveJob
18
18
  module QueueAdapters
19
- # == Resque adapter for Active Job
19
+ # = Resque adapter for Active Job
20
20
  #
21
21
  # Resque (pronounced like "rescue") is a Redis-backed library for creating
22
22
  # background jobs, placing those jobs on multiple queues, and processing
@@ -27,7 +27,7 @@ module ActiveJob
27
27
  # To use Resque set the queue_adapter config to +:resque+.
28
28
  #
29
29
  # Rails.application.config.active_job.queue_adapter = :resque
30
- class ResqueAdapter
30
+ class ResqueAdapter < AbstractAdapter
31
31
  def enqueue(job) # :nodoc:
32
32
  JobWrapper.instance_variable_set(:@queue, job.queue_name)
33
33
  Resque.enqueue_to job.queue_name, JobWrapper, job.serialize