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,28 +1,61 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
4
+ # Loads and configures the Shoryuken environment from configuration files
5
+ # and command line options. Handles Rails integration and queue setup.
2
6
  class EnvironmentLoader
7
+ # @return [Hash] the configuration options
3
8
  attr_reader :options
4
9
 
10
+ # Sets up a new EnvironmentLoader with the given options
11
+ #
12
+ # @param options [Hash] configuration options
13
+ # @option options [String] :config_file path to the configuration file
14
+ # @option options [Boolean] :rails whether to initialize Rails
15
+ # @option options [String] :logfile path to the log file
16
+ # @option options [Boolean] :verbose whether to enable verbose logging
17
+ # @option options [String] :require path to require workers from
18
+ # @option options [Integer] :concurrency number of concurrent workers
19
+ # @return [Shoryuken::EnvironmentLoader] the configured instance
5
20
  def self.setup_options(options)
6
21
  instance = new(options)
7
22
  instance.setup_options
8
23
  instance
9
24
  end
10
25
 
26
+ # Loads the environment for Rails console usage
27
+ #
28
+ # @return [void]
11
29
  def self.load_for_rails_console
12
30
  instance = setup_options(config_file: (Rails.root + 'config' + 'shoryuken.yml'))
13
31
  instance.load
14
32
  end
15
33
 
34
+ # Initializes a new EnvironmentLoader with the given options
35
+ #
36
+ # @param options [Hash] configuration options
37
+ # @option options [String] :config_file path to the configuration file
38
+ # @option options [Boolean] :rails whether to initialize Rails
39
+ # @option options [String] :logfile path to the log file
40
+ # @option options [Boolean] :verbose whether to enable verbose logging
41
+ # @option options [String] :require path to require workers from
42
+ # @option options [Integer] :concurrency number of concurrent workers
16
43
  def initialize(options)
17
44
  @options = options
18
45
  end
19
46
 
47
+ # Sets up configuration options from file and initializes components
48
+ #
49
+ # @return [void]
20
50
  def setup_options
21
51
  initialize_rails if load_rails?
22
52
  initialize_options
23
53
  initialize_logger
24
54
  end
25
55
 
56
+ # Loads the environment including queues and workers
57
+ #
58
+ # @return [void]
26
59
  def load
27
60
  prefix_active_job_queue_names
28
61
  parse_queues
@@ -33,28 +66,40 @@ module Shoryuken
33
66
 
34
67
  private
35
68
 
69
+ # Merges configuration file options with runtime options
70
+ #
71
+ # @return [void]
36
72
  def initialize_options
37
73
  Shoryuken.options.merge!(config_file_options)
38
74
  Shoryuken.options.merge!(options)
39
75
  end
40
76
 
77
+ # Reads and parses the configuration file
78
+ #
79
+ # @return [Hash] the parsed configuration options
41
80
  def config_file_options
42
81
  return {} unless (path = options[:config_file])
43
82
 
44
- fail ArgumentError, "The supplied config file #{path} does not exist" unless File.exist?(path)
83
+ raise Errors::InvalidConfigurationError, "The supplied config file #{path} does not exist" unless File.exist?(path)
45
84
 
46
85
  if (result = YAML.load(ERB.new(IO.read(path)).result))
47
- result.deep_symbolize_keys
86
+ Shoryuken::Helpers::HashUtils.deep_symbolize_keys(result)
48
87
  else
49
88
  {}
50
89
  end
51
90
  end
52
91
 
92
+ # Initializes the logger with file output and verbosity settings
93
+ #
94
+ # @return [void]
53
95
  def initialize_logger
54
96
  Shoryuken::Logging.initialize_logger(Shoryuken.options[:logfile]) if Shoryuken.options[:logfile]
55
97
  Shoryuken.logger.level = Logger::DEBUG if Shoryuken.options[:verbose]
56
98
  end
57
99
 
100
+ # Initializes the Rails environment
101
+ #
102
+ # @return [void]
58
103
  def initialize_rails
59
104
  # Adapted from: https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/cli.rb
60
105
 
