shoryuken 7.0.0.alpha2 → 7.0.0

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 (178) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +2 -2
  3. data/.github/workflows/specs.yml +38 -43
  4. data/.github/workflows/verify-action-pins.yml +1 -1
  5. data/.gitignore +3 -0
  6. data/.rspec +1 -0
  7. data/.ruby-version +1 -1
  8. data/.yard-lint.yml +279 -0
  9. data/CHANGELOG.md +69 -1
  10. data/Gemfile +1 -1
  11. data/README.md +2 -7
  12. data/Rakefile +4 -10
  13. data/bin/clean_localstack +52 -0
  14. data/bin/cli/base.rb +21 -0
  15. data/bin/cli/sqs.rb +61 -2
  16. data/bin/integrations +275 -0
  17. data/bin/scenario +154 -0
  18. data/bin/shoryuken +1 -1
  19. data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +15 -4
  20. data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
  21. data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
  22. data/lib/shoryuken/active_job/current_attributes.rb +139 -0
  23. data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
  24. data/lib/shoryuken/body_parser.rb +8 -0
  25. data/lib/shoryuken/client.rb +14 -0
  26. data/lib/shoryuken/default_exception_handler.rb +9 -0
  27. data/lib/shoryuken/default_worker_registry.rb +29 -1
  28. data/lib/shoryuken/environment_loader.rb +78 -8
  29. data/lib/shoryuken/errors.rb +33 -0
  30. data/lib/shoryuken/fetcher.rb +37 -1
  31. data/lib/shoryuken/helpers/atomic_boolean.rb +19 -5
  32. data/lib/shoryuken/helpers/timer_task.rb +80 -0
  33. data/lib/shoryuken/launcher.rb +53 -0
  34. data/lib/shoryuken/logging/base.rb +26 -0
  35. data/lib/shoryuken/logging/pretty.rb +25 -0
  36. data/lib/shoryuken/logging/without_timestamp.rb +25 -0
  37. data/lib/shoryuken/logging.rb +39 -25
  38. data/lib/shoryuken/manager.rb +70 -1
  39. data/lib/shoryuken/message.rb +114 -1
  40. data/lib/shoryuken/middleware/chain.rb +139 -43
  41. data/lib/shoryuken/middleware/entry.rb +30 -0
  42. data/lib/shoryuken/middleware/server/active_record.rb +8 -0
  43. data/lib/shoryuken/middleware/server/auto_delete.rb +10 -0
  44. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +27 -1
  45. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +29 -0
  46. data/lib/shoryuken/middleware/server/timing.rb +11 -0
  47. data/lib/shoryuken/options.rb +129 -6
  48. data/lib/shoryuken/polling/base_strategy.rb +1 -0
  49. data/lib/shoryuken/polling/strict_priority.rb +39 -0
  50. data/lib/shoryuken/polling/weighted_round_robin.rb +42 -0
  51. data/lib/shoryuken/processor.rb +32 -1
  52. data/lib/shoryuken/queue.rb +93 -4
  53. data/lib/shoryuken/runner.rb +45 -4
  54. data/lib/shoryuken/util.rb +26 -1
  55. data/lib/shoryuken/version.rb +2 -1
  56. data/lib/shoryuken/worker/default_executor.rb +21 -1
  57. data/lib/shoryuken/worker/inline_executor.rb +24 -0
  58. data/lib/shoryuken/worker.rb +193 -0
  59. data/lib/shoryuken/worker_registry.rb +33 -0
  60. data/lib/shoryuken.rb +18 -6
  61. data/renovate.json +29 -2
  62. data/shoryuken.gemspec +2 -1
  63. data/spec/integration/.rspec +1 -0
  64. data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
  65. data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
  66. data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
  67. data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
  68. data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
  69. data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
  70. data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
  71. data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
  72. data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
  73. data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
  74. data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
  75. data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
  76. data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
  77. data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
  78. data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
  79. data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
  80. data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
  81. data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
  82. data/spec/integration/aws_config/aws_config_spec.rb +59 -0
  83. data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
  84. data/spec/integration/body_parser/json_parser_spec.rb +45 -0
  85. data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
  86. data/spec/integration/body_parser/text_parser_spec.rb +43 -0
  87. data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
  88. data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
  89. data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
  90. data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
  91. data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
  92. data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
  93. data/spec/integration/launcher/launcher_spec.rb +40 -0
  94. data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
  95. data/spec/integration/message_operations/message_operations_spec.rb +59 -0
  96. data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
  97. data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
  98. data/spec/integration/middleware_chain/removal_spec.rb +31 -0
  99. data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
  100. data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
  101. data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
  102. data/spec/integration/rails/rails_72/Gemfile +6 -0
  103. data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
  104. data/spec/integration/rails/rails_80/Gemfile +6 -0
  105. data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
  106. data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
  107. data/spec/integration/rails/rails_81/Gemfile +6 -0
  108. data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
  109. data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
  110. data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
  111. data/spec/integration/spec_helper.rb +7 -0
  112. data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
  113. data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
  114. data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
  115. data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
  116. data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
  117. data/spec/integrations_helper.rb +243 -0
  118. data/spec/lib/active_job/extensions_spec.rb +149 -0
  119. data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
  120. data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +3 -3
  121. data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +4 -4
  122. data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +1 -1
  123. data/spec/{shoryuken → lib/shoryuken}/helpers/hash_utils_spec.rb +14 -14
  124. data/spec/{shoryuken → lib/shoryuken}/helpers/string_utils_spec.rb +3 -3
  125. data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
  126. data/spec/{shoryuken → lib/shoryuken}/helpers_integration_spec.rb +9 -9
  127. data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +22 -0
  128. data/spec/lib/shoryuken/logging_spec.rb +242 -0
  129. data/spec/lib/shoryuken/message_spec.rb +109 -0
  130. data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
  131. data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
  132. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +50 -0
  133. data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +2 -2
  134. data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +1 -1
  135. data/spec/lib/shoryuken/version_spec.rb +17 -0
  136. data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
  137. data/spec/shared_examples_for_active_job.rb +29 -9
  138. data/spec/spec_helper.rb +34 -3
  139. metadata +230 -91
  140. data/.devcontainer/Dockerfile +0 -17
  141. data/.devcontainer/base.Dockerfile +0 -43
  142. data/.devcontainer/devcontainer.json +0 -35
  143. data/Appraisals +0 -23
  144. data/gemfiles/.gitignore +0 -1
  145. data/gemfiles/rails_7_0.gemfile +0 -19
  146. data/gemfiles/rails_7_1.gemfile +0 -19
  147. data/gemfiles/rails_7_2.gemfile +0 -19
  148. data/gemfiles/rails_8_0.gemfile +0 -19
  149. data/lib/shoryuken/extensions/active_job_adapter.rb +0 -110
  150. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
  151. data/spec/integration/launcher_spec.rb +0 -127
  152. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -8
  153. data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -85
  154. /data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +0 -0
  155. /data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +0 -0
  156. /data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +0 -0
  157. /data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +0 -0
  158. /data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +0 -0
  159. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_boolean_spec.rb +0 -0
  160. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_counter_spec.rb +0 -0
  161. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_hash_spec.rb +0 -0
  162. /data/spec/{shoryuken → lib/shoryuken}/inline_message_spec.rb +0 -0
  163. /data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +0 -0
  164. /data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +0 -0
  165. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +0 -0
  166. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +0 -0
  167. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +0 -0
  168. /data/spec/{shoryuken → lib/shoryuken}/polling/base_strategy_spec.rb +0 -0
  169. /data/spec/{shoryuken → lib/shoryuken}/polling/queue_configuration_spec.rb +0 -0
  170. /data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +0 -0
  171. /data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +0 -0
  172. /data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +0 -0
  173. /data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +0 -0
  174. /data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +0 -0
  175. /data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +0 -0
  176. /data/spec/{shoryuken → lib/shoryuken}/worker/inline_executor_spec.rb +0 -0
  177. /data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +0 -0
  178. /data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +0 -0
