activejob 6.0.6.1 → 6.1.0.rc1

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.
@@ -29,13 +29,21 @@ module ActiveJob
29
29
 
30
30
  # Performs the job immediately. The job is not sent to the queuing adapter
31
31
  # but directly executed by blocking the execution of others until it's finished.
32
+ # `perform_now` returns the value of your job's `perform` method.
32
33
  #
33
- # MyJob.new(*args).perform_now
34
+ # class MyJob < ActiveJob::Base
35
+ # def perform
36
+ # "Hello World!"
37
+ # end
38
+ # end
39
+ #
40
+ # puts MyJob.new(*args).perform_now # => "Hello World!"
34
41
  def perform_now
35
42
  # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
36
43
  self.executions = (executions || 0) + 1
37
44
 
38
45
  deserialize_arguments_if_needed
46
+
39
47
  run_callbacks :perform do
40
48
  perform(*arguments)
41
49
  end
@@ -8,9 +8,9 @@ module ActiveJob
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 6
11
- MINOR = 0
12
- TINY = 6
13
- PRE = "1"
11
+ MINOR = 1
12
+ TINY = 0
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -0,0 +1,37 @@
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
+ block.call if block
22
+ if defined?(@_halted_callback_hook_called) && @_halted_callback_hook_called
23
+ event_payload[:aborted] = true
24
+ @_halted_callback_hook_called = nil
25
+ end
26
+ end
27
+
28
+ ActiveSupport::Notifications.instrument \
29
+ "#{operation}.active_job", payload.merge(adapter: queue_adapter, job: self), &enhanced_block
30
+ end
31
+
32
+ def halted_callback_hook(*)
33
+ super
34
+ @_halted_callback_hook_called = true
35
+ end
36
+ end
37
+ 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,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/string/filters"
4
3
  require "active_support/tagged_logging"
5
4
  require "active_support/logger"
6
5
 
@@ -10,32 +9,10 @@ module ActiveJob
10
9
 
11
10
  included do
12
11
  cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
12
+ class_attribute :log_arguments, instance_accessor: false, default: true
13
13
 
14
- around_enqueue do |_, block|
15
- tag_logger do
16
- block.call
17
- end
18
- end
19
-
20
- around_perform do |job, block|
21
- tag_logger(job.class.name, job.job_id) do
22
- payload = { adapter: job.class.queue_adapter, job: job }
23
- ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
24
- ActiveSupport::Notifications.instrument("perform.active_job", payload) do
25
- block.call
26
- end
27
- end
28
- end
29
-
30
- around_enqueue do |job, block|
31
- if job.scheduled_at
32
- ActiveSupport::Notifications.instrument("enqueue_at.active_job",
33
- adapter: job.class.queue_adapter, job: job, &block)
34
- else
35
- ActiveSupport::Notifications.instrument("enqueue.active_job",
36
- adapter: job.class.queue_adapter, job: job, &block)
37
- end
38
- end
14
+ around_enqueue { |_, block| tag_logger(&block) }
15
+ around_perform { |job, block| tag_logger(job.class.name, job.job_id, &block) }
39
16
  end
40
17
 
41
18
  private
@@ -51,111 +28,5 @@ module ActiveJob
51
28
  def logger_tagged_by_active_job?
52
29
  logger.formatter.current_tags.include?("ActiveJob")
53
30
  end
