shoryuken 6.2.1 → 7.0.2

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 (205) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +36 -0
  3. data/.github/workflows/specs.yml +49 -44
  4. data/.github/workflows/verify-action-pins.yml +16 -0
  5. data/.gitignore +4 -1
  6. data/.rspec +3 -1
  7. data/.rubocop.yml +6 -1
  8. data/.ruby-version +1 -0
  9. data/.yard-lint.yml +279 -0
  10. data/CHANGELOG.md +308 -139
  11. data/Gemfile +1 -8
  12. data/Gemfile.lint +9 -0
  13. data/Gemfile.lint.lock +69 -0
  14. data/README.md +16 -33
  15. data/Rakefile +6 -10
  16. data/bin/clean_sqs +52 -0
  17. data/bin/cli/base.rb +22 -2
  18. data/bin/cli/sqs.rb +74 -7
  19. data/bin/integrations +275 -0
  20. data/bin/scenario +154 -0
  21. data/bin/shoryuken +3 -2
  22. data/docker-compose.yml +6 -0
  23. data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +20 -6
  24. data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
  25. data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
  26. data/lib/shoryuken/active_job/current_attributes.rb +139 -0
  27. data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
  28. data/lib/shoryuken/body_parser.rb +11 -1
  29. data/lib/shoryuken/client.rb +16 -0
  30. data/lib/shoryuken/default_exception_handler.rb +11 -0
  31. data/lib/shoryuken/default_worker_registry.rb +39 -11
  32. data/lib/shoryuken/environment_loader.rb +85 -15
  33. data/lib/shoryuken/errors.rb +36 -0
  34. data/lib/shoryuken/fetcher.rb +41 -3
  35. data/lib/shoryuken/helpers/atomic_boolean.rb +58 -0
  36. data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
  37. data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
  38. data/lib/shoryuken/helpers/hash_utils.rb +56 -0
  39. data/lib/shoryuken/helpers/string_utils.rb +65 -0
  40. data/lib/shoryuken/helpers/timer_task.rb +80 -0
  41. data/lib/shoryuken/inline_message.rb +22 -0
  42. data/lib/shoryuken/launcher.rb +55 -0
  43. data/lib/shoryuken/logging/base.rb +26 -0
  44. data/lib/shoryuken/logging/pretty.rb +25 -0
  45. data/lib/shoryuken/logging/without_timestamp.rb +25 -0
  46. data/lib/shoryuken/logging.rb +43 -15
  47. data/lib/shoryuken/manager.rb +84 -5
  48. data/lib/shoryuken/message.rb +116 -1
  49. data/lib/shoryuken/middleware/chain.rb +141 -43
  50. data/lib/shoryuken/middleware/entry.rb +30 -0
  51. data/lib/shoryuken/middleware/server/active_record.rb +10 -0
  52. data/lib/shoryuken/middleware/server/auto_delete.rb +12 -0
  53. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +37 -11
  54. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +34 -3
  55. data/lib/shoryuken/middleware/server/non_retryable_exception.rb +95 -0
  56. data/lib/shoryuken/middleware/server/timing.rb +13 -0
  57. data/lib/shoryuken/options.rb +154 -13
  58. data/lib/shoryuken/polling/base_strategy.rb +127 -0
  59. data/lib/shoryuken/polling/queue_configuration.rb +103 -0
  60. data/lib/shoryuken/polling/strict_priority.rb +41 -0
  61. data/lib/shoryuken/polling/weighted_round_robin.rb +44 -0
  62. data/lib/shoryuken/processor.rb +37 -3
  63. data/lib/shoryuken/queue.rb +99 -8
  64. data/lib/shoryuken/runner.rb +54 -16
  65. data/lib/shoryuken/util.rb +32 -7
  66. data/lib/shoryuken/version.rb +4 -1
  67. data/lib/shoryuken/worker/default_executor.rb +23 -1
  68. data/lib/shoryuken/worker/inline_executor.rb +33 -2
  69. data/lib/shoryuken/worker.rb +224 -0
  70. data/lib/shoryuken/worker_registry.rb +35 -0
  71. data/lib/shoryuken.rb +27 -38
  72. data/renovate.json +62 -0
  73. data/shoryuken.gemspec +8 -4
  74. data/spec/integration/.rspec +1 -0
  75. data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
  76. data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
  77. data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
  78. data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
  79. data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
  80. data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
  81. data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
  82. data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
  83. data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
  84. data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
  85. data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
  86. data/spec/integration/active_job/keyword_arguments/keyword_arguments_spec.rb +63 -0
  87. data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
  88. data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
  89. data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
  90. data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
  91. data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
  92. data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
  93. data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
  94. data/spec/integration/aws_config/aws_config_spec.rb +59 -0
  95. data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
  96. data/spec/integration/body_parser/json_parser_spec.rb +45 -0
  97. data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
  98. data/spec/integration/body_parser/text_parser_spec.rb +43 -0
  99. data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
  100. data/spec/integration/custom_group_polling_strategy/custom_group_polling_strategy_spec.rb +87 -0
  101. data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
  102. data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
  103. data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
  104. data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
  105. data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
  106. data/spec/integration/launcher/launcher_spec.rb +40 -0
  107. data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
  108. data/spec/integration/message_operations/message_operations_spec.rb +59 -0
  109. data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
  110. data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
  111. data/spec/integration/middleware_chain/removal_spec.rb +31 -0
  112. data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
  113. data/spec/integration/non_retryable_exception/non_retryable_exception_spec.rb +149 -0
  114. data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
  115. data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
  116. data/spec/integration/rails/rails_72/Gemfile +6 -0
  117. data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
  118. data/spec/integration/rails/rails_80/Gemfile +6 -0
  119. data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
  120. data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
  121. data/spec/integration/rails/rails_81/Gemfile +6 -0
  122. data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
  123. data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
  124. data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
  125. data/spec/integration/spec_helper.rb +7 -0
  126. data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
  127. data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
  128. data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
  129. data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
  130. data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
  131. data/spec/integrations_helper.rb +243 -0
  132. data/spec/lib/active_job/extensions_spec.rb +225 -0
  133. data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
  134. data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +5 -4
  135. data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +6 -5
  136. data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +2 -4
  137. data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +1 -1
  138. data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +9 -10
  139. data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +1 -2
  140. data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +10 -9
  141. data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +23 -26
  142. data/spec/lib/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
  143. data/spec/lib/shoryuken/helpers/atomic_counter_spec.rb +177 -0
  144. data/spec/lib/shoryuken/helpers/atomic_hash_spec.rb +307 -0
  145. data/spec/lib/shoryuken/helpers/hash_utils_spec.rb +145 -0
  146. data/spec/lib/shoryuken/helpers/string_utils_spec.rb +124 -0
  147. data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
  148. data/spec/lib/shoryuken/helpers_integration_spec.rb +96 -0
  149. data/spec/lib/shoryuken/inline_message_spec.rb +196 -0
  150. data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +23 -2
  151. data/spec/lib/shoryuken/logging_spec.rb +242 -0
  152. data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +1 -2
  153. data/spec/lib/shoryuken/message_spec.rb +109 -0
  154. data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +1 -1
  155. data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
  156. data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
  157. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +1 -1
  158. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +51 -1
  159. data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +1 -1
  160. data/spec/lib/shoryuken/middleware/server/non_retryable_exception_spec.rb +214 -0
  161. data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +1 -1
  162. data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +49 -6
  163. data/spec/lib/shoryuken/polling/base_strategy_spec.rb +280 -0
  164. data/spec/lib/shoryuken/polling/queue_configuration_spec.rb +195 -0
  165. data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +1 -1
  166. data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +1 -1
  167. data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +1 -1
  168. data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +2 -3
  169. data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +1 -3
  170. data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +2 -2
  171. data/spec/lib/shoryuken/version_spec.rb +17 -0
  172. data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +1 -1
  173. data/spec/lib/shoryuken/worker/inline_executor_spec.rb +105 -0
  174. data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
  175. data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +15 -11
  176. data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +1 -1
  177. data/spec/shared_examples_for_active_job.rb +40 -15
  178. data/spec/spec_helper.rb +48 -2
  179. metadata +295 -101
  180. data/.codeclimate.yml +0 -20
  181. data/.devcontainer/Dockerfile +0 -17
  182. data/.devcontainer/base.Dockerfile +0 -43
  183. data/.devcontainer/devcontainer.json +0 -35
  184. data/.github/FUNDING.yml +0 -12
  185. data/.github/dependabot.yml +0 -6
  186. data/.github/workflows/stale.yml +0 -20
  187. data/.reek.yml +0 -5
  188. data/Appraisals +0 -42
  189. data/gemfiles/.gitignore +0 -1
  190. data/gemfiles/aws_sdk_core_2.gemfile +0 -21
  191. data/gemfiles/rails_4_2.gemfile +0 -20
  192. data/gemfiles/rails_5_2.gemfile +0 -21
  193. data/gemfiles/rails_6_0.gemfile +0 -21
  194. data/gemfiles/rails_6_1.gemfile +0 -21
  195. data/gemfiles/rails_7_0.gemfile +0 -22
  196. data/lib/shoryuken/core_ext.rb +0 -69
  197. data/lib/shoryuken/extensions/active_job_adapter.rb +0 -103
  198. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
  199. data/lib/shoryuken/polling/base.rb +0 -67
  200. data/shoryuken.jpg +0 -0
  201. data/spec/integration/launcher_spec.rb +0 -128
  202. data/spec/shoryuken/core_ext_spec.rb +0 -40
  203. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -7
  204. data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -84
  205. data/spec/shoryuken/worker/inline_executor_spec.rb +0 -49