@@ -1,56 +1,244 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shoryuken
4
+ # Worker module provides the core functionality for creating Shoryuken workers
5
+ # that process messages from Amazon SQS queues.
6
+ #
7
+ # Including this module in a class provides methods for configuring queue processing,
8
+ # enqueueing jobs, and setting up middleware. Workers can be configured for different
9
+ # processing patterns including single message processing, batch processing, and
10
+ # various retry and visibility timeout strategies.
11
+ #
12
+ # @example Basic worker implementation
13
+ # class EmailWorker
14
+ # include Shoryuken::Worker
15
+ # shoryuken_options queue: 'emails'
16
+ #
17
+ # def perform(sqs_msg, body)
18
+ # send_email(body['recipient'], body['subject'], body['content'])
19
+ # end
20
+ # end
21
+ #
22
+ # @example Advanced worker with all options
23
+ # class AdvancedWorker
24
+ # include Shoryuken::Worker
25
+ #
26
+ # shoryuken_options queue: 'advanced_queue',
27
+ # batch: false,
28
+ # auto_delete: true,
29
+ # auto_visibility_timeout: true,
30
+ # retry_intervals: [1, 5, 25, 125, 625]
31
+ #
32
+ # server_middleware do |chain|
33
+ # chain.add MyCustomMiddleware
34
+ # end
35
+ #
36
+ # def perform(sqs_msg, body)
37
+ # # Worker implementation
38
+ # end
39
+ # end
40
+ #
41
+ # @see ClassMethods#shoryuken_options Primary configuration method
42
+ # @see ClassMethods#perform_async For enqueueing jobs
43
+ # @see https://github.com/ruby-shoryuken/shoryuken/wiki/Workers Comprehensive worker documentation
4
44
  module Worker