@@ -78,18 +123,26 @@ module Shoryuken
78
123
  end
79
124
  end
80
125
  if Shoryuken.active_job?
81
- require 'shoryuken/extensions/active_job_extensions'
82
- require 'shoryuken/extensions/active_job_adapter'
83
- require 'shoryuken/extensions/active_job_concurrent_send_adapter'
126
+ require 'active_job/extensions'
127
+ require 'active_job/queue_adapters/shoryuken_adapter'
128
+ require 'active_job/queue_adapters/shoryuken_concurrent_send_adapter'
84
129
  end
85
130
  require File.expand_path('config/environment.rb')
86
131
  end
87
132
  end
88
133
 
134
+ # Checks if Rails should be loaded
135
+ #
136
+ # @return [Boolean] true if Rails should be initialized
89
137
  def load_rails?
90
138
  options[:rails]
91
139
  end
92
140
 
141
+ # Prefixes a queue name with ActiveJob queue name prefix
142
+ #
143
+ # @param queue_name [String] the queue name to prefix
144
+ # @param weight [Integer] the queue weight
145
+ # @return [Array<String, Integer>] the prefixed queue name and weight
93
146
  def prefix_active_job_queue_name(queue_name, weight)
94
147
  return [queue_name, weight] if queue_name.start_with?('https://', 'arn:')
95
148
 
@@ -102,6 +155,9 @@ module Shoryuken
102
155
  [prefixed_queue_name, weight]
103
156
  end
104
157
 
158
+ # Prefixes all queue names with ActiveJob prefix if enabled
159
+ #
160
+ # @return [void]
105
161
  def prefix_active_job_queue_names
106
162
  return unless Shoryuken.active_job?
107
163
  return unless Shoryuken.active_job_queue_name_prefixing?
@@ -121,10 +177,19 @@ module Shoryuken
121
177
  end
122
178
  end
123
179
 
180
+ # Parses a single queue and adds it to a group
181
+ #
182
+ # @param queue [String] the queue name
183
+ # @param weight [Integer] the queue weight
184
+ # @param group [String] the group name
185
+ # @return [void]
124
186
  def parse_queue(queue, weight, group)
125
187
  Shoryuken.add_queue(queue, [weight.to_i, 1].max, group)
126
188
  end
127
189
 
190
+ # Parses all queues from configuration and adds them to groups
191
+ #
192
+ # @return [void]
128
193
  def parse_queues
129
194
  if Shoryuken.options[:queues].to_a.any?
130
195
  Shoryuken.add_group('default', Shoryuken.options[:concurrency])
@@ -135,7 +200,7 @@ module Shoryuken
135
200
  end
136
201
 
137
202
  Shoryuken.options[:groups].to_a.each do |group, options|
138
- Shoryuken.add_group(group, options[:concurrency], delay: options[:delay])
203
+ Shoryuken.add_group(group, options[:concurrency], delay: options[:delay], polling_strategy: options[:polling_strategy])
139
204
 
140
205
  options[:queues].to_a.each do |queue, weight|
141
206
  parse_queue(queue, weight, group)
@@ -143,6 +208,9 @@ module Shoryuken
143
208
  end
144
209
  end
145
210
 
211
+ # Requires worker files from the configured path
212
+ #
213
+ # @return [void]
146
214
  def require_workers
147
215
  required = Shoryuken.options[:require]
148
216
 
@@ -155,17 +223,19 @@ module Shoryuken
155
223
  end
156
224
  end
157
225
 
226
+ # Validates that all configured queues exist in SQS
227
+ #
228
+ # @return [void]
229
+ # @raise [ArgumentError] if any queues do not exist
158
230
  def validate_queues
159
231
  return Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.ungrouped_queues.empty?
160
232
 
161
233
  non_existent_queues = []
162
234
 
163
235
  Shoryuken.ungrouped_queues.uniq.each do |queue|
164
- begin
165
- Shoryuken::Client.queues(queue)
166
- rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
167
- non_existent_queues << queue
168
- end
236
+ Shoryuken::Client.queues(queue)
237
+ rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
238
+ non_existent_queues << queue
169
239
  end