@@ -1,54 +1,273 @@
1
+ # frozen_string_literal: true
2
+
1
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
2
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]
3
49
  def self.included(base)
4
50
  base.extend(ClassMethods)
5
51
  base.shoryuken_class_attribute :shoryuken_options_hash
6
52
  end
7
53
 
54
+ # Class methods added to classes that include Shoryuken::Worker.
55
+ # Provides methods for configuring the worker, enqueueing jobs, and managing middleware.
8
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')
9
71
  def perform_async(body, options = {})
10
72
  Shoryuken.worker_executor.perform_async(self, body, options)
11
73
  end
12
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 })
13
90
  def perform_in(interval, body, options = {})
14
91
  Shoryuken.worker_executor.perform_in(self, interval, body, options)
15
92
  end
16
93
 
17
94
  alias_method :perform_at, :perform_in
18
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
19
111
  def server_middleware
20
112
  @_server_chain ||= Shoryuken.server_middleware.dup
21
113
  yield @_server_chain if block_given?
22
114
  @_server_chain
23
115
  end
24
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 [Array<Class>, Proc] :non_retryable_exceptions Exception classes or lambda that should skip retries and delete message immediately
127
+ # @option opts [Hash] :sqs Additional SQS client options
128
+ #
129
+ # @example Basic worker configuration
130
+ # class MyWorker
131
+ # include Shoryuken::Worker
132
+ # shoryuken_options queue: 'my_queue'
133
+ #
134
+ # def perform(sqs_msg, body)
135
+ # # Process the message
136
+ # end
137
+ # end
138
+ #
139
+ # @example Worker with auto-delete and retries
140
+ # class ReliableWorker
141
+ # include Shoryuken::Worker
142
+ # shoryuken_options queue: 'important_queue',
143
+ # auto_delete: true,
144
+ # retry_intervals: [1, 5, 25, 125]
145
+ # end
146
+ #
147
+ # @example Batch processing worker
148
+ # class BatchWorker
149
+ # include Shoryuken::Worker
150
+ # shoryuken_options queue: 'batch_queue', batch: true
151
+ #
152
+ # def perform(sqs_msgs, bodies)
153
+ # # Process array of up to 10 messages
154
+ # bodies.each { |body| process_item(body) }
155
+ # end
156
+ # end
157
+ #
158
+ # @example Multiple queues with priorities
159
+ # class MultiQueueWorker
160
+ # include Shoryuken::Worker
161
+ # shoryuken_options queue: ['high_priority', 'low_priority']
162
+ # end
163
+ #
164
+ # @example Auto-extending visibility timeout for long-running jobs
165
+ # class LongRunningWorker
166
+ # include Shoryuken::Worker
167
+ # shoryuken_options queue: 'slow_queue',
168
+ # auto_visibility_timeout: true
169
+ #
170
+ # def perform(sqs_msg, body)
171
+ # # Long processing that might exceed visibility timeout
172
+ # complex_processing(body)
173
+ # end
174
+ # end
175
+ #
176
+ # @example Worker with non-retryable exceptions
177
+ # class ValidationWorker
178
+ # include Shoryuken::Worker
179
+ # shoryuken_options queue: 'validation_queue',
180
+ # non_retryable_exceptions: [InvalidInputError, RecordNotFoundError]
181
+ #
182
+ # def perform(sqs_msg, body)
183
+ # # If InvalidInputError or RecordNotFoundError is raised,
184
+ # # the message will be deleted immediately instead of retrying
185
+ # validate_and_process(body)
186
+ # end
187
+ # end
188
+ #
189
+ # @example Worker with lambda for dynamic exception classification
190
+ # class SmartWorker
191
+ # include Shoryuken::Worker
192
+ # shoryuken_options queue: 'smart_queue',
193
+ # non_retryable_exceptions: ->(error) {
194
+ # error.is_a?(ValidationError) ||
195
+ # (error.is_a?(NetworkError) && error.message.include?('permanent'))
196
+ # }
197
+ #
198
+ # def perform(sqs_msg, body)
199
+ # # Lambda receives the exception and returns true if non-retryable
200
+ # process_with_validation(body)
201
+ # end
202
+ # end
25
203
  def shoryuken_options(opts = {})