45
+ # Sets up the including class with Shoryuken worker functionality
46
+ #
47
+ # @param base [Class] the class including this module
48
+ # @return [void]
5
49
  def self.included(base)
6
50
  base.extend(ClassMethods)
7
51
  base.shoryuken_class_attribute :shoryuken_options_hash
8
52
  end
9
53
 
54
+ # Class methods added to classes that include Shoryuken::Worker.
55
+ # Provides methods for configuring the worker, enqueueing jobs, and managing middleware.
10
56
  module ClassMethods
57
+ # Enqueues a job to be processed asynchronously by a Shoryuken worker.
58
+ #
59
+ # @param body [Object] The job payload that will be passed to the worker's perform method
60
+ # @param options [Hash] Additional options for job enqueueing
61
+ # @option options [String] :message_group_id FIFO queue group ID for message ordering
62
+ # @option options [String] :message_deduplication_id FIFO queue deduplication ID
63
+ # @option options [Hash] :message_attributes Custom SQS message attributes
64
+ # @return [String] The message ID of the enqueued job
65
+ #
66
+ # @example Basic job enqueueing
67
+ # MyWorker.perform_async({ user_id: 123, action: 'send_email' })
68
+ #
69
+ # @example FIFO queue with ordering
70
+ # MyWorker.perform_async(data, message_group_id: 'user_123')
11
71
  def perform_async(body, options = {})
12
72
  Shoryuken.worker_executor.perform_async(self, body, options)
13
73
  end
14
74
 
75
+ # Enqueues a job to be processed after a specified time interval.
76
+ #
77
+ # @param interval [Integer, ActiveSupport::Duration] Delay in seconds, or duration object
78
+ # @param body [Object] The job payload that will be passed to the worker's perform method
79
+ # @param options [Hash] SQS message options for the delayed job
80
+ # @option options [String] :message_group_id FIFO queue group ID for message ordering
81
+ # @option options [String] :message_deduplication_id FIFO queue deduplication ID
82
+ # @option options [Hash] :message_attributes Custom SQS message attributes
83
+ # @return [String] The message ID of the enqueued job
84
+ #
85
+ # @example Delay job by 5 minutes
86
+ # MyWorker.perform_in(5.minutes, { user_id: 123 })
87
+ #
88
+ # @example Delay job by specific number of seconds
89
+ # MyWorker.perform_in(300, { user_id: 123 })
15
90
  def perform_in(interval, body, options = {})
16
91
  Shoryuken.worker_executor.perform_in(self, interval, body, options)
17
92
  end
18
93
 
19
94
  alias_method :perform_at, :perform_in
20
95
 
96
+ # Configures server-side middleware chain for this worker class.
97
+ # Middleware runs before and after job processing, similar to Rack middleware.
98
+ #
99
+ # @yield [Shoryuken::Middleware::Chain] The middleware chain for configuration
100
+ # @return [Shoryuken::Middleware::Chain] The configured middleware chain
101
+ #
102
+ # @example Adding custom middleware
103
+ # class MyWorker
104
+ # include Shoryuken::Worker
105
+ #
106
+ # server_middleware do |chain|
107
+ # chain.add MyCustomMiddleware
108
+ # chain.remove Shoryuken::Middleware::Server::ActiveRecord
109
+ # end
110
+ # end
21
111
  def server_middleware
