activejob 7.2.3 → 8.1.3

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +93 -62
  3. data/README.md +7 -5
  4. data/lib/active_job/arguments.rb +51 -48
  5. data/lib/active_job/base.rb +3 -4
  6. data/lib/active_job/configured_job.rb +6 -1
  7. data/lib/active_job/continuable.rb +102 -0
  8. data/lib/active_job/continuation/step.rb +83 -0
  9. data/lib/active_job/continuation/test_helper.rb +89 -0
  10. data/lib/active_job/continuation/validation.rb +50 -0
  11. data/lib/active_job/continuation.rb +332 -0
  12. data/lib/active_job/core.rb +21 -3
  13. data/lib/active_job/enqueue_after_transaction_commit.rb +20 -10
  14. data/lib/active_job/enqueuing.rb +11 -8
  15. data/lib/active_job/exceptions.rb +17 -8
  16. data/lib/active_job/execution_state.rb +11 -0
  17. data/lib/active_job/gem_version.rb +2 -2
  18. data/lib/active_job/instrumentation.rb +12 -12
  19. data/lib/active_job/log_subscriber.rb +63 -4
  20. data/lib/active_job/queue_adapter.rb +1 -0
  21. data/lib/active_job/queue_adapters/abstract_adapter.rb +5 -7
  22. data/lib/active_job/queue_adapters/async_adapter.rb +6 -2
  23. data/lib/active_job/queue_adapters/delayed_job_adapter.rb +0 -8
  24. data/lib/active_job/queue_adapters/inline_adapter.rb +0 -4
  25. data/lib/active_job/queue_adapters/queue_classic_adapter.rb +0 -8
  26. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +19 -0
  27. data/lib/active_job/queue_adapters/test_adapter.rb +5 -9
  28. data/lib/active_job/queue_adapters.rb +0 -4
  29. data/lib/active_job/railtie.rb +15 -6
  30. data/lib/active_job/serializers/action_controller_parameters_serializer.rb +25 -0
  31. data/lib/active_job/serializers/big_decimal_serializer.rb +3 -4
  32. data/lib/active_job/serializers/date_serializer.rb +3 -4
  33. data/lib/active_job/serializers/date_time_serializer.rb +3 -4
  34. data/lib/active_job/serializers/duration_serializer.rb +5 -6
  35. data/lib/active_job/serializers/module_serializer.rb +3 -4
  36. data/lib/active_job/serializers/object_serializer.rb +11 -14
  37. data/lib/active_job/serializers/range_serializer.rb +9 -9
  38. data/lib/active_job/serializers/symbol_serializer.rb +4 -5
  39. data/lib/active_job/serializers/time_serializer.rb +3 -4
  40. data/lib/active_job/serializers/time_with_zone_serializer.rb +3 -4
  41. data/lib/active_job/serializers.rb +62 -18
  42. data/lib/active_job/structured_event_subscriber.rb +220 -0
  43. data/lib/active_job.rb +3 -12
  44. metadata +16 -11
  45. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +0 -49
  46. data/lib/active_job/timezones.rb +0 -13
  47. data/lib/active_job/translation.rb +0 -13
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module ActiveJob
6
4
  # = Active Job \Serializers
7
5
  #
@@ -21,16 +19,17 @@ module ActiveJob
21
19
  autoload :ModuleSerializer
22
20
  autoload :RangeSerializer
23
21
  autoload :BigDecimalSerializer
22
+ autoload :ActionControllerParametersSerializer
24
23
 
25
- mattr_accessor :_additional_serializers
26
- self._additional_serializers = Set.new
24
+ @serializers = Set.new
25
+ @serializers_index = {}
27
26
 
28
27
  class << self
29
28
  # Returns serialized representative of the passed object.
30
29
  # Will look up through all known serializers.
31
30
  # Raises ActiveJob::SerializationError if it can't find a proper serializer.
32
31
  def serialize(argument)
33
- serializer = serializers.detect { |s| s.serialize?(argument) }
32
+ serializer = @serializers_index[argument.class] || serializers.find { |s| s.serialize?(argument) }
34
33
  raise SerializationError.new("Unsupported argument type: #{argument.class.name}") unless serializer
35
34
  serializer.serialize(argument)
36
35
  end
@@ -39,7 +38,7 @@ module ActiveJob
39
38
  # Will look up through all known serializers.
40
39
  # If no serializer found will raise <tt>ArgumentError</tt>.
41
40
  def deserialize(argument)
