activejob 5.2.4.4 → 6.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -50
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +18 -13
  5. data/lib/active_job.rb +2 -1
  6. data/lib/active_job/arguments.rb +80 -30
  7. data/lib/active_job/base.rb +6 -1
  8. data/lib/active_job/callbacks.rb +46 -3
  9. data/lib/active_job/configured_job.rb +2 -0
  10. data/lib/active_job/core.rb +40 -21
  11. data/lib/active_job/enqueuing.rb +20 -7
  12. data/lib/active_job/exceptions.rb +60 -28
  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 +40 -0
  16. data/lib/active_job/log_subscriber.rb +140 -0
  17. data/lib/active_job/logging.rb +3 -101
  18. data/lib/active_job/queue_adapter.rb +5 -0
  19. data/lib/active_job/queue_adapters.rb +13 -11
  20. data/lib/active_job/queue_adapters/async_adapter.rb +1 -1
  21. data/lib/active_job/queue_adapters/backburner_adapter.rb +2 -2
  22. data/lib/active_job/queue_adapters/inline_adapter.rb +1 -1
  23. data/lib/active_job/queue_adapters/que_adapter.rb +2 -2
  24. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +2 -2
  25. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +1 -1
  26. data/lib/active_job/queue_adapters/test_adapter.rb +32 -14
  27. data/lib/active_job/queue_name.rb +23 -3
  28. data/lib/active_job/railtie.rb +20 -1
  29. data/lib/active_job/serializers.rb +66 -0
  30. data/lib/active_job/serializers/date_serializer.rb +20 -0
  31. data/lib/active_job/serializers/date_time_serializer.rb +16 -0
  32. data/lib/active_job/serializers/duration_serializer.rb +23 -0
  33. data/lib/active_job/serializers/module_serializer.rb +20 -0
  34. data/lib/active_job/serializers/object_serializer.rb +53 -0
  35. data/lib/active_job/serializers/symbol_serializer.rb +20 -0
  36. data/lib/active_job/serializers/time_object_serializer.rb +13 -0
  37. data/lib/active_job/serializers/time_serializer.rb +16 -0
  38. data/lib/active_job/serializers/time_with_zone_serializer.rb +16 -0
  39. data/lib/active_job/test_helper.rb +316 -68
  40. data/lib/active_job/timezones.rb +13 -0
  41. data/lib/active_job/translation.rb +1 -1
  42. data/lib/rails/generators/job/job_generator.rb +4 -0
  43. metadata +29 -14
  44. data/lib/active_job/queue_adapters/qu_adapter.rb +0 -46