22
112
  @_server_chain ||= Shoryuken.server_middleware.dup
23
113
  yield @_server_chain if block_given?
24
114
  @_server_chain
25
115
  end
26
116
 
117
+ # Configures worker options including queue assignment, processing behavior,
118
+ # and SQS-specific settings. This is the main configuration method for workers.
119
+ #
120
+ # @param opts [Hash] Configuration options for the worker
121
+ # @option opts [String, Array<String>] :queue Queue name(s) this worker processes
122
+ # @option opts [Boolean] :batch (false) Process messages in batches of up to 10
123
+ # @option opts [Boolean] :auto_delete (false) Automatically delete messages after processing
124
+ # @option opts [Boolean] :auto_visibility_timeout (false) Automatically extend message visibility
125
+ # @option opts [Array<Integer>] :retry_intervals Exponential backoff retry intervals in seconds
126
+ # @option opts [Hash] :sqs Additional SQS client options
127
+ #
128
+ # @example Basic worker configuration
129
+ # class MyWorker
130
+ # include Shoryuken::Worker
131
+ # shoryuken_options queue: 'my_queue'
132
+ #
133
+ # def perform(sqs_msg, body)
134
+ # # Process the message
135
+ # end
136
+ # end
137
+ #
138
+ # @example Worker with auto-delete and retries
139
+ # class ReliableWorker
140
+ # include Shoryuken::Worker
141
+ # shoryuken_options queue: 'important_queue',
142
+ # auto_delete: true,
143
+ # retry_intervals: [1, 5, 25, 125]
144
+ # end
145
+ #
146
+ # @example Batch processing worker
147
+ # class BatchWorker
148
+ # include Shoryuken::Worker
149
+ # shoryuken_options queue: 'batch_queue', batch: true
150
+ #
151
+ # def perform(sqs_msgs, bodies)
152
+ # # Process array of up to 10 messages
153
+ # bodies.each { |body| process_item(body) }
154
+ # end
155
+ # end
156
+ #
157
+ # @example Multiple queues with priorities
158
+ # class MultiQueueWorker
159
+ # include Shoryuken::Worker
160
+ # shoryuken_options queue: ['high_priority', 'low_priority']
161
+ # end
162
+ #
163
+ # @example Auto-extending visibility timeout for long-running jobs
164
+ # class LongRunningWorker
165
+ # include Shoryuken::Worker
166
+ # shoryuken_options queue: 'slow_queue',
167
+ # auto_visibility_timeout: true
168
+ #
169
+ # def perform(sqs_msg, body)
170
+ # # Long processing that might exceed visibility timeout
171
+ # complex_processing(body)
172
+ # end
173
+ # end
27
174
  def shoryuken_options(opts = {})
28
175
  self.shoryuken_options_hash = get_shoryuken_options.merge(stringify_keys(opts || {}))
29
176
  normalize_worker_queue!
30
177
  end
31
178
 
179
+ # Checks if automatic visibility timeout extension is enabled for this worker.
180
+ # When enabled, Shoryuken automatically extends the message visibility timeout
181
+ # during processing to prevent the message from becoming visible to other consumers.
182
+ #
183
+ # @return [Boolean] true if auto visibility timeout is enabled
184
+ #
185
+ # @see #shoryuken_options Documentation for enabling auto_visibility_timeout
32
186
  def auto_visibility_timeout?
33
187
  !!get_shoryuken_options['auto_visibility_timeout']
34
188
  end
35
189
 
190
+ # Checks if exponential backoff retry is configured for this worker.
191
+ # When retry intervals are specified, failed jobs will be retried with
192
+ # increasing delays between attempts.
193
+ #
194
+ # @return [Boolean] true if retry intervals are configured
195
+ #
196
+ # @example Configuring exponential backoff
197
+ # shoryuken_options retry_intervals: [1, 5, 25, 125, 625]
198
+ # # Will retry after 1s, 5s, 25s, 125s, then 625s before giving up
199
+ #
200
+ # @see #shoryuken_options Documentation for configuring retry_intervals
36
201
  def exponential_backoff?
37
202
  !!get_shoryuken_options['retry_intervals']
38
203
  end
39
204
 