170
240
 
171
241
  return if non_existent_queues.none?
@@ -178,12 +248,12 @@ module Shoryuken
178
248
  It's also possible that you don't have permission to access the specified queues.
179
249
  MSG
180
250
 
181
- fail(
182
- ArgumentError,
183
- error_msg
184
- )
251
+ raise Errors::QueueNotFoundError, error_msg
185
252
  end
186
253
 
254
+ # Validates that all queues have registered workers
255
+ #
256
+ # @return [void]
187
257
  def validate_workers
188
258
  return if Shoryuken.active_job?
189
259
 
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ # Namespace for all Shoryuken-specific errors.
5
+ # These provide more meaningful error types than generic Ruby exceptions,
6
+ # making it easier to rescue and handle specific failure cases.
7
+ module Errors
8
+ # Base class for all Shoryuken errors
9
+ BaseError = Class.new(StandardError)
10
+
11
+ # Raised when there is a configuration validation failure
12
+ InvalidConfigurationError = Class.new(BaseError)
13
+
14
+ # Raised when a specified SQS queue does not exist or cannot be accessed
15
+ QueueNotFoundError = Class.new(BaseError)
16
+
17
+ # Raised when worker registration fails due to conflicts
18
+ # (e.g., registering multiple workers for a batch queue)
19
+ InvalidWorkerRegistrationError = Class.new(BaseError)
20
+
21
+ # Raised when an invalid polling strategy is specified
22
+ InvalidPollingStrategyError = Class.new(BaseError)
23
+
24
+ # Raised when an invalid lifecycle event name is used
25
+ InvalidEventError = Class.new(BaseError)
26
+
27
+ # Raised when a delay exceeds the maximum allowed by SQS (15 minutes)
28
+ InvalidDelayError = Class.new(BaseError)
29
+
30
+ # Raised when a delay is used with a FIFO queue
31
+ FifoDelayNotSupportedError = Class.new(BaseError)
32
+
33
+ # Raised when an ARN format is invalid
34
+ InvalidArnError = Class.new(BaseError)
35
+ end
36
+ end
@@ -1,13 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
4
+ # Fetches messages from SQS queues.
5
+ # Handles message retrieval with automatic retry on connectivity errors.
2
6
  class Fetcher
3
7
  include Util
4
8
 
9
+ # Maximum number of messages that can be fetched in a single SQS request
5
10
  FETCH_LIMIT = 10
6
11
 
12
+ # Initializes a new Fetcher for a processing group
13
+ #
14
+ # @param group [String] the processing group name
7
15
  def initialize(group)
8
16
  @group = group
9
17
  end
10
18
 
19
+ # Fetches messages from a queue with automatic retry
20
+ #
21
+ # @param queue [Shoryuken::Polling::QueueConfiguration] the queue configuration
22
+ # @param limit [Integer] the maximum number of messages to fetch
23
+ # @return [Array<Aws::SQS::Types::Message>] the fetched messages
11
24
  def fetch(queue, limit)
12
25
  fetch_with_auto_retry(3) do
13
26
  started_at = Time.now
@@ -25,18 +38,23 @@ module Shoryuken
25
38
 
26
39
  private
27
40
 
41
+ # Fetches with automatic retry on errors
42
+ #
43
+ # @param max_attempts [Integer] maximum number of retry attempts
44
+ # @yield the fetch operation to retry
45
+ # @return [Object] the result of the block
28
46
  def fetch_with_auto_retry(max_attempts)
29
47
  attempts = 0
30
48
 
31
49
  begin
32
50
  yield
33
- rescue => ex
51
+ rescue => e
34
52
  # Tries to auto retry connectivity errors
35
53
  raise if attempts >= max_attempts
36
54
 
37
55
  attempts += 1
38
56
 
39
- logger.debug { "Retrying fetch attempt #{attempts} for #{ex.message}" }
57
+ logger.debug { "Retrying fetch attempt #{attempts} for #{e.message}" }
40
58
 
41
59
  sleep((1..5).to_a.sample)