26
204
  self.shoryuken_options_hash = get_shoryuken_options.merge(stringify_keys(opts || {}))
27
205
  normalize_worker_queue!
28
206
  end
29
207
 
208
+ # Checks if automatic visibility timeout extension is enabled for this worker.
209
+ # When enabled, Shoryuken automatically extends the message visibility timeout
210
+ # during processing to prevent the message from becoming visible to other consumers.
211
+ #
212
+ # @return [Boolean] true if auto visibility timeout is enabled
213
+ #
214
+ # @see #shoryuken_options Documentation for enabling auto_visibility_timeout
30
215
  def auto_visibility_timeout?
31
216
  !!get_shoryuken_options['auto_visibility_timeout']
32
217
  end
33
218
 
219
+ # Checks if exponential backoff retry is configured for this worker.
220
+ # When retry intervals are specified, failed jobs will be retried with
221
+ # increasing delays between attempts.
222
+ #
223
+ # @return [Boolean] true if retry intervals are configured
224
+ #
225
+ # @example Configuring exponential backoff
226
+ # shoryuken_options retry_intervals: [1, 5, 25, 125, 625]
227
+ # # Will retry after 1s, 5s, 25s, 125s, then 625s before giving up
228
+ #
229
+ # @see #shoryuken_options Documentation for configuring retry_intervals
34
230
  def exponential_backoff?