205
+ # Checks if automatic message deletion is enabled for this worker.
206
+ # When enabled, successfully processed messages are automatically deleted
207
+ # from the SQS queue. When disabled, you must manually delete messages
208
+ # or they will become visible again after the visibility timeout.
209
+ #
210
+ # @return [Boolean] true if auto delete is enabled
211
+ #
212
+ # @example Manual message deletion when auto_delete is false
213
+ # def perform(sqs_msg, body)
214
+ # process_message(body)
215
+ # # Manually delete the message after successful processing
216
+ # sqs_msg.delete
217
+ # end
218
+ #
219
+ # @see #shoryuken_options Documentation for enabling auto_delete
40
220
  def auto_delete?
41
221
  !!(get_shoryuken_options['delete'] || get_shoryuken_options['auto_delete'])
42
222
  end
43
223
 
224
+ # Returns the shoryuken options for this worker class
225
+ # @return [Hash] the options hash
44
226
  def get_shoryuken_options # :nodoc:
45
227
  shoryuken_options_hash || Shoryuken.default_worker_options
46
228
  end
47
229
 
230
+ # Converts hash keys to strings
231
+ # @param hash [Hash] the hash to convert
232
+ # @return [Hash] hash with string keys
48
233
  def stringify_keys(hash) # :nodoc:
49
234
  new_hash = {}
50
235
  hash.each { |key, value| new_hash[key.to_s] = value }
51
236
  new_hash
52
237
  end
53
238
 
239
+ # Defines inheritable class attributes for workers
240
+ # @param attrs [Array<Symbol>] attribute names to define
241
+ # @return [void]
54
242
  def shoryuken_class_attribute(*attrs) # :nodoc:
55
243
  attrs.each do |name|
56
244
  singleton_class.instance_eval do
@@ -108,6 +296,8 @@ module Shoryuken
108
296
 
109
297
  private
110
298
 
299
+ # Normalizes the queue option and registers the worker
300
+ # @return [void]
111
301
  def normalize_worker_queue!
112
302
  queue = shoryuken_options_hash['queue']
113
303
  if queue.respond_to?(:call)
@@ -125,6 +315,9 @@ module Shoryuken
125
315
  [shoryuken_options_hash['queue']].flatten.compact.each(&method(:register_worker))
126
316
  end
127
317
 
318
+ # Registers this worker class for a queue
319
+ # @param queue [String] the queue name
320
+ # @return [void]
128
321
  def register_worker(queue)
129
322
  Shoryuken.register_worker(queue, self)
130
323
  end
@@ -1,33 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shoryuken
4
+ # Abstract base class for worker registries.
5
+ # Defines the interface for storing and retrieving worker classes.
6
+ # @abstract Subclass and implement all methods to create a custom registry
4
7
  class WorkerRegistry
8
+ # Checks if the workers for a queue support batch message processing
9
+ #
10
+ # @param _queue [String] the queue name
11
+ # @return [Boolean] true if batch processing is supported
12
+ # @raise [NotImplementedError] if not implemented by subclass
5
13
  def batch_receive_messages?(_queue)
6
14
  # true if the workers for queue support batch processing of messages
7
15
  fail NotImplementedError
8
16
  end
9
17
 
18
+ # Removes all worker registrations
19
+ #
20
+ # @return [void]
21
+ # @raise [NotImplementedError] if not implemented by subclass
10
22
  def clear
11
23
  # must remove all worker registrations
12
24
  fail NotImplementedError
13
25
  end
14
26
 
27
+ # Fetches a worker instance for processing a message
28
+ #
29
+ # @param _queue [String] the queue name
30
+ # @param _message [Shoryuken::Message, Array<Shoryuken::Message>] the message or batch
31
+ # @return [Object] a worker instance
32
+ # @raise [NotImplementedError] if not implemented by subclass
15
33
  def fetch_worker(_queue, _message)
16
34
  # must return an instance of the worker that handles
17
35
  # message received on queue
18
36
  fail NotImplementedError
19
37
  end
20
38
 
39
+ # Returns a list of all queues with registered workers
40
+ #
41
+ # @return [Array<String>] the queue names
42
+ # @raise [NotImplementedError] if not implemented by subclass
21
43
  def queues
22
44
  # must return a list of all queues with registered workers
23
45
  fail NotImplementedError
24
46
  end
25
47
 
48
+ # Registers a worker class for a queue
49
+ #
50
+ # @param _queue [String] the queue name
51
+ # @param _clazz [Class] the worker class
52
+ # @return [void]
53
+ # @raise [NotImplementedError] if not implemented by subclass
26
54
  def register_worker(_queue, _clazz)