42
- serializer_name = argument[Arguments::OBJECT_SERIALIZER_KEY]
41
+ serializer_name = argument[OBJECT_SERIALIZER_KEY]
43
42
  raise ArgumentError, "Serializer name is not present in the argument: #{argument.inspect}" unless serializer_name
44
43
 
45
44
  serializer = serializer_name.safe_constantize
@@ -49,24 +48,69 @@ module ActiveJob
49
48
  end
50
49
 
51
50
  # Returns list of known serializers.
52
- def serializers
53
- self._additional_serializers
51
+ attr_reader :serializers
52
+
53
+ def serializers=(serializers)
54
+ @serializers_index.clear
55
+ @serializers = Set.new
56
+ add_new_serializers(serializers)
54
57
  end
55
58
 
56
59
  # Adds new serializers to a list of known serializers.
57
60
  def add_serializers(*new_serializers)
58
- self._additional_serializers += new_serializers.flatten
61
+ new_serializers = new_serializers.flatten
62
+ add_new_serializers(new_serializers)
59
63
  end
64
+
65
+ private
66
+ def add_new_serializers(new_serializers)
67
+ new_serializers.map! do |s|
68
+ if s.is_a?(Class) && s < ObjectSerializer
69
+ s.instance
70
+ else
71
+ s
72
+ end
73
+ end
74
+
75
+ @serializers += new_serializers
76
+ index_serializers(new_serializers)
77
+ @serializers
78
+ end
79
+
80
+ def index_serializers(new_serializers)
81
+ new_serializers.each do |s|
82
+ if s.respond_to?(:klass)
83
+ @serializers_index[s.klass] = s
84
+ elsif s.respond_to?(:klass, true)
85
+ klass = s.send(:klass)
86
+ ActiveJob.deprecator.warn(<<~MSG.squish)
87
+ #{s.class.name}#klass method should be public.
88
+ This will raise an error in Rails 8.2.
89
+ MSG
90
+ @serializers_index[klass] = s
91
+ else
92
+ ActiveJob.deprecator.warn(
93
+ <<~MSG.squish
94
+ #{s.class.name} should implement a public #klass method.
95
+ This will raise an error in Rails 8.2.
96
+ MSG
97
+ )
98
+ end
99
+ end
100
+ end
60
101
  end
61
102
 
62
- add_serializers SymbolSerializer,
63
- DurationSerializer,
64
- DateTimeSerializer,
65
- DateSerializer,
66
- TimeWithZoneSerializer,
67
- TimeSerializer,
68
- ModuleSerializer,
69
- RangeSerializer,
70
- BigDecimalSerializer
103
+ # :nodoc:
104
+ OBJECT_SERIALIZER_KEY = "_aj_serialized"
105
+
106
+ add_serializers SymbolSerializer.instance,
107
+ DurationSerializer.instance,
108
+ DateTimeSerializer.instance,
109
+ DateSerializer.instance,
110
+ TimeWithZoneSerializer.instance,
111
+ TimeSerializer.instance,
112
+ ModuleSerializer.instance,
113
+ RangeSerializer.instance,
114
+ BigDecimalSerializer.instance
71
115
  end
72
116
  end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/structured_event_subscriber"