42
60
 
@@ -44,6 +62,11 @@ module Shoryuken
44
62
  end
45
63
  end
46
64
 
65
+ # Receives messages from an SQS queue
66
+ #
67
+ # @param queue [Shoryuken::Polling::QueueConfiguration] the queue configuration
68
+ # @param limit [Integer] the maximum number of messages to receive
69
+ # @return [Array<Aws::SQS::Types::Message>, nil] the received messages
47
70
  def receive_messages(queue, limit)
48
71
  options = receive_options(queue)
49
72
 
@@ -58,6 +81,13 @@ module Shoryuken
58
81
  shoryuken_queue.receive_messages(options)
59
82
  end
60
83
 
84
+ # Determines the maximum number of messages to fetch
85
+ #
86
+ # @param shoryuken_queue [Shoryuken::Queue] the queue instance
87
+ # @param limit [Integer] the requested limit
88
+ # @param options [Hash] receive options that may contain max_number_of_messages
89
+ # @option options [Integer] :max_number_of_messages optional override for max messages
90
+ # @return [Integer] the maximum number of messages to fetch
61
91
  def max_number_of_messages(shoryuken_queue, limit, options)
62
92
  # For FIFO queues we want to make sure we process one message per group at a time
63
93
  # if we set max_number_of_messages greater than 1,
@@ -73,6 +103,10 @@ module Shoryuken
73
103
  [limit, FETCH_LIMIT, options[:max_number_of_messages]].compact.min
74
104
  end
75
105
 
106
+ # Returns the receive options for a queue
107
+ #
108
+ # @param queue [Shoryuken::Polling::QueueConfiguration] the queue configuration
109
+ # @return [Hash] the receive options hash
76
110
  def receive_options(queue)
77
111
  options = Shoryuken.sqs_client_receive_message_opts[queue.name]
78
112
  options ||= Shoryuken.sqs_client_receive_message_opts[@group]
@@ -80,8 +114,12 @@ module Shoryuken
80
114
  options.to_h.dup
81
115
  end
82
116
 
117
+ # Checks if a queue uses batch message processing
118
+ #
119
+ # @param queue [Shoryuken::Queue] the queue to check
120
+ # @return [Boolean] true if the queue is configured for batch processing
83
121
  def batched_queue?(queue)
84
122
  Shoryuken.worker_registry.batch_receive_messages?(queue.name)
85
123
  end
86
124
  end