27
55
  # must register the worker as a consumer of messages from queue
28
56
  fail NotImplementedError
29
57
  end
30
58
 
59
+ # Returns all worker classes registered for a queue
60
+ #
61
+ # @param _queue [String] the queue name
62
+ # @return [Array<Class>] the worker classes, or empty array
63
+ # @raise [NotImplementedError] if not implemented by subclass
31
64
  def workers(_queue)
32
65
  # must return the list of workers registered for queue, or []
33
66
  fail NotImplementedError
data/lib/shoryuken.rb CHANGED
@@ -1,25 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
4
- require 'json'
5
3
  require 'aws-sdk-sqs'
4
+ require 'json'
5
+ require 'logger'
6
6
  require 'time'
7
7
  require 'concurrent'
8
8
  require 'forwardable'
9
9
  require 'zeitwerk'
10
+ require 'yaml'
10
11
 
11
12
  # Set up Zeitwerk loader
12
13
  loader = Zeitwerk::Loader.for_gem
13
- loader.ignore("#{__dir__}/shoryuken/extensions")
14
+ loader.ignore("#{__dir__}/active_job")
14
15
  loader.setup
15
16
 
17
+ # Shoryuken is a super efficient AWS SQS thread based message processor.
18
+ # It provides a simple interface to process SQS messages using Ruby workers.
16
19
  module Shoryuken
17
20
  extend SingleForwardable
18
21
 
22
+ # Returns the global Shoryuken configuration options instance.
23
+ # This is used internally for storing and accessing configuration settings.
24
+ #
25
+ # @return [Shoryuken::Options] The global options instance
19
26
  def self.shoryuken_options
20
27
  @_shoryuken_options ||= Shoryuken::Options.new
21
28
  end
22
29
 
30
+ # Checks if the Shoryuken server is running and healthy.
31
+ # A server is considered healthy when all configured processing groups
32
+ # are running and able to process messages.
33
+ #
34
+ # @return [Boolean] true if the server is healthy
23
35
  def self.healthy?
24
36
  Shoryuken::Runner.instance.healthy?
25
37
  end
@@ -77,7 +89,7 @@ module Shoryuken
77
89
  end
78
90
 
79
91
  if Shoryuken.active_job?
80
- require 'shoryuken/extensions/active_job_extensions'
81
- require 'shoryuken/extensions/active_job_adapter'
82
- require 'shoryuken/extensions/active_job_concurrent_send_adapter'
92
+ require 'active_job/extensions'
93
+ require 'active_job/queue_adapters/shoryuken_adapter'
94
+ require 'active_job/queue_adapters/shoryuken_concurrent_send_adapter'
83
95
  end
data/renovate.json CHANGED
@@ -3,14 +3,41 @@
3
3
  "extends": [
4
4
  "config:recommended"
5
5
  ],
6
+ "minimumReleaseAge": "7 days",
6
7
  "github-actions": {
7
8
  "enabled": true,
8
9
  "pinDigests": true
9
10
  },
11
+ "bundler": {
12
+ "enabled": true,
13
+ "managerFilePatterns": [
14
+ "/(^|/)Gemfile$/",
15
+ "/\\.gemfile$/",
16
+ "/(^|/)gems\\.rb$/",
17
+ "/spec/gemfiles/.+\\.gemfile$/",
18
+ "/spec/integration/.*/Gemfile$/"
19
+ ]
20
+ },
10
21
  "packageRules": [
11
22
  {
12
- "matchManagers": ["github-actions"],
13
- "minimumReleaseAge": "7 days"
23
+ "matchManagers": [
24
+ "bundler"
25
+ ],
26
+ "matchFileNames": [
27
+ "spec/gemfiles/**"
28
+ ],
29
+ "groupName": "Rails test dependencies",
30
+ "description": "Group Rails version-specific test Gemfiles together"
31
+ },
32
+ {
33
+ "matchManagers": [
34
+ "bundler"
35
+ ],
36
+ "matchFileNames": [
37
+ "spec/integration/**/Gemfile"
38
+ ],
39
+ "groupName": "Integration test dependencies",
40
+ "description": "Group integration test Gemfiles together"
14
41
  }
15
42
  ]
16
43
  }
data/shoryuken.gemspec CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency 'ostruct'
26
26
  spec.add_development_dependency 'rake'
27
27
  spec.add_development_dependency 'rspec'
