activejob 7.0.4 → 7.1.5.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +222 -94
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +4 -4
  5. data/lib/active_job/arguments.rb +28 -31
  6. data/lib/active_job/base.rb +1 -1
  7. data/lib/active_job/callbacks.rb +3 -5
  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 -3
  12. data/lib/active_job/exceptions.rb +52 -8
  13. data/lib/active_job/execution.rb +11 -2
  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 +81 -9
  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 +3 -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 +3 -3
  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_name.rb +1 -2
  30. data/lib/active_job/queue_priority.rb +18 -1
  31. data/lib/active_job/railtie.rb +26 -7
  32. data/lib/active_job/serializers/big_decimal_serializer.rb +22 -0
  33. data/lib/active_job/serializers/duration_serializer.rb +4 -2
  34. data/lib/active_job/serializers/time_with_zone_serializer.rb +11 -2
  35. data/lib/active_job/serializers.rb +7 -3
  36. data/lib/active_job/test_helper.rb +32 -14
  37. data/lib/active_job/version.rb +1 -1
  38. data/lib/active_job.rb +29 -4
  39. data/lib/rails/generators/job/USAGE +19 -0
  40. data/lib/rails/generators/job/job_generator.rb +6 -2
  41. data/lib/rails/generators/job/templates/job.rb.tt +1 -1
  42. metadata +14 -12
  43. data/lib/active_job/queue_adapters/que_adapter.rb +0 -61
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveJob
4
+ # = Active Job \Core
5
+ #
4
6
  # Provides general behavior that will be included into every Active Job
5
7
  # object that inherits from ActiveJob::Base.
6
8
  module Core
@@ -10,8 +12,10 @@ module ActiveJob
10
12
  attr_accessor :arguments
11
13
  attr_writer :serialized_arguments
12
14
 
13
- # Timestamp when the job should be performed
14
- attr_accessor :scheduled_at
15
+ # Time when the job should be performed
16
+ attr_reader :scheduled_at
17
+
18
+ attr_reader :_scheduled_at_time # :nodoc:
15
19
 
16
20
  # Job Identifier
17
21
  attr_accessor :job_id
@@ -92,6 +96,8 @@ module ActiveJob
92
96
  @arguments = arguments
93
97
  @job_id = SecureRandom.uuid
94
98
  @queue_name = self.class.queue_name
99
+ @scheduled_at = nil
100
+ @_scheduled_at_time = nil
95
101
  @priority = self.class.priority
96
102
  @executions = 0
97
103
  @exception_executions = {}
@@ -113,7 +119,8 @@ module ActiveJob
113
119
  "exception_executions" => exception_executions,
114
120
  "locale" => I18n.locale.to_s,
115
121
  "timezone" => timezone,
116
- "enqueued_at" => Time.now.utc.iso8601
122
+ "enqueued_at" => Time.now.utc.iso8601(9),
123
+ "scheduled_at" => _scheduled_at_time ? _scheduled_at_time.utc.iso8601(9) : nil,
117
124
  }
118
125
  end
119
126
 
@@ -153,19 +160,32 @@ module ActiveJob
153
160
  self.exception_executions = job_data["exception_executions"]
154
161
  self.locale = job_data["locale"] || I18n.locale.to_s
155
162
  self.timezone = job_data["timezone"] || Time.zone&.name
156
- self.enqueued_at = job_data["enqueued_at"]
163
+ self.enqueued_at = Time.iso8601(job_data["enqueued_at"]) if job_data["enqueued_at"]
164
+ self.scheduled_at = Time.iso8601(job_data["scheduled_at"]) if job_data["scheduled_at"]
157
165
  end
158
166
 
159
167
  # Configures the job with the given options.
160
168
  def set(options = {}) # :nodoc:
161
- self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait]
162
- self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
169
+ self.scheduled_at = options[:wait].seconds.from_now if options[:wait]
170
+ self.scheduled_at = options[:wait_until] if options[:wait_until]
163
171
  self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
164
172
  self.priority = options[:priority].to_i if options[:priority]
165
173
 
166
174
  self
167
175
  end
168
176
 
177
+ def scheduled_at=(value)
178
+ @_scheduled_at_time = if value&.is_a?(Numeric)
179
+ ActiveJob.deprecator.warn(<<~MSG.squish)
180
+ Assigning a numeric/epoch value to scheduled_at is deprecated. Use a Time object instead.
181
+ MSG
182
+ Time.at(value)
183
+ else
184
+ value
185
+ end
186
+ @scheduled_at = value
187
+ end
188
+
169
189
  private
170
190
  def serialize_arguments_if_needed(arguments)
171
191
  if arguments_serialized?
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -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,6 +7,36 @@ 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_time.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
 
@@ -62,7 +90,7 @@ module ActiveJob
62
90
 
63
91
  run_callbacks :enqueue do
64
92
  if scheduled_at
65
- queue_adapter.enqueue_at self, scheduled_at
93
+ queue_adapter.enqueue_at self, _scheduled_at_time.to_f
66
94
  else
67
95
  queue_adapter.enqueue self
68
96
  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,10 +21,13 @@ 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
32
  # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts) or a symbol reference of <tt>:unlimited</tt>
29
33
  # to retry the job until it succeeds
@@ -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)
@@ -56,6 +60,12 @@ module ActiveJob
56
60
  # end
57
61
  # end
58
62
  def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT)
63
+ if wait == :exponentially_longer
64
+ ActiveJob.deprecator.warn(<<~MSG.squish)
65
+ `wait: :exponentially_longer` will actually wait polynomially longer and is therefore deprecated.
66
+ Prefer `wait: :polynomially_longer` to avoid confusion and keep the same behavior.
67
+ MSG
68
+ end
59
69
  rescue_from(*exceptions) do |error|
60
70
  executions = executions_for(exceptions)
61
71
  if attempts == :unlimited || executions < attempts
@@ -65,8 +75,10 @@ module ActiveJob
65
75
  instrument :retry_stopped, error: error do
66
76
  yield self, error
67
77
  end
78
+ run_after_discard_procs(error)
68
79
  else
69
80
  instrument :retry_stopped, error: error
81
+ run_after_discard_procs(error)
70
82
  raise error
71
83
  end
72
84
  end
@@ -78,6 +90,9 @@ module ActiveJob
78
90
  #
79
91
  # 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
92
  #
93
+ # `retry_on` and `discard_on` handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for
94
+ # which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if any.
95
+ #
81
96
  # ==== Example
82
97
  #
83
98
  # class SearchIndexingJob < ActiveJob::Base
@@ -95,14 +110,32 @@ module ActiveJob
95
110
  rescue_from(*exceptions) do |error|
96
111
  instrument :discard, error: error do
97
112
  yield self, error if block_given?
113
+ run_after_discard_procs(error)
98
114
  end
99
115
  end
100
116
  end
117
+
118
+ # A block to run when a job is about to be discarded for any reason.
119
+ #
120
+ # ==== Example
121
+ #
122
+ # class WorkJob < ActiveJob::Base
123
+ # after_discard do |job, exception|
124
+ # ExceptionNotifier.report(exception)
125
+ # end
126
+ #
127
+ # ...
128
+ #
129
+ # end
130
+ def after_discard(&blk)
131
+ self.after_discard_procs += [blk]
132
+ end
101
133
  end
102
134
 
103
- # Reschedules the job to be re-executed. This is useful in combination
104
- # with the +rescue_from+ option. When you rescue an exception from your job
105
- # you can ask Active Job to retry performing your job.
135
+ # Reschedules the job to be re-executed. This is useful in combination with
136
+ # {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from].
137
+ # When you rescue an exception from your job you can ask Active Job to retry
138
+ # performing your job.
106
139
  #
107
140
  # ==== Options
108
141
  # * <tt>:wait</tt> - Enqueues the job with the specified delay in seconds
@@ -135,7 +168,8 @@ module ActiveJob
135
168
  jitter = jitter == JITTER_DEFAULT ? self.class.retry_jitter : (jitter || 0.0)
136
169
 
137
170
  case seconds_or_duration_or_algorithm
138
- when :exponentially_longer
171
+ when :exponentially_longer, :polynomially_longer
172
+ # This delay uses a polynomial backoff strategy, which was previously misnamed as exponential
139
173
  delay = executions**4
140
174
  delay_jitter = determine_jitter_for_delay(delay, jitter)
141
175
  delay + delay_jitter + 2
@@ -164,5 +198,15 @@ module ActiveJob
164
198
  executions
165
199
  end
166
200
  end
201
+
202
+ def run_after_discard_procs(exception)
203
+ exceptions = []
204
+ after_discard_procs.each do |blk|
205
+ instance_exec(self, exception, &blk)
206
+ rescue StandardError => e
207
+ exceptions << e
208
+ end
209
+ raise exceptions.last unless exceptions.empty?
210
+ end
167
211
  end
168
212
  end
@@ -1,9 +1,14 @@
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
6
+ # = Active Job \Execution
7
+ #
8
+ # Provides methods to execute jobs immediately, and wraps job execution so
9
+ # that exceptions configured with
10
+ # {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
11
+ # are handled.
7
12
  module Execution
8
13
  extend ActiveSupport::Concern
9
14
  include ActiveSupport::Rescuable
@@ -45,7 +50,11 @@ module ActiveJob
45
50
 
46
51
  _perform_job
47
52
  rescue Exception => exception
48
- 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
49
58
  end
50
59
 
51
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 = 4
13
- PRE = nil
11
+ MINOR = 1
12
+ TINY = 5
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,13 +1,14 @@
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
- ex = event.payload[:exception_object]
11
+ ex = event.payload[:exception_object] || job.enqueue_error
11
12
 
12
13
  if ex
13
14
  error do
@@ -23,10 +24,11 @@ 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]
29
- ex = event.payload[:exception_object]
31
+ ex = event.payload[:exception_object] || job.enqueue_error
30
32
 
31
33
  if ex
32
34
  error do
@@ -42,13 +44,44 @@ module ActiveJob
42
44
  end
43
45
  end
44
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)}"
71
+ end
72
+ end
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
 
@@ -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
@@ -50,6 +50,8 @@ module ActiveJob
50
50
  private
51
51
  def log_arguments?
52
52
  job_data["job_class"].constantize.log_arguments?
53
+ rescue NameError
54
+ false
53
55
  end
54
56
  end
55
57
  end
@@ -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