54
-
55
- class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
56
- def enqueue(event)
57
- info do
58
- job = event.payload[:job]
59
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
60
- end
61
- end
62
-
63
- def enqueue_at(event)
64
- info do
65
- job = event.payload[:job]
66
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
67
- end
68
- end
69
-
70
- def perform_start(event)
71
- info do
72
- job = event.payload[:job]
73
- "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} enqueued at #{job.enqueued_at}" + args_info(job)
74
- end
75
- end
76
-
77
- def perform(event)
78
- job = event.payload[:job]
79
- ex = event.payload[:exception_object]
80
- if ex
81
- error do
82
- "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")
83
- end
84
- else
85
- info do
86
- "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
87
- end
88
- end
89
- end
90
-
91
- def enqueue_retry(event)
92
- job = event.payload[:job]
93
- ex = event.payload[:error]
94
- wait = event.payload[:wait]
95
-
96
- info do
97
- if ex
98
- "Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
99
- else
100
- "Retrying #{job.class} in #{wait.to_i} seconds."
101
- end
102
- end
103
- end
104
-
105
- def retry_stopped(event)
106
- job = event.payload[:job]
107
- ex = event.payload[:error]
108
-
109
- error do
110
- "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
111
- end
112
- end
113
-
114
- def discard(event)
115
- job = event.payload[:job]
116
- ex = event.payload[:error]
117
-
118
- error do
119
- "Discarded #{job.class} due to a #{ex.class}."
120
- end
121
- end
122
-
123
- private
124
- def queue_name(event)
125
- event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
126
- end
127
-
128
- def args_info(job)
129
- if job.arguments.any?
130
- " with arguments: " +
131
- job.arguments.map { |arg| format(arg).inspect }.join(", ")
132
- else
133
- ""
134
- end
135
- end
136
-
137
- def format(arg)
138
- case arg
139
- when Hash
140
- arg.transform_values { |value| format(value) }
141
- when Array
142
- arg.map { |value| format(value) }
143
- when GlobalID::Identification
144
- arg.to_global_id rescue arg
145
- else
146
- arg
147
- end
148
- end
149
-
150
- def scheduled_at(event)
151
- Time.at(event.payload[:job].scheduled_at).utc
152
- end
153
-
154
- def logger
155
- ActiveJob::Base.logger
156
- end
157
- end
158
31
  end
159
32
  end
160
-
161
- 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
 
@@ -12,7 +12,7 @@ module ActiveJob
12
12
  # Rails.application.config.active_job.queue_adapter = :inline
13
13
  class InlineAdapter
14
14
  def enqueue(job) #:nodoc:
15
- Base.execute(job.serialize)
15
+ Thread.new { Base.execute(job.serialize) }.join
16
16
  end
17
17
 
18
18
  def enqueue_at(*) #:nodoc:
@@ -10,7 +10,7 @@ module ActiveJob
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 (eg. 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
  #
@@ -12,7 +12,7 @@ module ActiveJob
12
12
  #
13
13
  # Rails.application.config.active_job.queue_adapter = :test
14
14
  class TestAdapter
15
- attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue)
15
+ attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue, :at)
16
16
  attr_writer(:enqueued_jobs, :performed_jobs)
17
17
 
18
18
  # Provides a store of all the enqueued jobs with the TestAdapter so you can check them.
@@ -54,7 +54,11 @@ module ActiveJob
54
54
  end
55
55
 
56
56
  def filtered?(job)
57
- filtered_queue?(job) || filtered_job_class?(job)
57
+ filtered_queue?(job) || filtered_job_class?(job) || filtered_time?(job)
58
+ end
59
+
60
+ def filtered_time?(job)
61
+ job.scheduled_at > at.to_f if at && job.scheduled_at
58
62
  end
59
63
 
60
64
  def filtered_queue?(job)
@@ -72,7 +72,7 @@ module ActiveJob
72
72
  # Yes: Allows the priority to be set on the job object, at the queue level or
73
73
  # as default configuration option.
74
74
  #
75
- # 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.
76
76
  #
77
77
  # N/A: The adapter does not support queuing, and therefore sorting them.
78
78
  #
@@ -86,6 +86,8 @@ module ActiveJob
86
86
  #
87
87
  # Global: The adapter is configured that all jobs have a maximum run time.
88
88
  #
89
+ # No: The adapter does not allow the timeout of jobs to be configured.
90
+ #
89
91
  # N/A: This adapter does not run in a separate process, and therefore timeout
90
92
  # is unsupported.
91
93
  #
@@ -99,6 +101,8 @@ module ActiveJob
99
101
  #
100
102
  # Global: The adapter has a global number of retries.
101
103
  #
104
+ # No: The adapter does not allow the number of retries to be configured.
105
+ #
102
106
  # N/A: The adapter does not run in a separate process, and therefore doesn't
103
107
  # support retries.
104
108
  #
@@ -6,7 +6,6 @@ module ActiveJob
6
6
 
7
7
  # Includes the ability to override the default queue name and prefix.
8
8
  module ClassMethods
9
- mattr_accessor :queue_name_prefix
10
9
  mattr_accessor :default_queue_name, default: "default"
11
10
 
12
11
  # Specifies the name of the queue to process the job on.