35
231
  !!get_shoryuken_options['retry_intervals']
36
232
  end
37
233
 
234
+ # Checks if automatic message deletion is enabled for this worker.
235
+ # When enabled, successfully processed messages are automatically deleted
236
+ # from the SQS queue. When disabled, you must manually delete messages
237
+ # or they will become visible again after the visibility timeout.
238
+ #
239
+ # @return [Boolean] true if auto delete is enabled
240
+ #
241
+ # @example Manual message deletion when auto_delete is false
242
+ # def perform(sqs_msg, body)
243
+ # process_message(body)
244
+ # # Manually delete the message after successful processing
245
+ # sqs_msg.delete
246
+ # end
247
+ #
248
+ # @see #shoryuken_options Documentation for enabling auto_delete
38
249
  def auto_delete?
39
250
  !!(get_shoryuken_options['delete'] || get_shoryuken_options['auto_delete'])
40
251
  end
41
252
 
253
+ # Returns the shoryuken options for this worker class
254
+ # @return [Hash] the options hash
42
255
  def get_shoryuken_options # :nodoc:
43
256
  shoryuken_options_hash || Shoryuken.default_worker_options
44
257
  end
45
258
 
259
+ # Converts hash keys to strings
260
+ # @param hash [Hash] the hash to convert
261
+ # @return [Hash] hash with string keys
46
262
  def stringify_keys(hash) # :nodoc:
47
263
  new_hash = {}
48
264
  hash.each { |key, value| new_hash[key.to_s] = value }
49
265
  new_hash
50
266
  end
51
267
 
268
+ # Defines inheritable class attributes for workers
269
+ # @param attrs [Array<Symbol>] attribute names to define
270
+ # @return [void]
52
271
  def shoryuken_class_attribute(*attrs) # :nodoc:
53
272
  attrs.each do |name|