4
+
5
+ module ActiveJob
6
+ class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
7
+ def enqueue(event)
8
+ job = event.payload[:job]
9
+ adapter = event.payload[:adapter]
10
+ exception = event.payload[:exception_object] || job.enqueue_error
11
+ payload = {
12
+ job_class: job.class.name,
13
+ job_id: job.job_id,
14
+ queue: job.queue_name,
15
+ adapter: ActiveJob.adapter_name(adapter),
16
+ aborted: event.payload[:aborted],
17
+ }
18
+
19
+ if exception
20
+ payload[:exception_class] = exception.class.name
21
+ payload[:exception_message] = exception.message
22
+ end
23
+
24
+ if job.class.log_arguments?
25
+ payload[:arguments] = job.arguments
26
+ end
27
+
28
+ emit_event("active_job.enqueued", payload)
29
+ end
30
+
31
+ def enqueue_at(event)
32
+ job = event.payload[:job]
33
+ adapter = event.payload[:adapter]
34
+ exception = event.payload[:exception_object] || job.enqueue_error
35
+ payload = {
36
+ job_class: job.class.name,
37
+ job_id: job.job_id,
38
+ queue: job.queue_name,
39
+ scheduled_at: job.scheduled_at,
40
+ adapter: ActiveJob.adapter_name(adapter),
41
+ aborted: event.payload[:aborted],
42
+ }
43
+
44
+ if exception
45
+ payload[:exception_class] = exception.class.name
46
+ payload[:exception_message] = exception.message
47
+ end
48
+
49
+ if job.class.log_arguments?
50
+ payload[:arguments] = job.arguments
51
+ end
52
+
53
+ emit_event("active_job.enqueued_at", payload)
54
+ end
55
+
56
+ def enqueue_all(event)
57
+ jobs = event.payload[:jobs]
58
+ adapter = event.payload[:adapter]
59
+ enqueued_count = event.payload[:enqueued_count].to_i
60
+ failed_count = jobs.size - enqueued_count
61
+
62
+ emit_event("active_job.bulk_enqueued",
63
+ adapter: ActiveJob.adapter_name(adapter),
64
+ job_count: jobs.size,
65
+ enqueued_count: enqueued_count,
66
+ failed_enqueue_count: failed_count,
67
+ enqueued_classes: jobs.filter_map do |job|
68
+ job.class.name if jobs.count == enqueued_count || job.successfully_enqueued?
69
+ end.tally
70
+ )
71
+ end
72
+
73
+ def perform_start(event)
74
+ job = event.payload[:job]
75
+ payload = {
76
+ job_class: job.class.name,
77
+ job_id: job.job_id,
78
+ queue: job.queue_name,
79
+ enqueued_at: job.enqueued_at&.utc&.iso8601(9),
80
+ }
81
+ if job.class.log_arguments?
82
+ payload[:arguments] = job.arguments
83
+ end
84
+ emit_event("active_job.started", payload)
85
+ end
86
+
87
+ def perform(event)
88
+ job = event.payload[:job]
89
+ exception = event.payload[:exception_object]
90
+ adapter = event.payload[:adapter]
91
+ payload = {
92
+ job_class: job.class.name,
93
+ job_id: job.job_id,
94
+ queue: job.queue_name,
95
+ adapter: ActiveJob.adapter_name(adapter),
96
+ aborted: event.payload[:aborted],
97
+ duration: event.duration.round(2),
98
+ }
99
+
100
+ if exception
101
+ payload[:exception_class] = exception.class.name
102
+ payload[:exception_message] = exception.message
103
+ payload[:exception_backtrace] = exception.backtrace
104
+ end
105
+
106
+ emit_event("active_job.completed", payload)
107
+ end
108
+
109
+ def enqueue_retry(event)
110
+ job = event.payload[:job]
111
+ exception = event.payload[:error]
112
+ wait = event.payload[:wait]
113
+
114
+ emit_event("active_job.retry_scheduled",
115
+ job_class: job.class.name,
116
+ job_id: job.job_id,
117
+ executions: job.executions,
118
+ wait_seconds: wait.to_i,
119
+ exception_class: exception&.class&.name,
120
+ exception_message: exception&.message
121
+ )
122
+ end
123
+
124
+ def retry_stopped(event)
125
+ job = event.payload[:job]
126
+ exception = event.payload[:error]
127
+
128
+ emit_event("active_job.retry_stopped",
129
+ job_class: job.class.name,
130
+ job_id: job.job_id,
131
+ executions: job.executions,
132
+ exception_class: exception.class.name,
133
+ exception_message: exception.message
134
+ )
135
+ end
136
+
137
+ def discard(event)
138
+ job = event.payload[:job]
139
+ exception = event.payload[:error]
140
+
141
+ emit_event("active_job.discarded",
142
+ job_class: job.class.name,
143
+ job_id: job.job_id,
144
+ exception_class: exception.class.name,
145
+ exception_message: exception.message
146
+ )
147
+ end
148
+
149
+ def interrupt(event)
150
+ job = event.payload[:job]
151
+ description = event.payload[:description]
152
+ reason = event.payload[:reason]
153
+
154
+ emit_event("active_job.interrupt",
155
+ job_class: job.class.name,
156
+ job_id: job.job_id,
157
+ description: description,
158
+ reason: reason,
159
+ )
160
+ end
161
+
162
+ def resume(event)
163
+ job = event.payload[:job]
164
+ description = event.payload[:description]
165
+
166
+ emit_event("active_job.resume",
167
+ job_class: job.class.name,
168
+ job_id: job.job_id,
169
+ description: description,
170
+ )
171
+ end
172
+
173
+ def step_skipped(event)
174
+ job = event.payload[:job]
175
+ step = event.payload[:step]
176
+
177
+ emit_event("active_job.step_skipped",
178
+ job_class: job.class.name,
179
+ job_id: job.job_id,
180
+ step: step.name,
181
+ )
182
+ end
183
+
184
+ def step_started(event)
185
+ job = event.payload[:job]
186
+ step = event.payload[:step]
187
+
188
+ emit_event("active_job.step_started",
189
+ job_class: job.class.name,
190
+ job_id: job.job_id,
191
+ step: step.name,
192
+ cursor: step.cursor,
193
+ resumed: step.resumed?,
194
+ )
195
+ end
196
+
197
+ def step(event)
198
+ job = event.payload[:job]
199
+ step = event.payload[:step]
200
+ exception = event.payload[:exception_object]
201
+ payload = {
202
+ job_class: job.class.name,
203
+ job_id: job.job_id,
204
+ step: step.name,
205
+ cursor: step.cursor,
206
+ interrupted: event.payload[:interrupted],
207
+ duration: event.duration.round(2),
208
+ }
209
+
210
+ if exception
211
+ payload[:exception_class] = exception.class.name
212
+ payload[:exception_message] = exception.message
213
+ end
214
+
215
+ emit_event("active_job.step", payload)
216
+ end
217
+ end
218
+ end
219
+
220
+ ActiveJob::StructuredEventSubscriber.attach_to :active_job
data/lib/active_job.rb CHANGED
@@ -39,9 +39,12 @@ module ActiveJob
39
39
  autoload :Arguments