@@ -7,10 +7,10 @@ module ActiveJob
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 5
11
- MINOR = 2
12
- TINY = 4
13
- PRE = "4"
10
+ MAJOR = 6
11
+ MINOR = 1
12
+ TINY = 1
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Instrumentation #:nodoc:
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ around_enqueue do |_, block|
9
+ scheduled_at ? instrument(:enqueue_at, &block) : instrument(:enqueue, &block)
10
+ end
11
+
12
+ around_perform do |_, block|
13
+ instrument :perform_start
14
+ instrument :perform, &block
15
+ end
16
+ end
17
+
18
+ private
19
+ def instrument(operation, payload = {}, &block)
20
+ enhanced_block = ->(event_payload) do
21
+ value = block.call if block
22
+
23
+ if defined?(@_halted_callback_hook_called) && @_halted_callback_hook_called
24
+ event_payload[:aborted] = true
25
+ @_halted_callback_hook_called = nil
26
+ end
27
+
28
+ value
29
+ end
30
+
31
+ ActiveSupport::Notifications.instrument \
32
+ "#{operation}.active_job", payload.merge(adapter: queue_adapter, job: self), &enhanced_block
33
+ end
34
+
35
+ def halted_callback_hook(*)
36
+ super
37
+ @_halted_callback_hook_called = true
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/filters"
4
+ require "active_support/log_subscriber"
5
+
6
+ module ActiveJob
7
+ class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
8
+ def enqueue(event)
9
+ job = event.payload[:job]
10
+ ex = event.payload[:exception_object]
11
+
12
+ if ex
13
+ error do
14
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
15
+ end
16
+ elsif event.payload[:aborted]
17
+ info do
18
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
19
+ end
20
+ else
21
+ info do
22
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
23
+ end
24
+ end
25
+ end
26
+
27
+ def enqueue_at(event)
28
+ job = event.payload[:job]
29
+ ex = event.payload[:exception_object]
30
+
31
+ if ex
32
+ error do
33
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
34
+ end
35
+ elsif event.payload[:aborted]
36
+ info do
37
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
38
+ end
39
+ else
40
+ info do
41
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
42
+ end
43
+ end
44
+ end
45
+
46
+ def perform_start(event)
47
+ info do
48
+ 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)
50
+ end
51
+ end
52
+
53
+ def perform(event)
54
+ job = event.payload[:job]
55
+ ex = event.payload[:exception_object]
56
+ if ex
57
+ error do
58
+ "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
59
+ end
60
+ elsif event.payload[:aborted]
61
+ error do
62
+ "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: a before_perform callback halted the job execution"
63
+ end
64
+ else
65
+ info do
66
+ "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
67
+ end
68
+ end
69
+ end
70
+
71
+ def enqueue_retry(event)
72
+ job = event.payload[:job]
73
+ ex = event.payload[:error]
74
+ wait = event.payload[:wait]
75
+
76
+ info do
77
+ if ex
78
+ "Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
79
+ else
80
+ "Retrying #{job.class} in #{wait.to_i} seconds."
81
+ end
82
+ end
83
+ end
84
+
85
+ def retry_stopped(event)
86
+ job = event.payload[:job]
87
+ ex = event.payload[:error]
88
+
89
+ error do
90
+ "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
91
+ end
92
+ end
93
+
94
+ def discard(event)
95
+ job = event.payload[:job]
96
+ ex = event.payload[:error]
97
+
98
+ error do
99
+ "Discarded #{job.class} due to a #{ex.class}."
100
+ end
101
+ end
102
+
103
+ private
104
+ def queue_name(event)
105
+ event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
106
+ end
107
+
108
+ def args_info(job)
109
+ if job.class.log_arguments? && job.arguments.any?
110
+ " with arguments: " +
111
+ job.arguments.map { |arg| format(arg).inspect }.join(", ")
112
+ else
113
+ ""
114
+ end
115
+ end
116
+
117
+ def format(arg)
118
+ case arg
119
+ when Hash
120
+ arg.transform_values { |value| format(value) }
121
+ when Array
122
+ arg.map { |value| format(value) }
123
+ when GlobalID::Identification
124
+ arg.to_global_id rescue arg
125
+ else
126
+ arg
127
+ end
128
+ end
129
+
130
+ def scheduled_at(event)
131
+ Time.at(event.payload[:job].scheduled_at).utc
132
+ end
133
+
134
+ def logger
135
+ ActiveJob::Base.logger
136
+ end
137
+ end
138
+ end
139
+
140
+ ActiveJob::LogSubscriber.attach_to :active_job
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/transform_values"
4
- require "active_support/core_ext/string/filters"
5
3
  require "active_support/tagged_logging"
6
4
  require "active_support/logger"
7
5
 
@@ -11,32 +9,10 @@ module ActiveJob
11
9
 
12
10
  included do
13
11
  cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
12
+ class_attribute :log_arguments, instance_accessor: false, default: true
14
13
 