28
+ spec.add_development_dependency 'warning'
28
29
 
29
- spec.required_ruby_version = '>= 3.1.0'
30
+ spec.required_ruby_version = '>= 3.2.0'
30
31
  end
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests ActiveJob adapter configuration including adapter type,
4
+ # Rails 7.2+ transaction commit hook, and singleton pattern.
5
+
6
+ setup_active_job
7
+
8
+ class ConfigTestJob < ActiveJob::Base
9
+ queue_as :config_test
10
+
11
+ def perform(data)
12
+ "Processed: #{data}"
13
+ end
14
+ end
15
+
16
+ adapter = ActiveJob::Base.queue_adapter
17
+ assert_equal("ActiveJob::QueueAdapters::ShoryukenAdapter", adapter.class.name)
18
+
19
+ adapter_instance = ActiveJob::QueueAdapters::ShoryukenAdapter.new
20
+ assert(adapter_instance.respond_to?(:enqueue_after_transaction_commit?))
21
+ assert_equal(true, adapter_instance.enqueue_after_transaction_commit?)
22
+
23
+ instance1 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
24
+ instance2 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
25
+ assert_equal(instance1.object_id, instance2.object_id)
26
+ assert(instance1.is_a?(ActiveJob::QueueAdapters::ShoryukenAdapter))
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Bulk enqueue integration test
4
+ # Tests perform_all_later with the new enqueue_all method using SQS batch API
5
+
6
+ setup_localstack
7
+ setup_active_job
8
+
9
+ queue_name = DT.queue
10
+ create_test_queue(queue_name)
11
+
12
+ class BulkTestJob < ActiveJob::Base
13
+ def perform(index, data)
14
+ DT[:executions] << {
15
+ index: index,
16
+ data: data,
17
+ job_id: job_id,
18
+ executed_at: Time.now
19
+ }
20
+ end
21
+ end
22
+
23
+ BulkTestJob.queue_as(queue_name)
24
+
25
+ Shoryuken.add_group('default', 1)
26
+ Shoryuken.add_queue(queue_name, 1, 'default')
27
+ Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
28
+
29
+ jobs = (1..15).map { |i| BulkTestJob.new(i, "payload_#{i}") }
30
+
31
+ # Use perform_all_later which should call enqueue_all
32
+ ActiveJob.perform_all_later(jobs)
33
+
34
+ successfully_enqueued_count = jobs.count(&:successfully_enqueued?)
35
+ assert_equal(15, successfully_enqueued_count, "Expected all 15 jobs to be marked as successfully enqueued")
36
+
37
+ poll_queues_until(timeout: 45) do
38
+ DT[:executions].size >= 15
39
+ end
40
+
41
+ assert_equal(15, DT[:executions].size, "Expected 15 job executions, got #{DT[:executions].size}")
42
+
43
+ executed_indices = DT[:executions].map { |e| e[:index] }.sort
44
+ expected_indices = (1..15).to_a
45
+ assert_equal(expected_indices, executed_indices, "All job indices should be present")
46
+
47
+ DT[:executions].each do |execution|
48
+ expected_data = "payload_#{execution[:index]}"
49
+ assert_equal(expected_data, execution[:data], "Job #{execution[:index]} should have correct data")
50
+ end
51
+
52
+ job_ids = DT[:executions].map { |e| e[:job_id] }
53
+ assert_equal(15, job_ids.uniq.size, "All job IDs should be unique")
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # CurrentAttributes are persisted correctly when using bulk enqueue (perform_all_later)
4
+
5
+ setup_localstack
6
+ setup_active_job
7
+
8
+ require 'active_support/current_attributes'
9
+ require 'shoryuken/active_job/current_attributes'
10
+
11
+ queue_name = DT.queue
12
+ create_test_queue(queue_name)
13
+
14
+ class TestCurrent < ActiveSupport::CurrentAttributes
15
+ attribute :user_id, :tenant_id
16
+ end
17
+
18
+ Shoryuken::ActiveJob::CurrentAttributes.persist(TestCurrent)
19
+
20
+ class BulkCurrentAttributesTestJob < ActiveJob::Base
21
+ def perform(index)
22
+ DT[:executions] << {
23
+ index: index,
24
+ user_id: TestCurrent.user_id,
25
+ tenant_id: TestCurrent.tenant_id
26
+ }
27
+ end
28
+ end
29
+
30
+ BulkCurrentAttributesTestJob.queue_as(queue_name)
31
+
32
+ Shoryuken.add_group('default', 1)
33
+ Shoryuken.add_queue(queue_name, 1, 'default')
34
+ Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
35
+
36
+ TestCurrent.user_id = 'bulk-user-123'
37
+ TestCurrent.tenant_id = 'bulk-tenant'
38
+
39
+ jobs = (1..3).map { |i| BulkCurrentAttributesTestJob.new(i) }
40
+ ActiveJob.perform_all_later(jobs)
41
+
42
+ TestCurrent.reset
43
+
44
+ poll_queues_until(timeout: 30) { DT[:executions].size >= 3 }
45
+
46
+ assert_equal(3, DT[:executions].size)
47
+ DT[:executions].each do |job|
48
+ assert_equal('bulk-user-123', job[:user_id])
49
+ assert_equal('bulk-tenant', job[:tenant_id])
50
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # CurrentAttributes with complex data types (hashes, arrays, symbols) are serialized and restored
4
+
5
+ setup_localstack
6
+ setup_active_job
7
+
8
+ require 'active_support/current_attributes'
9
+ require 'shoryuken/active_job/current_attributes'
10
+
11
+ queue_name = DT.queue
12
+ create_test_queue(queue_name)
13
+
14
+ class TestCurrent < ActiveSupport::CurrentAttributes
15
+ attribute :user_id, :tenant_id
16
+ end
17
+
18
+ Shoryuken::ActiveJob::CurrentAttributes.persist(TestCurrent)
19
+
20
+ class ComplexTypesTestJob < ActiveJob::Base
21
+ def perform
22
+ DT[:executions] << {
23
+ user_id: TestCurrent.user_id,
24
+ tenant_id: TestCurrent.tenant_id
25
+ }
26
+ end
27
+ end
28
+
29
+ ComplexTypesTestJob.queue_as(queue_name)
30
+
31
+ Shoryuken.add_group('default', 1)
32
+ Shoryuken.add_queue(queue_name, 1, 'default')
33
+ Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
34
+
35
+ TestCurrent.user_id = { role: :admin, permissions: [:read, :write, :delete] }
36
+ TestCurrent.tenant_id = [:tenant_a, :tenant_b]
37
+
38
+ ComplexTypesTestJob.perform_later
39
+
40
+ TestCurrent.reset
41
+
42
+ poll_queues_until(timeout: 30) { DT[:executions].size >= 1 }
43
+
44
+ result = DT[:executions].first
45
+
46
+ user_data = result[:user_id]
47
+ assert(user_data.is_a?(Hash))
48
+ role = user_data['role'] || user_data[:role]
49
+ assert_equal('admin', role.to_s)
50
+ permissions = user_data['permissions'] || user_data[:permissions]
51
+ assert_equal(3, permissions.size)
52
+
53
+ tenant_data = result[:tenant_id]
54
+ assert(tenant_data.is_a?(Array))
55
+ assert_equal(2, tenant_data.size)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # CurrentAttributes without any values set result in nil attributes during job execution
4
+
5
+ setup_localstack
6
+ setup_active_job
7
+
8
+ require 'active_support/current_attributes'
9
+ require 'shoryuken/active_job/current_attributes'
10
+
11
+ queue_name = DT.queue
12
+ create_test_queue(queue_name)
13
+
14
+ class TestCurrent < ActiveSupport::CurrentAttributes
15
+ attribute :user_id, :tenant_id
16
+ end
17
+
18
+ Shoryuken::ActiveJob::CurrentAttributes.persist(TestCurrent)
19
+
20
+ class EmptyContextTestJob < ActiveJob::Base
21
+ def perform
22
+ DT[:executions] << {
23
+ user_id: TestCurrent.user_id,
24
+ tenant_id: TestCurrent.tenant_id
25
+ }
26
+ end
27
+ end
28
+
29
+ EmptyContextTestJob.queue_as(queue_name)
30
+
31
+ Shoryuken.add_group('default', 1)
32
+ Shoryuken.add_queue(queue_name, 1, 'default')
33
+ Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
34
+
35
+ EmptyContextTestJob.perform_later
36
+
37
+ poll_queues_until(timeout: 30) { DT[:executions].size >= 1 }
38
+
39
+ result = DT[:executions].first
40
+ assert(result[:user_id].nil?)
41
+ assert(result[:tenant_id].nil?)