activejob 7.0.8 → 7.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -150
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +2 -2
  5. data/lib/active_job/arguments.rb +14 -25
  6. data/lib/active_job/base.rb +1 -1
  7. data/lib/active_job/callbacks.rb +1 -4
  8. data/lib/active_job/configured_job.rb +4 -0
  9. data/lib/active_job/core.rb +26 -6
  10. data/lib/active_job/deprecator.rb +7 -0
  11. data/lib/active_job/enqueuing.rb +31 -1
  12. data/lib/active_job/exceptions.rb +42 -5
  13. data/lib/active_job/execution.rb +5 -1
  14. data/lib/active_job/gem_version.rb +4 -4
  15. data/lib/active_job/instrumentation.rb +18 -10
  16. data/lib/active_job/log_subscriber.rb +78 -8
  17. data/lib/active_job/queue_adapter.rb +13 -2
  18. data/lib/active_job/queue_adapters/async_adapter.rb +2 -2
  19. data/lib/active_job/queue_adapters/backburner_adapter.rb +7 -3
  20. data/lib/active_job/queue_adapters/delayed_job_adapter.rb +1 -1
  21. data/lib/active_job/queue_adapters/inline_adapter.rb +1 -1
  22. data/lib/active_job/queue_adapters/queue_classic_adapter.rb +4 -4
  23. data/lib/active_job/queue_adapters/resque_adapter.rb +1 -1
  24. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +42 -14
  25. data/lib/active_job/queue_adapters/sneakers_adapter.rb +1 -1
  26. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +2 -2
  27. data/lib/active_job/queue_adapters/test_adapter.rb +3 -3
  28. data/lib/active_job/queue_adapters.rb +8 -7
  29. data/lib/active_job/queue_priority.rb +18 -1
  30. data/lib/active_job/railtie.rb +25 -6
  31. data/lib/active_job/serializers/big_decimal_serializer.rb +22 -0
  32. data/lib/active_job/serializers/duration_serializer.rb +4 -2
  33. data/lib/active_job/serializers.rb +7 -3
  34. data/lib/active_job/test_helper.rb +23 -3
  35. data/lib/active_job/version.rb +1 -1
  36. data/lib/active_job.rb +26 -4
  37. data/lib/rails/generators/job/USAGE +19 -0
  38. data/lib/rails/generators/job/job_generator.rb +6 -2
  39. data/lib/rails/generators/job/templates/job.rb.tt +1 -1
  40. metadata +12 -10
  41. 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
@@ -23,7 +24,7 @@ module ActiveJob
23
24
  # ==== Options
24
25
  # * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
25
26
  # 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>
27
+ # <tt>:polynomially_longer</tt>, which applies the wait algorithm of <tt>((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2</tt>
27
28
  # (first wait ~3s, then ~18s, then ~83s, etc)
28
29
  # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts) or a symbol reference of <tt>:unlimited</tt>
29
30
  # to retry the job until it succeeds
@@ -39,11 +40,11 @@ module ActiveJob
39
40
  # retry_on CustomInfrastructureException, wait: 5.minutes, attempts: :unlimited
40
41
  #
41
42
  # 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
43
+ # retry_on Net::OpenTimeout, Timeout::Error, wait: :polynomially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
43
44
  # # To retry at most 10 times for each individual exception:
44
- # # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
45
+ # # retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10
45
46
  # # retry_on Net::ReadTimeout, wait: 5.seconds, jitter: 0.30, attempts: 10
46
- # # retry_on Timeout::Error, wait: :exponentially_longer, attempts: 10
47
+ # # retry_on Timeout::Error, wait: :polynomially_longer, attempts: 10
47
48
  #
48
49
  # retry_on(YetAnotherCustomAppException) do |job, error|
49
50
  # ExceptionNotifier.caught(error)
@@ -56,6 +57,12 @@ module ActiveJob
56
57
  # end
57
58
  # end
58
59
  def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT)
60
+ if wait == :exponentially_longer
61
+ ActiveJob.deprecator.warn(<<~MSG.squish)
62
+ `wait: :exponentially_longer` will actually wait polynomially longer and is therefore deprecated.
63
+ Prefer `wait: :polynomially_longer` to avoid confusion and keep the same behavior.
64
+ MSG
65
+ end
59
66
  rescue_from(*exceptions) do |error|
60
67
  executions = executions_for(exceptions)
61
68
  if attempts == :unlimited || executions < attempts