87
- end
125
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ # Helper classes providing thread-safe utilities and data structures
5
+ module Helpers
6
+ # A thread-safe boolean implementation using AtomicCounter as base.
7
+ # Drop-in replacement for Concurrent::AtomicBoolean without external dependencies.
8
+ # Uses 1 for true and 0 for false internally.
9
+ class AtomicBoolean < AtomicCounter
10
+ # Prevent misuse of counter operations on a boolean
11
+ undef_method :increment, :decrement
12
+
13
+ # Initializes a new AtomicBoolean
14
+ #
15
+ # @param initial_value [Boolean] the initial value
16
+ def initialize(initial_value = false)
17
+ super(initial_value ? 1 : 0)
18
+ end
19
+
20
+ # Gets the current value as a boolean
21
+ #
22
+ # @return [Boolean] the current value
23
+ def value
24
+ super != 0
25
+ end
26
+
27
+ # Sets the value to true
28
+ #
29
+ # @return [Boolean] true
30
+ def make_true
31
+ @mutex.synchronize { @value = 1 }
32
+ true
33
+ end
34
+
35
+ # Sets the value to false
36
+ #
37
+ # @return [Boolean] false
38
+ def make_false
39
+ @mutex.synchronize { @value = 0 }
40
+ false
41
+ end
42
+
43
+ # Checks if the value is true
44
+ #
45
+ # @return [Boolean] true if the value is true
46
+ def true?
47
+ @mutex.synchronize { @value != 0 }
48
+ end
49
+
50
+ # Checks if the value is false
51
+ #
52
+ # @return [Boolean] true if the value is false
53
+ def false?
54
+ @mutex.synchronize { @value == 0 }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Helpers
5
+ # A thread-safe counter implementation using Ruby's Mutex.
6
+ #
7
+ # This class provides atomic operations for incrementing, decrementing, and reading
8
+ # integer values in a thread-safe manner. It serves as a drop-in replacement for
9
+ # Concurrent::AtomicFixnum without requiring external dependencies.
10
+ #
11
+ # The implementation uses a Mutex to ensure thread safety across all Ruby
12
+ # implementations including JRuby, where true parallelism makes atomic operations
13
+ # critical for data integrity.
14
+ #
15
+ # @note This class is optimized for scenarios with frequent atomic updates
16
+ # and occasional reads, such as tracking active worker counts.
17
+ #
18
+ # @example Basic usage
19
+ # counter = Shoryuken::Helpers::AtomicCounter.new(0)
20
+ # counter.increment # => 1
21
+ # counter.increment # => 2
22
+ # counter.value # => 2
23
+ # counter.decrement # => 1
24
+ #
25
+ # @example Tracking busy processors
26
+ # @busy_processors = Shoryuken::Helpers::AtomicCounter.new(0)
27
+ #
28
+ # # When starting work
29
+ # @busy_processors.increment
30
+ #
31
+ # # When work is done
32
+ # @busy_processors.decrement
33
+ #
34
+ # # Check current load
35
+ # current_busy = @busy_processors.value
36
+ class AtomicCounter
37
+ # Creates a new atomic counter with the specified initial value.
38
+ #
39
+ # @param initial_value [Integer] The starting value for the counter
40
+ # @return [AtomicCounter] A new atomic counter instance
41
+ #
42
+ # @example Create counter starting at zero
43
+ # counter = Shoryuken::Helpers::AtomicCounter.new
44
+ # counter.value # => 0
45
+ #
46
+ # @example Create counter with custom initial value
47
+ # counter = Shoryuken::Helpers::AtomicCounter.new(100)
48
+ # counter.value # => 100
49
+ def initialize(initial_value = 0)
50
+ @mutex = Mutex.new
51
+ @value = initial_value
52
+ end
53
+
54
+ # Returns the current value of the counter.
55
+ #
56
+ # This operation is thread-safe and will return a consistent value
57
+ # even when called concurrently with increment/decrement operations.
58
+ #
59
+ # @return [Integer] The current counter value
60
+ #
61
+ # @example Reading the current value
62
+ # counter = Shoryuken::Helpers::AtomicCounter.new(42)
63
+ # counter.value # => 42
64
+ def value
65
+ @mutex.synchronize { @value }
66
+ end
67
+
68
+ # Atomically increments the counter by 1 and returns the new value.
69
+ #
70
+ # This operation is thread-safe and can be called concurrently from
71
+ # multiple threads without risk of data corruption or lost updates.
72
+ #
73
+ # @return [Integer] The new counter value after incrementing
74
+ #
75
+ # @example Incrementing the counter
76
+ # counter = Shoryuken::Helpers::AtomicCounter.new(5)
77
+ # counter.increment # => 6
78
+ # counter.increment # => 7
79
+ def increment
80
+ @mutex.synchronize { @value += 1 }
81
+ end
82
+
83
+ # Atomically decrements the counter by 1 and returns the new value.
84
+ #
85
+ # This operation is thread-safe and can be called concurrently from
86
+ # multiple threads without risk of data corruption or lost updates.
87
+ # The counter can go negative if decremented below zero.
88
+ #
89
+ # @return [Integer] The new counter value after decrementing
90
+ #
91
+ # @example Decrementing the counter
92
+ # counter = Shoryuken::Helpers::AtomicCounter.new(5)
93
+ # counter.decrement # => 4
94
+ # counter.decrement # => 3
95
+ #
96
+ # @example Counter can go negative
97
+ # counter = Shoryuken::Helpers::AtomicCounter.new(0)
98
+ # counter.decrement # => -1
99
+ def decrement
100
+ @mutex.synchronize { @value -= 1 }
101
+ end
102
+ end
103
+ end
104
+ end