54
273
  singleton_class.instance_eval do
@@ -106,6 +325,8 @@ module Shoryuken
106
325
 
107
326
  private
108
327
 
328
+ # Normalizes the queue option and registers the worker
329
+ # @return [void]
109
330
  def normalize_worker_queue!
110
331
  queue = shoryuken_options_hash['queue']
111
332
  if queue.respond_to?(:call)
@@ -123,6 +344,9 @@ module Shoryuken
123
344
  [shoryuken_options_hash['queue']].flatten.compact.each(&method(:register_worker))
124
345
  end
125
346
 
347
+ # Registers this worker class for a queue
348
+ # @param queue [String] the queue name
349
+ # @return [void]
126
350
  def register_worker(queue)
127
351
  Shoryuken.register_worker(queue, self)
128
352
  end
@@ -1,31 +1,66 @@
1
+ # frozen_string_literal: true
2
+
1
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
2
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
3
13
  def batch_receive_messages?(_queue)
4
14
  # true if the workers for queue support batch processing of messages
5
15
  fail NotImplementedError
6
16
  end
7
17
 
18
+ # Removes all worker registrations
19
+ #
20
+ # @return [void]
21
+ # @raise [NotImplementedError] if not implemented by subclass
8
22
  def clear
9
23
  # must remove all worker registrations
10
24
  fail NotImplementedError
11
25
  end
12
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
13
33
  def fetch_worker(_queue, _message)
14
34
  # must return an instance of the worker that handles
15
35
  # message received on queue
16
36
  fail NotImplementedError
17
37
  end
18
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
19
43
  def queues
20
44
  # must return a list of all queues with registered workers
21
45
  fail NotImplementedError
22
46
  end
23
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
24
54
  def register_worker(_queue, _clazz)
25
55
  # must register the worker as a consumer of messages from queue
26
56
  fail NotImplementedError
27
57
  end
28
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
29
64
  def workers(_queue)
30
65
  # must return the list of workers registered for queue, or []
31
66
  fail NotImplementedError
data/lib/shoryuken.rb CHANGED
@@ -1,51 +1,37 @@
1
- require 'yaml'
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-sqs'
2
4
  require 'json'
3
- require 'aws-sdk-core'
4
- begin
5
- require 'aws-sdk-sqs' unless defined?(Aws::SQS)
6
- rescue LoadError
7
- fail "AWS SDK 3 requires aws-sdk-sqs to be installed separately. Please add gem 'aws-sdk-sqs' to your Gemfile"
8
- end
5
+ require 'logger'
9
6
  require 'time'
10
7
  require 'concurrent'
11
8
  require 'forwardable'
9
+ require 'zeitwerk'
10
+ require 'yaml'
12
11
 
13
- require 'shoryuken/version'
14
- require 'shoryuken/core_ext'
15
- require 'shoryuken/util'
16
- require 'shoryuken/logging'
17
- require 'shoryuken/environment_loader'
18
- require 'shoryuken/queue'
19
- require 'shoryuken/message'
20
- require 'shoryuken/client'
21
- require 'shoryuken/worker'
22
- require 'shoryuken/worker/default_executor'
23
- require 'shoryuken/worker/inline_executor'
24
- require 'shoryuken/worker_registry'
25
- require 'shoryuken/default_worker_registry'
26
- require 'shoryuken/default_exception_handler'
27
- require 'shoryuken/middleware/chain'
28
- require 'shoryuken/middleware/server/auto_delete'
29
- Shoryuken::Middleware::Server.autoload :AutoExtendVisibility, 'shoryuken/middleware/server/auto_extend_visibility'
30
- require 'shoryuken/middleware/server/exponential_backoff_retry'
31
- require 'shoryuken/middleware/server/timing'
32
- require 'shoryuken/polling/base'
33
- require 'shoryuken/polling/weighted_round_robin'
34
- require 'shoryuken/polling/strict_priority'
35
- require 'shoryuken/manager'
36
- require 'shoryuken/launcher'
37
- require 'shoryuken/processor'
38
- require 'shoryuken/body_parser'
39
- require 'shoryuken/fetcher'
40
- require 'shoryuken/options'
12
+ # Set up Zeitwerk loader
13
+ loader = Zeitwerk::Loader.for_gem
14
+ loader.ignore("#{__dir__}/active_job")
15
+ loader.setup
41
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.
42
19
  module Shoryuken
43
20
  extend SingleForwardable
44
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
45
26
  def self.shoryuken_options
46
27
  @_shoryuken_options ||= Shoryuken::Options.new
47
28
  end
48
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
49
35
  def self.healthy?
50
36
  Shoryuken::Runner.instance.healthy?
51
37
  end
@@ -57,6 +43,8 @@ module Shoryuken
57
43
  :groups,
58
44
  :add_queue,
59
45
  :ungrouped_queues,
46
+ :thread_priority,
47
+ :thread_priority=,
60
48
  :worker_registry,
61
49
  :worker_registry=,
62
50
  :worker_executor,
@@ -78,6 +66,7 @@ module Shoryuken
78
66
  :exception_handlers=,
79
67
  :options,
80
68
  :logger,
69
+ :logger=,
81
70
  :register_worker,
82
71
  :configure_server,
83
72
  :server?,
@@ -100,7 +89,7 @@ module Shoryuken
100
89
  end
101
90
 
102
91
  if Shoryuken.active_job?
103
- require 'shoryuken/extensions/active_job_extensions'
104
- require 'shoryuken/extensions/active_job_adapter'
105
- 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'
106
95
  end
data/renovate.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ],
6
+ "minimumReleaseAge": "7 days",
7
+ "includePaths": [
8
+ ".ruby-version",
9
+ "Gemfile",
10
+ "Gemfile.lint",
11
+ "shoryuken.gemspec",
12
+ "spec/gemfiles/**",
13
+ "spec/integration/**/Gemfile",
14
+ ".github/workflows/**",
15
+ "docker-compose.yml"
16
+ ],
17
+ "github-actions": {
18
+ "enabled": true,
19
+ "pinDigests": true
20
+ },
21
+ "bundler": {
22
+ "enabled": true,
23
+ "managerFilePatterns": [
24
+ "/(^|/)Gemfile$/",
25
+ "/(^|/)Gemfile\\.lint$/",
26
+ "/\\.gemfile$/",
27
+ "/(^|/)gems\\.rb$/",
28
+ "/spec/gemfiles/.+\\.gemfile$/",
29
+ "/spec/integration/.*/Gemfile$/"
30
+ ]
31
+ },
32
+ "packageRules": [
33
+ {
34
+ "matchManagers": [
35
+ "bundler"
36
+ ],
37
+ "matchFileNames": [
38
+ "spec/gemfiles/**"
39
+ ],
40
+ "groupName": "Rails test dependencies",
41
+ "description": "Group Rails version-specific test Gemfiles together"
42
+ },
43
+ {
44
+ "matchManagers": [
45
+ "bundler"
46
+ ],
47
+ "matchFileNames": [
48
+ "spec/integration/**/Gemfile"
49
+ ],
50
+ "groupName": "Integration test dependencies",
51
+ "description": "Group integration test Gemfiles together"
52
+ },
53
+ {
54
+ "description": "Group ruby/setup-ruby action with ruby version updates",
55
+ "matchPackageNames": [
56
+ "ruby/setup-ruby",
57
+ "ruby"
58
+ ],
59
+ "groupName": "ruby setup"
60
+ }
61
+ ]
62
+ }
data/shoryuken.gemspec CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'shoryuken/version'
@@ -17,11 +16,16 @@ Gem::Specification.new do |spec|
17
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
17
  spec.require_paths = ['lib']
19
18
 
19
+ spec.add_dependency 'aws-sdk-sqs', '>= 1.66.0'
20
+ spec.add_dependency 'concurrent-ruby'
21
+ spec.add_dependency 'thor'
22
+ spec.add_dependency 'zeitwerk', '~> 2.6'
23
+
20
24
  spec.add_development_dependency 'dotenv'
25
+ spec.add_development_dependency 'ostruct'
21
26
  spec.add_development_dependency 'rake'
22
27
  spec.add_development_dependency 'rspec'
28
+ spec.add_development_dependency 'warning'
23
29
 
24
- spec.add_dependency 'aws-sdk-core', '>= 2'
25
- spec.add_dependency 'concurrent-ruby'
26
- spec.add_dependency 'thor'
30
+ spec.required_ruby_version = '>= 3.2.0'
27
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_sqs
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_sqs
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