@@ -49,13 +48,14 @@ module ActiveJob
49
48
  def queue_name_from_part(part_name) #:nodoc:
50
49
  queue_name = part_name || default_queue_name
51
50
  name_parts = [queue_name_prefix.presence, queue_name]
52
- name_parts.compact.join(queue_name_delimiter)
51
+ -name_parts.compact.join(queue_name_delimiter)
53
52
  end
54
53
  end
55
54
 
56
55
  included do
57
56
  class_attribute :queue_name, instance_accessor: false, default: -> { self.class.default_queue_name }
58
57
  class_attribute :queue_name_delimiter, instance_accessor: false, default: "_"
58
+ class_attribute :queue_name_prefix
59
59
  end
60
60
 
61
61
  # Returns the name of the queue the job will be run on.
@@ -34,6 +34,10 @@ module ActiveJob
34
34
  ActiveSupport.on_load(:action_dispatch_integration_test) do
35
35
  include ActiveJob::TestHelper
36
36
  end
37
+
38
+ ActiveSupport.on_load(:active_record) do
39
+ self.destroy_association_async_job = ActiveRecord::DestroyAssociationAsyncJob
40
+ end
37
41
  end
38
42
 
39
43
  initializer "active_job.set_reloader_hook" do |app|
@@ -2,11 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module Serializers
5
- class DateTimeSerializer < ObjectSerializer # :nodoc:
6
- def serialize(time)
7
- super("value" => time.iso8601)
8
- end
9
-
5
+ class DateTimeSerializer < TimeObjectSerializer # :nodoc:
10
6
  def deserialize(hash)
11
7
  DateTime.iso8601(hash["value"])
12
8
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class ModuleSerializer < ObjectSerializer # :nodoc:
6
+ def serialize(constant)
7
+ super("value" => constant.name)
8
+ end
9
+
10
+ def deserialize(hash)
11
+ hash["value"].constantize
12
+ end
13
+
14
+ private
15
+ def klass
16
+ Module
17
+ end
18
+ end
19
+ end
20
+ end
@@ -39,7 +39,7 @@ module ActiveJob
39
39
  end
40
40
 
41
41
  # Deserializes an argument from a JSON primitive type.
42
- def deserialize(_argument)
42
+ def deserialize(json)
43
43
  raise NotImplementedError
44
44
  end
45
45
 
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class TimeObjectSerializer < ObjectSerializer # :nodoc:
6
+ NANO_PRECISION = 9
7
+
8
+ def serialize(time)
9
+ super("value" => time.iso8601(NANO_PRECISION))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,11 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module Serializers
5
- class TimeSerializer < ObjectSerializer # :nodoc:
6
- def serialize(time)
7
- super("value" => time.iso8601)
8
- end
9
-
5
+ class TimeSerializer < TimeObjectSerializer # :nodoc:
10
6
  def deserialize(hash)
11
7
  Time.iso8601(hash["value"])
12
8
  end
@@ -2,11 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module Serializers
5
- class TimeWithZoneSerializer < ObjectSerializer # :nodoc:
6
- def serialize(time)
7
- super("value" => time.iso8601)
8
- end
9
-
5
+ class TimeWithZoneSerializer < TimeObjectSerializer # :nodoc:
10
6
  def deserialize(hash)
11
7
  Time.iso8601(hash["value"]).in_time_zone
12
8
  end
@@ -9,12 +9,14 @@ module ActiveJob
9
9
  extend ActiveSupport::Autoload
10
10
 
11
11
  autoload :ObjectSerializer
12
+ autoload :TimeObjectSerializer
12
13
  autoload :SymbolSerializer
13
14
  autoload :DurationSerializer
14
15
  autoload :DateTimeSerializer
15
16
  autoload :DateSerializer
16
17
  autoload :TimeWithZoneSerializer
17
18
  autoload :TimeSerializer
19
+ autoload :ModuleSerializer
18
20
 
19
21
  mattr_accessor :_additional_serializers
20
22
  self._additional_serializers = Set.new
@@ -58,6 +60,7 @@ module ActiveJob
58
60
  DateTimeSerializer,
59
61
  DateSerializer,
60
62
  TimeWithZoneSerializer,
61
- TimeSerializer
63
+ TimeSerializer,
64
+ ModuleSerializer
62
65
  end
63
66
  end