40
40
  autoload :DeserializationError, "active_job/arguments"
41
41
  autoload :SerializationError, "active_job/arguments"
42
+ autoload :UnknownJobClassError, "active_job/core"
42
43
  autoload :EnqueueAfterTransactionCommit
43
44
 
44
45
  eager_autoload do
46
+ autoload :Continuable
47
+ autoload :Continuation
45
48
  autoload :Serializers
46
49
  autoload :ConfiguredJob
47
50
  end
@@ -49,18 +52,6 @@ module ActiveJob
49
52
  autoload :TestCase
50
53
  autoload :TestHelper
51
54
 
52
- def self.use_big_decimal_serializer
53
- ActiveJob.deprecator.warn <<-WARNING.squish
54
- Rails.application.config.active_job.use_big_decimal_serializer is deprecated and will be removed in Rails 8.0.
55
- WARNING
56
- end
57
-
58
- def self.use_big_decimal_serializer=(value)
59
- ActiveJob.deprecator.warn <<-WARNING.squish
60
- Rails.application.config.active_job.use_big_decimal_serializer is deprecated and will be removed in Rails 8.0.
61
- WARNING
62
- end
63
-
64
55
  ##
65
56
  # :singleton-method: verbose_enqueue_logs
66
57
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activejob
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.2.3
4
+ version: 8.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 7.2.3
18
+ version: 8.1.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 7.2.3
25
+ version: 8.1.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: globalid
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -51,12 +51,18 @@ files:
51
51
  - lib/active_job/base.rb
52
52
  - lib/active_job/callbacks.rb
53
53
  - lib/active_job/configured_job.rb
54
+ - lib/active_job/continuable.rb
55
+ - lib/active_job/continuation.rb
56
+ - lib/active_job/continuation/step.rb
57
+ - lib/active_job/continuation/test_helper.rb
58
+ - lib/active_job/continuation/validation.rb
54
59
  - lib/active_job/core.rb
55
60
  - lib/active_job/deprecator.rb
56
61
  - lib/active_job/enqueue_after_transaction_commit.rb
57
62
  - lib/active_job/enqueuing.rb
58
63
  - lib/active_job/exceptions.rb
59
64
  - lib/active_job/execution.rb
65
+ - lib/active_job/execution_state.rb
60
66
  - lib/active_job/gem_version.rb
61
67
  - lib/active_job/instrumentation.rb
62
68
  - lib/active_job/log_subscriber.rb
@@ -72,12 +78,12 @@ files:
72
78
  - lib/active_job/queue_adapters/resque_adapter.rb
73
79
  - lib/active_job/queue_adapters/sidekiq_adapter.rb
74
80
  - lib/active_job/queue_adapters/sneakers_adapter.rb
75
- - lib/active_job/queue_adapters/sucker_punch_adapter.rb
76
81
  - lib/active_job/queue_adapters/test_adapter.rb
77
82
  - lib/active_job/queue_name.rb
78
83
  - lib/active_job/queue_priority.rb
79
84
  - lib/active_job/railtie.rb
80
85
  - lib/active_job/serializers.rb
86
+ - lib/active_job/serializers/action_controller_parameters_serializer.rb
81
87
  - lib/active_job/serializers/big_decimal_serializer.rb