15
- around_enqueue do |_, block, _|
16
- tag_logger do
17
- block.call
18
- end
19
- end
20
-
21
- around_perform do |job, block, _|
22
- tag_logger(job.class.name, job.job_id) do
23
- payload = { adapter: job.class.queue_adapter, job: job }
24
- ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
25
- ActiveSupport::Notifications.instrument("perform.active_job", payload) do
26
- block.call
27
- end
28
- end
29
- end
30
-
31
- after_enqueue do |job|
32
- if job.scheduled_at
33
- ActiveSupport::Notifications.instrument "enqueue_at.active_job",
34
- adapter: job.class.queue_adapter, job: job
35
- else
36
- ActiveSupport::Notifications.instrument "enqueue.active_job",
37
- adapter: job.class.queue_adapter, job: job
38
- end
39
- end
14
+ around_enqueue { |_, block| tag_logger(&block) }
15
+ around_perform { |job, block| tag_logger(job.class.name, job.job_id, &block) }
40
16
  end
41
17
 
42
18
  private
@@ -52,79 +28,5 @@ module ActiveJob
52
28
  def logger_tagged_by_active_job?
53
29
  logger.formatter.current_tags.include?("ActiveJob")
54
30
  end
55
-
56
- class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
57
- def enqueue(event)
58
- info do
59
- job = event.payload[:job]
60
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
61
- end
62
- end
63
-
64
- def enqueue_at(event)
65
- info do
66
- job = event.payload[:job]
67
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
68
- end
69
- end
70
-
71
- def perform_start(event)
72
- info do
73
- job = event.payload[:job]
74
- "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + args_info(job)
75
- end
76
- end
77
-
78
- def perform(event)
79
- job = event.payload[:job]
80
- ex = event.payload[:exception_object]
81
- if ex
82
- error do
83
- "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
84
- end
85
- else
86
- info do
87
- "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
88
- end
89
- end
90
- end
91
-
92
- private
93
- def queue_name(event)
94
- event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
95
- end
96
-
97
- def args_info(job)
98
- if job.arguments.any?
99
- " with arguments: " +
100
- job.arguments.map { |arg| format(arg).inspect }.join(", ")
101
- else
102
- ""
103
- end
104
- end
105
-
106
- def format(arg)
107
- case arg
108
- when Hash
109
- arg.transform_values { |value| format(value) }
110
- when Array
111
- arg.map { |value| format(value) }
112
- when GlobalID::Identification
113
- arg.to_global_id rescue arg
114
- else
115
- arg
116
- end
117
- end
118
-
119
- def scheduled_at(event)
120
- Time.at(event.payload[:job].scheduled_at).utc
121
- end
122
-
123
- def logger
124
- ActiveJob::Base.logger
125
- end
126
- end
127
31
  end
128
32
  end
129
-
130
- ActiveJob::Logging::LogSubscriber.attach_to :active_job
@@ -11,6 +11,9 @@ module ActiveJob
11
11
  included do
12
12
  class_attribute :_queue_adapter_name, instance_accessor: false, instance_predicate: false
13
13
  class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
14
+
15
+ delegate :queue_adapter, to: :class
16
+
14
17
  self.queue_adapter = :async
15
18
  end
16
19
 
@@ -22,6 +25,8 @@ module ActiveJob
22
25
  _queue_adapter
23
26
  end
24
27
 
28
+ # Returns string denoting the name of the configured queue adapter.
29
+ # By default returns +"async"+.
25
30
  def queue_adapter_name
26
31
  _queue_adapter_name
27
32
  end
@@ -3,19 +3,19 @@
3
3
  module ActiveJob
4
4
  # == Active Job adapters
5
5
  #
6
- # Active Job has adapters for the following queueing backends:
6
+ # Active Job has adapters for the following queuing backends:
7
7
  #
8
8
  # * {Backburner}[https://github.com/nesquena/backburner]
9
9
  # * {Delayed Job}[https://github.com/collectiveidea/delayed_job]
10
- # * {Qu}[https://github.com/bkeepers/qu]
11
10
  # * {Que}[https://github.com/chanks/que]
12
11
  # * {queue_classic}[https://github.com/QueueClassic/queue_classic]
13
12
  # * {Resque}[https://github.com/resque/resque]