@@ -65,8 +72,10 @@ module ActiveJob
65
72
  instrument :retry_stopped, error: error do
66
73
  yield self, error
67
74
  end
75
+ run_after_discard_procs(error)
68
76
  else
69
77
  instrument :retry_stopped, error: error
78
+ run_after_discard_procs(error)
70
79
  raise error
71
80
  end
72
81
  end
@@ -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 :exponentially_longer, :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
@@ -51,7 +51,11 @@ module ActiveJob
51
51
 
52
52
  _perform_job
53
53
  rescue Exception => exception
54
- rescue_with_handler(exception) || raise
54
+ handled = rescue_with_handler(exception)
55
+ return handled if handled
56
+
57
+ run_after_discard_procs(exception)
58
+ raise
55
59
  end
56
60
 
57
61
  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 = nil
11
+ MINOR = 1
12
+ TINY = 0
13
+ PRE = "rc1"
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,46 @@ 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
+ "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} enqueued at #{job.enqueued_at.utc.iso8601(9)}" + args_info(job)
50
80
  end
51
81
  end
82
+ subscribe_log_level :perform_start, :info
52
83
 
53
84
  def perform(event)
54
85
  job = event.payload[:job]
@@ -67,6 +98,7 @@ module ActiveJob
67
98
  end
68
99
  end
69
100
  end
101
+ subscribe_log_level :perform, :info
70
102
 
71
103
  def enqueue_retry(event)
72
104
  job = event.payload[:job]
@@ -75,34 +107,37 @@ module ActiveJob
75
107
 
76
108
  info do
77
109
  if ex
78
- "Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
110
+ "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
111
  else
80
- "Retrying #{job.class} in #{wait.to_i} seconds."
112
+ "Retrying #{job.class} (Job ID: #{job.job_id}) after #{job.executions} attempts in #{wait.to_i} seconds."
81
113
  end
82
114
  end
83
115
  end
116
+ subscribe_log_level :enqueue_retry, :info
84
117
 
85
118
  def retry_stopped(event)
86
119
  job = event.payload[:job]
87
120
  ex = event.payload[:error]
88
121
 
89
122
  error do
90
- "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
123
+ "Stopped retrying #{job.class} (Job ID: #{job.job_id}) due to a #{ex.class} (#{ex.message}), which reoccurred on #{job.executions} attempts."
91
124
  end
92
125
  end
126
+ subscribe_log_level :enqueue_retry, :error
93
127
 
94
128
  def discard(event)
95
129
  job = event.payload[:job]
96
130
  ex = event.payload[:error]
97
131
 
98
132
  error do
99
- "Discarded #{job.class} due to a #{ex.class}."
133
+ "Discarded #{job.class} (Job ID: #{job.job_id}) due to a #{ex.class} (#{ex.message})."
100
134
  end
101
135
  end
136
+ subscribe_log_level :discard, :error
102
137
 
103
138
  private
104
139
  def queue_name(event)
105
- event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
140
+ ActiveJob.adapter_name(event.payload[:adapter]) + "(#{event.payload[:job].queue_name})"
106
141
  end
107
142
 
108
143
  def args_info(job)
@@ -134,6 +169,41 @@ module ActiveJob
134
169
  def logger
135
170
  ActiveJob::Base.logger
136
171
  end
172
+
173
+ def info(progname = nil, &block)
174
+ return unless super
175
+
176
+ if ActiveJob.verbose_enqueue_logs
177
+ log_enqueue_source
178
+ end
179
+ end
180
+
181
+ def error(progname = nil, &block)
182
+ return unless super
183
+
184
+ if ActiveJob.verbose_enqueue_logs
185
+ log_enqueue_source
186
+ end
187
+ end
188
+
189
+ def log_enqueue_source
190
+ source = extract_enqueue_source_location(caller)
191
+
192
+ if source
193
+ logger.info("↳ #{source}")
194
+ end
195
+ end
196
+
197
+ def extract_enqueue_source_location(locations)
198
+ backtrace_cleaner.clean(locations.lazy).first
199
+ end
200
+
201
+ def enqueued_jobs_message(adapter, enqueued_jobs)
202
+ enqueued_count = enqueued_jobs.size
203
+ job_classes_counts = enqueued_jobs.map(&:class).tally.sort_by { |_k, v| -v }
204
+ "Enqueued #{enqueued_count} #{'job'.pluralize(enqueued_count)} to #{ActiveJob.adapter_name(adapter)}"\
205
+ " (#{job_classes_counts.map { |klass, count| "#{count} #{klass}" }.join(', ')})"
206
+ end
137
207
  end
138
208
  end
139
209
 
@@ -3,7 +3,18 @@
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
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
7
18
  # correct adapter. The default queue adapter is the +:async+ queue.
8
19
  module QueueAdapter # :nodoc:
9
20
  extend ActiveSupport::Concern
@@ -41,7 +52,7 @@ module ActiveJob
41
52
  assign_adapter(name_or_adapter.to_s, queue_adapter)
42
53
  else
43
54
  if queue_adapter?(name_or_adapter)
44
- adapter_name = "#{name_or_adapter.class.name.demodulize.remove('Adapter').underscore}"
55
+ adapter_name = ActiveJob.adapter_name(name_or_adapter).underscore
45
56
  assign_adapter(adapter_name, name_or_adapter)
46
57
  else
47
58
  raise ArgumentError
@@ -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
  #
@@ -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
@@ -16,12 +16,16 @@ 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(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
@@ -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.
@@ -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
@@ -37,10 +37,10 @@ module ActiveJob
37
37
  qc_job
38
38
  end
39
39
 
40
- # Builds a <tt>QC::Queue</tt> object to schedule jobs on.
40
+ # Builds a +QC::Queue+ object to schedule jobs on.
41
41
  #
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
42
+ # If you have a custom +QC::Queue+ subclass you'll need to subclass
43
+ # +ActiveJob::QueueAdapters::QueueClassicAdapter+ and override the
44
44
  # <tt>build_queue</tt> method.
45
45
  def build_queue(queue_name)
46
46
  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
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ gem "sidekiq", ">= 4.1.0"
3
4
  require "sidekiq"
4
5
 
5
6
  module ActiveJob
6
7
  module QueueAdapters
7
- # == Sidekiq adapter for Active Job
8
+ # = Sidekiq adapter for Active Job
8
9
  #
9
10
  # Simple, efficient background processing for Ruby. Sidekiq uses threads to
10
11
  # handle many jobs at the same time in the same process. It does not
11
- # require Rails but will integrate tightly with it to make background
12
+ # require \Rails but will integrate tightly with it to make background
12
13
  # processing dead simple.
13
14
  #
14
15
  # Read more about Sidekiq {here}[http://sidekiq.org].
@@ -18,21 +19,48 @@ module ActiveJob
18
19
  # Rails.application.config.active_job.queue_adapter = :sidekiq
19
20
  class SidekiqAdapter
20
21
  def enqueue(job) # :nodoc:
21
- # Sidekiq::Client does not support symbols as keys
22
- job.provider_job_id = Sidekiq::Client.push \
23
- "class" => JobWrapper,
24
- "wrapped" => job.class,
25
- "queue" => job.queue_name,
26
- "args" => [ job.serialize ]
22
+ job.provider_job_id = JobWrapper.set(
23
+ wrapped: job.class,
24
+ queue: job.queue_name
25
+ ).perform_async(job.serialize)
27
26
  end
28
27
 
29
28
  def enqueue_at(job, timestamp) # :nodoc:
30
- job.provider_job_id = Sidekiq::Client.push \
31
- "class" => JobWrapper,
32
- "wrapped" => job.class,
33
- "queue" => job.queue_name,
34
- "args" => [ job.serialize ],
35
- "at" => timestamp
29
+ job.provider_job_id = JobWrapper.set(
30
+ wrapped: job.class,
31
+ queue: job.queue_name,
32
+ ).perform_at(timestamp, job.serialize)
33
+ end
34
+
35
+ def enqueue_all(jobs) # :nodoc:
36
+ enqueued_count = 0
37
+ jobs.group_by(&:class).each do |job_class, same_class_jobs|
38
+ same_class_jobs.group_by(&:queue_name).each do |queue, same_class_and_queue_jobs|
39
+ immediate_jobs, scheduled_jobs = same_class_and_queue_jobs.partition { |job| job.scheduled_at.nil? }
40
+
41
+ if immediate_jobs.any?
42
+ jids = Sidekiq::Client.push_bulk(
43
+ "class" => JobWrapper,
44
+ "wrapped" => job_class,
45
+ "queue" => queue,
46
+ "args" => immediate_jobs.map { |job| [job.serialize] },
47
+ )
48
+ enqueued_count += jids.compact.size
49
+ end
50
+
51
+ if scheduled_jobs.any?
52
+ jids = Sidekiq::Client.push_bulk(
53
+ "class" => JobWrapper,
54
+ "wrapped" => job_class,
55
+ "queue" => queue,
56
+ "args" => scheduled_jobs.map { |job| [job.serialize] },
57
+ "at" => scheduled_jobs.map { |job| job.scheduled_at&.to_f }
58
+ )
59
+ enqueued_count += jids.compact.size
60
+ end
61
+ end
62
+ end
63
+ enqueued_count
36
64
  end
37
65
 
38
66
  class JobWrapper # :nodoc:
@@ -5,7 +5,7 @@ require "monitor"
5
5
 
6
6
  module ActiveJob
7
7
  module QueueAdapters
8
- # == Sneakers adapter for Active Job
8
+ # = Sneakers adapter for Active Job
9
9
  #
10
10
  # A high-performance RabbitMQ background processing framework for Ruby.
11
11
  # Sneakers is being used in production for both I/O and CPU intensive
@@ -4,13 +4,13 @@ require "sucker_punch"
4
4
 
5
5
  module ActiveJob
6
6
  module QueueAdapters
7
- # == Sucker Punch adapter for Active Job
7
+ # = Sucker Punch adapter for Active Job
8
8
  #
9
9
  # Sucker Punch is a single-process Ruby asynchronous processing library.
10
10
  # This reduces the cost of hosting on a service like Heroku along
11
11
  # with the memory footprint of having to maintain additional jobs if
12
12
  # hosting on a dedicated server. All queues can run within a
13
- # single application (e.g. Rails, Sinatra, etc.) process.
13
+ # single application (e.g. \Rails, Sinatra, etc.) process.
14
14
  #
15
15
  # Read more about Sucker Punch {here}[https://github.com/brandonhilkert/sucker_punch].
16
16
  #
@@ -2,11 +2,11 @@
2
2
 
3
3
  module ActiveJob
4
4
  module QueueAdapters
5
- # == Test adapter for Active Job
5
+ # = Test adapter for Active Job
6
6
  #
7
7
  # The test adapter should be used only in testing. Along with
8
8
  # ActiveJob::TestCase and ActiveJob::TestHelper
9
- # it makes a great tool to test your Rails application.
9
+ # it makes a great tool to test your \Rails application.
10
10
  #
11
11
  # To use the test adapter set +queue_adapter+ config to +:test+.
12
12
  #
@@ -59,7 +59,7 @@ module ActiveJob
59
59
  end
60
60
 
61
61
  def filtered_time?(job)
62
- job.scheduled_at > at.to_f if at && job.scheduled_at
62
+ job.scheduled_at > at if at && job.scheduled_at
63
63
  end
64
64
 
65
65
  def filtered_queue?(job)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveJob
4
- # == Active Job adapters
4
+ # = Active Job adapters
5
5
  #
6
6
  # Active Job has adapters for the following queuing backends:
7
7
  #
@@ -13,10 +13,14 @@ module ActiveJob
13
13
  # * {Sidekiq}[https://sidekiq.org]
14
14
  # * {Sneakers}[https://github.com/jondot/sneakers]
15
15
  # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
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
16
  # * Please Note: We are not accepting pull requests for new adapters. See the {README}[link:files/activejob/README_md.html] for more details.
19
17
  #
18
+ # For testing and development Active Job has three built-in adapters:
19
+ #
20
+ # * {Active Job Async}[https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html]
21
+ # * {Active Job Inline}[https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html]
22
+ # * {Active Job Test}[https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/TestAdapter.html]
23
+ #
20
24
  # === Backends Features
21
25
  #
22
26
  # | | Async | Queues | Delayed | Priorities | Timeout | Retries |
@@ -31,6 +35,7 @@ module ActiveJob
31
35
  # | Sucker Punch | Yes | Yes | Yes | No | No | No |
32
36
  # | Active Job Async | Yes | Yes | Yes | No | No | No |
33
37
  # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
38
+ # | Active Job Test | No | Yes | N/A | N/A | N/A | N/A |
34
39
  #
35
40
  # ==== Async
36
41
  #
@@ -106,10 +111,6 @@ module ActiveJob
106
111
  # N/A: The adapter does not run in a separate process, and therefore doesn't
107
112
  # support retries.
108
113
  #
109
- # === Async and Inline Queue Adapters
110
- #
111
- # Active Job has two built-in queue adapters intended for development and
112
- # testing: +:async+ and +:inline+.
113
114
  module QueueAdapters
114
115
  extend ActiveSupport::Autoload
115
116