82
88
  - lib/active_job/serializers/date_serializer.rb
83
89
  - lib/active_job/serializers/date_time_serializer.rb
@@ -89,10 +95,9 @@ files:
89
95
  - lib/active_job/serializers/time_object_serializer.rb
90
96
  - lib/active_job/serializers/time_serializer.rb
91
97
  - lib/active_job/serializers/time_with_zone_serializer.rb
98
+ - lib/active_job/structured_event_subscriber.rb
92
99
  - lib/active_job/test_case.rb
93
100
  - lib/active_job/test_helper.rb
94
- - lib/active_job/timezones.rb
95
- - lib/active_job/translation.rb
96
101
  - lib/active_job/version.rb
97
102
  - lib/rails/generators/job/USAGE
98
103
  - lib/rails/generators/job/job_generator.rb
@@ -103,10 +108,10 @@ licenses:
103
108
  - MIT
104
109
  metadata:
105
110
  bug_tracker_uri: https://github.com/rails/rails/issues
106
- changelog_uri: https://github.com/rails/rails/blob/v7.2.3/activejob/CHANGELOG.md
107
- documentation_uri: https://api.rubyonrails.org/v7.2.3/
111
+ changelog_uri: https://github.com/rails/rails/blob/v8.1.3/activejob/CHANGELOG.md
112
+ documentation_uri: https://api.rubyonrails.org/v8.1.3/
108
113
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
109
- source_code_uri: https://github.com/rails/rails/tree/v7.2.3/activejob
114
+ source_code_uri: https://github.com/rails/rails/tree/v8.1.3/activejob
110
115
  rubygems_mfa_required: 'true'
111
116
  rdoc_options: []
112
117
  require_paths:
@@ -115,14 +120,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
120
  requirements:
116
121
  - - ">="
117
122
  - !ruby/object:Gem::Version
118
- version: 3.1.0
123
+ version: 3.2.0
119
124
  required_rubygems_version: !ruby/object:Gem::Requirement
120
125
  requirements:
121
126
  - - ">="
122
127
  - !ruby/object:Gem::Version
123
128
  version: '0'
124
129
  requirements: []
125
- rubygems_version: 3.6.9
130
+ rubygems_version: 4.0.6
126
131
  specification_version: 4
127
132
  summary: Job framework with pluggable queues.
128
133
  test_files: []
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "sucker_punch"
4
-
5
- module ActiveJob
6
- module QueueAdapters
7
- # = Sucker Punch adapter for Active Job
8
- #
9
- # Sucker Punch is a single-process Ruby asynchronous processing library.
10
- # This reduces the cost of hosting on a service like Heroku along
11
- # with the memory footprint of having to maintain additional jobs if
12
- # hosting on a dedicated server. All queues can run within a
13
- # single application (e.g. \Rails, Sinatra, etc.) process.
14
- #
15
- # Read more about Sucker Punch {here}[https://github.com/brandonhilkert/sucker_punch].
16
- #
17
- # To use Sucker Punch set the queue_adapter config to +:sucker_punch+.
18
- #
19
- # Rails.application.config.active_job.queue_adapter = :sucker_punch
20
- class SuckerPunchAdapter < AbstractAdapter
21
- def enqueue(job) # :nodoc:
22
- if JobWrapper.respond_to?(:perform_async)
23
- # sucker_punch 2.0 API
24
- JobWrapper.perform_async job.serialize
25
- else
26
- # sucker_punch 1.0 API
27
- JobWrapper.new.async.perform job.serialize
28
- end
29
- end
30
-
31
- def enqueue_at(job, timestamp) # :nodoc:
32
- if JobWrapper.respond_to?(:perform_in)
33
- delay = timestamp - Time.current.to_f
34
- JobWrapper.perform_in delay, job.serialize
35
- else
36
- raise NotImplementedError, "sucker_punch 1.0 does not support `enqueue_at`. Please upgrade to version ~> 2.0.0 to enable this behavior."
37
- end
38
- end
39
-
40
- class JobWrapper # :nodoc:
41
- include SuckerPunch::Job
42
-
43
- def perform(job_data)
44
- Base.execute job_data
45
- end
46
- end
47
- end
48
- end
49
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveJob
4
- module Timezones # :nodoc:
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- around_perform do |job, block|
9
- Time.use_zone(job.timezone, &block)
10
- end
11
- end
12
- end
13
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveJob
4
- module Translation # :nodoc:
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- around_perform do |job, block|
9
- I18n.with_locale(job.locale, &block)
10
- end
11
- end
12
- end
13
- end