14
- # * {Sidekiq}[http://sidekiq.org]
13
+ # * {Sidekiq}[https://sidekiq.org]
15
14
  # * {Sneakers}[https://github.com/jondot/sneakers]
16
15
  # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
17
- # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html]
18
- # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html]
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
+ # * Please Note: We are not accepting pull requests for new adapters. See the {README}[link:files/activejob/README_md.html] for more details.
19
19
  #
20
20
  # === Backends Features
21
21
  #
@@ -23,7 +23,6 @@ module ActiveJob
23
23
  # |-------------------|-------|--------|------------|------------|---------|---------|
24
24
  # | Backburner | Yes | Yes | Yes | Yes | Job | Global |
25
25
  # | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
26
- # | Qu | Yes | Yes | No | No | No | Global |
27
26
  # | Que | Yes | Yes | Yes | Job | No | Job |
28
27
  # | queue_classic | Yes | Yes | Yes* | No | No | No |
29
28
  # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
@@ -53,7 +52,7 @@ module ActiveJob
53
52
  #
54
53
  # No: The adapter will run jobs at the next opportunity and cannot use perform_later.
55
54
  #
56
- # N/A: The adapter does not support queueing.
55
+ # N/A: The adapter does not support queuing.
57
56
  #
58
57
  # NOTE:
59
58
  # queue_classic supports job scheduling since version 3.1.
@@ -73,9 +72,9 @@ module ActiveJob
73
72
  # Yes: Allows the priority to be set on the job object, at the queue level or
74
73
  # as default configuration option.
75
74
  #
76
- # No: Does not allow the priority of jobs to be configured.
75
+ # No: The adapter does not allow the priority of jobs to be configured.
77
76
  #
78
- # N/A: The adapter does not support queueing, and therefore sorting them.
77
+ # N/A: The adapter does not support queuing, and therefore sorting them.
79
78
  #
80
79
  # ==== Timeout
81
80
  #
@@ -87,6 +86,8 @@ module ActiveJob
87
86
  #
88
87
  # Global: The adapter is configured that all jobs have a maximum run time.
89
88
  #
89
+ # No: The adapter does not allow the timeout of jobs to be configured.
90
+ #
90
91
  # N/A: This adapter does not run in a separate process, and therefore timeout
91
92
  # is unsupported.
92
93
  #
@@ -100,6 +101,8 @@ module ActiveJob
100
101
  #
101
102
  # Global: The adapter has a global number of retries.
102
103
  #
104
+ # No: The adapter does not allow the number of retries to be configured.
105
+ #
103
106
  # N/A: The adapter does not run in a separate process, and therefore doesn't
104
107
  # support retries.
105
108
  #
@@ -114,7 +117,6 @@ module ActiveJob
114
117
  autoload :InlineAdapter
115
118
  autoload :BackburnerAdapter
116
119
  autoload :DelayedJobAdapter
117
- autoload :QuAdapter
118
120
  autoload :QueAdapter
119
121
  autoload :QueueClassicAdapter
120
122
  autoload :ResqueAdapter
@@ -123,7 +125,7 @@ module ActiveJob
123
125
  autoload :SuckerPunchAdapter
124
126
  autoload :TestAdapter
125
127
 
126
- ADAPTER = "Adapter".freeze
128
+ ADAPTER = "Adapter"
127
129
  private_constant :ADAPTER
128
130
 
129
131
  class << self
@@ -31,7 +31,7 @@ module ActiveJob
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
33
  class AsyncAdapter
34
- # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
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)
37
37
  end
@@ -16,12 +16,12 @@ 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
19
+ Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority)
20
20
  end
21
21
 
22
22
  def enqueue_at(job, timestamp) #:nodoc:
23
23
  delay = timestamp - Time.current.to_f
24
- Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay
24
+ Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, delay: delay)
25
25
  end
26
26
 
27
27
  class JobWrapper #:nodoc: