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
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ # Polling strategies for determining which queue to fetch messages from next
5
+ module Polling
6
+ # Abstract base class for queue polling strategies.
7
+ #
8
+ # This class defines the interface that all polling strategies must implement
9
+ # to manage queue selection and message flow control in Shoryuken workers.
10
+ # Polling strategies determine which queue to fetch messages from next and
11
+ # how to handle scenarios where queues have no messages available.
12
+ #
13
+ # @abstract Subclass and override {#next_queue}, {#messages_found}, and {#active_queues}
14
+ # to implement a custom polling strategy.
15
+ #
16
+ # @example Implementing a custom polling strategy
17
+ # class CustomStrategy < BaseStrategy
18
+ # def initialize(queues)
19
+ # @queues = queues
20
+ # end
21
+ #
22
+ # def next_queue
23
+ # # Return next queue to poll
24
+ # @queues.sample
25
+ # end
26
+ #
27
+ # def messages_found(queue, count)
28
+ # # Handle result of polling
29
+ # logger.info "Found #{count} messages in #{queue}"
30
+ # end
31
+ #
32
+ # def active_queues
33
+ # # Return list of active queues
34
+ # @queues
35
+ # end
36
+ # end
37
+ #
38
+ # @see WeightedRoundRobin
39
+ # @see StrictPriority
40
+ class BaseStrategy
41
+ include Util
42
+
43
+ # Returns the next queue to poll for messages.
44
+ #
45
+ # This method should return a QueueConfiguration object representing
46
+ # the next queue that should be polled for messages, or nil if no
47
+ # queues are currently available for polling.
48
+ #
49
+ # @abstract Subclasses must implement this method
50
+ # @return [QueueConfiguration, nil] Next queue to poll, or nil if none available
51
+ # @raise [NotImplementedError] if not implemented by subclass
52
+ def next_queue
53
+ fail NotImplementedError
54
+ end
55
+
56
+ # Called when messages are found (or not found) in a queue.
57
+ #
58
+ # This method is invoked after polling a queue to inform the strategy
59
+ # about the number of messages that were retrieved. Strategies can use
60
+ # this information to make decisions about future polling behavior,
61
+ # such as pausing empty queues or adjusting queue weights.
62
+ #
63
+ # @abstract Subclasses must implement this method
64
+ # @param _queue [String] The name of the queue that was polled
65
+ # @param _messages_found [Integer] The number of messages retrieved
66
+ # @raise [NotImplementedError] if not implemented by subclass
67
+ def messages_found(_queue, _messages_found)
68
+ fail NotImplementedError
69
+ end
70
+
71
+ # Called when a message from a queue has been processed.
72
+ #
73
+ # This optional callback is invoked after a message has been successfully
74
+ # processed by a worker. Strategies can use this information for cleanup
75
+ # or to adjust their polling behavior.
76
+ #
77
+ # @param _queue [String] The name of the queue whose message was processed
78
+ # @return [void]
79
+ def message_processed(_queue); end
80
+
81
+ # Returns the list of currently active queues.
82
+ #
83
+ # This method should return an array representing the queues that are
84
+ # currently active and available for polling. The format may vary by
85
+ # strategy implementation.
86
+ #
87
+ # @abstract Subclasses must implement this method
88
+ # @return [Array] List of active queues
89
+ # @raise [NotImplementedError] if not implemented by subclass
90
+ def active_queues
91
+ fail NotImplementedError
92
+ end
93
+
94
+ # Compares this strategy with another object for equality.
95
+ #
96
+ # Two strategies are considered equal if they have the same active queues.
97
+ # This method also supports comparison with Array objects for backward
98
+ # compatibility.
99
+ #
100
+ # @param other [Object] Object to compare with
101
+ # @return [Boolean] true if strategies are equivalent
102
+ def ==(other)
103
+ case other
104
+ when Array
105
+ @queues == other
106
+ else
107
+ if other.respond_to?(:active_queues)
108
+ active_queues == other.active_queues
109
+ else
110
+ false
111
+ end
112
+ end
113
+ end
114
+
115
+ # Returns the delay time for pausing empty queues.
116
+ #
117
+ # This method returns the amount of time (in seconds) that empty queues
118
+ # should be paused before being polled again. The delay can be set at
119
+ # the strategy level or falls back to the global Shoryuken delay setting.
120
+ #
121
+ # @return [Float] Delay time in seconds
122
+ def delay
123
+ @delay || Shoryuken.options[:delay].to_f
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Polling
5
+ # Configuration object representing a queue and its associated options.
6
+ #
7
+ # This class encapsulates a queue name along with any polling-specific
8
+ # options or metadata. It provides a structured way to pass queue
9
+ # information between polling strategies and the message fetching system.
10
+ #
11
+ # The class extends Struct to provide attribute accessors for name and options
12
+ # while adding custom behavior for equality comparison and string representation.
13
+ #
14
+ # @example Creating a basic queue configuration
15
+ # config = QueueConfiguration.new('my_queue', {})
16
+ # config.name # => 'my_queue'
17
+ # config.options # => {}
18
+ #
19
+ # @example Creating a queue configuration with options
20
+ # config = QueueConfiguration.new('priority_queue', { priority: :high })
21
+ # config.name # => 'priority_queue'
22
+ # config.options # => { priority: :high }
23
+ #
24
+ # @example Comparing configurations
25
+ # config1 = QueueConfiguration.new('queue', {})
26
+ # config2 = QueueConfiguration.new('queue', {})
27
+ # config1 == config2 # => true
28
+ # config1 == 'queue' # => true (when options are empty)
29
+ #
30
+ # @attr_reader [String] name The name of the queue
31
+ # @attr_reader [Hash] options Additional options or metadata for the queue
32
+ QueueConfiguration = Struct.new(:name, :options) do
33
+ # Generates a hash value based on the queue name.
34
+ #
35
+ # This method ensures that QueueConfiguration objects can be used
36
+ # as hash keys and that configurations with the same queue name
37
+ # will have the same hash value regardless of their options.
38
+ #
39
+ # @return [Integer] Hash value based on the queue name
40
+ def hash
41
+ name.hash
42
+ end
43
+
44
+ # Compares this configuration with another object for equality.
45
+ #
46
+ # Two QueueConfiguration objects are equal if they have the same name
47
+ # and options. For convenience, a configuration with empty options can
48
+ # also be compared directly with a string queue name.
49
+ #
50
+ # @param other [Object] The object to compare with
51
+ # @return [Boolean] true if the objects are considered equal
52
+ #
53
+ # @example Comparing with another QueueConfiguration
54
+ # config1 = QueueConfiguration.new('queue', {})
55
+ # config2 = QueueConfiguration.new('queue', {})
56
+ # config1 == config2 # => true
57
+ #
58
+ # @example Comparing with a string (only when options are empty)
59
+ # config = QueueConfiguration.new('queue', {})
60
+ # config == 'queue' # => true
61
+ #
62
+ # config_with_options = QueueConfiguration.new('queue', { weight: 5 })
63
+ # config_with_options == 'queue' # => false
64
+ def ==(other)
65
+ case other
66
+ when String
67
+ if options.empty?
68
+ name == other
69
+ else
70
+ false
71
+ end
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ alias_method :eql?, :==
78
+
79
+ # Returns a string representation of the queue configuration.
80
+ #
81
+ # For configurations with empty options, returns just the queue name.
82
+ # For configurations with options, returns a detailed representation
83
+ # showing both the name and the options hash.
84
+ #
85
+ # @return [String] String representation of the configuration
86
+ #
87
+ # @example Simple queue without options
88
+ # config = QueueConfiguration.new('simple_queue', {})
89
+ # config.to_s # => 'simple_queue'
90
+ #
91
+ # @example Queue with options
92
+ # config = QueueConfiguration.new('complex_queue', { priority: :high })
93
+ # config.to_s # => '#<QueueConfiguration complex_queue options={:priority=>:high}>'
94
+ def to_s
95
+ if options&.empty?
96
+ name
97
+ else
98
+ "#<QueueConfiguration #{name} options=#{options.inspect}>"
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -1,6 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Polling
5
+ # A polling strategy that processes queues in strict priority order.
6
+ # Higher priority queues are always processed before lower priority queues.
7
+ # Queues are temporarily paused when no messages are found.
3
8
  class StrictPriority < BaseStrategy
9
+ # Initializes a new StrictPriority polling strategy
10
+ #
11
+ # @param queues [Array<String>] array of queue names, with higher priority queues appearing more frequently
12
+ # @param delay [Float, nil] delay in seconds before unpausing empty queues
4
13
  def initialize(queues, delay = nil)
5
14
  # Priority ordering of the queues, highest priority first
6
15
  @queues = queues
@@ -17,11 +26,19 @@ module Shoryuken
17
26
  reset_next_queue
18
27
  end
19
28
 
29
+ # Returns the next queue to poll based on strict priority
30
+ #
31
+ # @return [QueueConfiguration, nil] the next queue configuration or nil if all paused
20
32
  def next_queue
21
33
  next_queue = next_active_queue
22
34
  next_queue.nil? ? nil : QueueConfiguration.new(next_queue, {})
23
35
  end
24
36
 
37
+ # Handles the result of polling a queue
38
+ #
39
+ # @param queue [String] the queue name
40
+ # @param messages_found [Integer] number of messages found
41
+ # @return [void]
25
42
  def messages_found(queue, messages_found)
26
43
  if messages_found == 0
27
44
  pause(queue)
@@ -30,6 +47,9 @@ module Shoryuken
30
47
  end
31
48
  end
32
49
 
50
+ # Returns the list of active (non-paused) queues with their priorities
51
+ #
52
+ # @return [Array<Array>] array of [queue_name, priority] pairs
33
53
  def active_queues
34
54
  @queues
35
55
  .reverse
@@ -38,6 +58,10 @@ module Shoryuken
38
58
  .reverse
39
59
  end
40
60
 
61
+ # Called when a message from a queue has been processed
62
+ #
63
+ # @param queue [String] the queue name
64
+ # @return [void]
41
65
  def message_processed(queue)
42
66
  if queue_paused?(queue)
43
67
  logger.debug "Unpausing #{queue}"
@@ -47,6 +71,9 @@ module Shoryuken
47
71
 
48
72
  private
49
73
 
74
+ # Finds the next active (non-paused) queue
75
+ #
76
+ # @return [String, nil] the queue name or nil if all paused
50
77
  def next_active_queue
51
78
  reset_next_queue if queues_unpaused_since?
52
79
 
@@ -60,6 +87,9 @@ module Shoryuken
60
87
  nil
61
88
  end
62
89
 
90
+ # Checks if any queues have been unpaused since last check
91
+ #
92
+ # @return [Boolean] true if queues were unpaused
63
93
  def queues_unpaused_since?
64
94
  last = @last_unpause_check
65
95
  now = @last_unpause_check = Time.now
@@ -67,14 +97,25 @@ module Shoryuken
67
97
  last && @paused_until.values.any? { |t| t > last && t <= now }
68
98
  end
69
99
 
100
+ # Resets the next queue index to start from the highest priority
101
+ #
102
+ # @return [void]
70
103
  def reset_next_queue
71
104
  @next_queue_index = 0
72
105
  end
73
106
 
107
+ # Checks if a queue is currently paused
108
+ #
109
+ # @param queue [String] the queue name
110
+ # @return [Boolean] true if the queue is paused
74
111
  def queue_paused?(queue)
75
112
  @paused_until[queue] > Time.now
76
113
  end
77
114
 
115
+ # Pauses a queue for the configured delay time
116
+ #
117
+ # @param queue [String] the queue name to pause
118
+ # @return [void]
78
119
  def pause(queue)
79
120
  return unless delay > 0
80
121
 
@@ -1,6 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Polling
5
+ # A polling strategy that processes queues in weighted round-robin order.
6
+ # Queue weights determine how often each queue is polled relative to others.
7
+ # Queues are temporarily paused when no messages are found.
3
8
  class WeightedRoundRobin < BaseStrategy
9
+ # Initializes a new WeightedRoundRobin polling strategy
10
+ #
11
+ # @param queues [Array<String>] array of queue names, with weights indicated by repetition
12
+ # @param delay [Float, nil] delay in seconds before unpausing empty queues
4
13
  def initialize(queues, delay = nil)
5
14
  @initial_queues = queues
6
15
  @queues = queues.dup.uniq
@@ -8,6 +17,9 @@ module Shoryuken
8
17
  @delay = delay
9
18
  end
10
19
 
20
+ # Returns the next queue to poll in round-robin order
21
+ #
22
+ # @return [QueueConfiguration, nil] the next queue configuration or nil if all paused
11
23
  def next_queue
12
24
  unpause_queues
13
25
  queue = @queues.shift
@@ -17,6 +29,11 @@ module Shoryuken
17
29
  QueueConfiguration.new(queue, {})
18
30
  end
19
31
 
32
+ # Handles the result of polling a queue, adjusting weight if messages were found
33
+ #
34
+ # @param queue [String] the queue name
35
+ # @param messages_found [Integer] number of messages found
36
+ # @return [void]
20
37
  def messages_found(queue, messages_found)
21
38
  if messages_found == 0
22
39
  pause(queue)
@@ -31,10 +48,17 @@ module Shoryuken
31
48
  end
32
49
  end
33
50
 
51
+ # Returns the list of active queues with their current weights
52
+ #
53
+ # @return [Array<Array>] array of [queue_name, weight] pairs
34
54
  def active_queues
35
55
  unparse_queues(@queues)
36
56
  end
37
57
 
58
+ # Called when a message from a queue has been processed
59
+ #
60
+ # @param queue [String] the queue name
61
+ # @return [void]
38
62
  def message_processed(queue)
39
63
  paused_queue = @paused_queues.find { |_time, name| name == queue }
40
64
  return unless paused_queue
@@ -44,6 +68,10 @@ module Shoryuken
44
68
 
45
69
  private
46
70
 
71
+ # Pauses a queue by removing it from active rotation
72
+ #
73
+ # @param queue [String] the queue name to pause
74
+ # @return [void]
47
75
  def pause(queue)
48
76
  return unless @queues.delete(queue)
49
77
 
@@ -51,6 +79,9 @@ module Shoryuken
51
79
  logger.debug "Paused #{queue}"
52
80
  end
53
81
 
82
+ # Unpauses queues whose delay has expired
83
+ #
84
+ # @return [void]
54
85
  def unpause_queues
55
86
  return if @paused_queues.empty?
56
87
  return if Time.now < @paused_queues.first[0]
@@ -60,14 +91,27 @@ module Shoryuken
60
91
  logger.debug "Unpaused #{pause[1]}"
61
92
  end
62
93
 
94
+ # Returns the current weight of a queue in the active rotation
95
+ #
96
+ # @param queue [String] the queue name
97
+ # @return [Integer] the current weight
63
98
  def current_queue_weight(queue)
64
99
  queue_weight(@queues, queue)
65
100
  end
66
101
 
102
+ # Returns the maximum configured weight of a queue
103
+ #
104
+ # @param queue [String] the queue name
105
+ # @return [Integer] the maximum weight
67
106
  def maximum_queue_weight(queue)
68
107
  queue_weight(@initial_queues, queue)
69
108
  end
70
109
 
110
+ # Counts how many times a queue appears in the given array
111
+ #
112
+ # @param queues [Array<String>] the array to count in
113
+ # @param queue [String] the queue name to count
114
+ # @return [Integer] the count
71
115
  def queue_weight(queues, queue)
72
116
  queues.count { |q| q == queue }
73
117
  end
@@ -1,21 +1,42 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
4
+ # Processes SQS messages by invoking the appropriate worker.
5
+ # Handles middleware chain execution and exception handling.
2
6
  class Processor
3
7
  include Util
4
8
 
5
- attr_reader :queue, :sqs_msg
9
+ # @return [String] the queue name
10
+ attr_reader :queue
11
+
12
+ # @return [Aws::SQS::Types::Message, Array<Aws::SQS::Types::Message>] the message or batch
13
+ attr_reader :sqs_msg
6
14
 
15
+ # Processes a message from a queue
16
+ #
17
+ # @param queue [String] the queue name
18
+ # @param sqs_msg [Aws::SQS::Types::Message, Array<Aws::SQS::Types::Message>] the message or batch
19
+ # @return [Object] the result of the worker's perform method
7
20
  def self.process(queue, sqs_msg)
8
21
  new(queue, sqs_msg).process
9
22
  end
10
23
 
24
+ # Initializes a new Processor
25
+ #
26
+ # @param queue [String] the queue name
27
+ # @param sqs_msg [Aws::SQS::Types::Message, Array<Aws::SQS::Types::Message>] the message or batch
11
28
  def initialize(queue, sqs_msg)
12
29
  @queue = queue
13
30
  @sqs_msg = sqs_msg
14
31
  end
15
32
 
33
+ # Processes the message through the middleware chain and worker
34
+ #
35
+ # @return [Object] the result of the worker's perform method
16
36
  def process
17
37
  worker_perform = proc do
18
38
  return logger.error { "No worker found for #{queue}" } unless worker
39
+
19
40
  Shoryuken::Logging.with_context("#{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id}") do
20
41
  worker.class.server_middleware.invoke(worker, queue, sqs_msg, body) do
21
42
  worker.perform(sqs_msg, body)
@@ -30,26 +51,39 @@ module Shoryuken
30
51
  else
31
52
  worker_perform.call
32
53
  end
33
- rescue Exception => ex
34
- Array(Shoryuken.exception_handlers).each { |handler| handler.call(ex, queue, sqs_msg) }
54
+ rescue Exception => e
55
+ Array(Shoryuken.exception_handlers).each { |handler| handler.call(e, queue, sqs_msg) }
35
56
 
36
57
  raise
37
58
  end
38
59
 
39
60
  private
40
61
 
62
+ # Fetches the worker instance for processing
63
+ #
64
+ # @return [Object, nil] the worker instance or nil if not found
41
65
  def worker
42
66
  @_worker ||= Shoryuken.worker_registry.fetch_worker(queue, sqs_msg)
43
67
  end
44
68
 
69
+ # Returns the worker class
70
+ #
71
+ # @return [Class] the worker class
45
72
  def worker_class
46
73
  worker.class
47
74
  end
48
75
 
76
+ # Parses the message body or bodies for batch processing
77
+ #
78
+ # @return [Object, Array<Object>] the parsed body or array of bodies
49
79
  def body
50
80
  @_body ||= sqs_msg.is_a?(Array) ? sqs_msg.map(&method(:parse_body)) : parse_body(sqs_msg)
51
81
  end
52
82
 
83
+ # Parses a single message body
84
+ #
85
+ # @param sqs_msg [Aws::SQS::Types::Message] the message to parse
86
+ # @return [Object] the parsed message body
53
87
  def parse_body(sqs_msg)
54
88
  BodyParser.parse(worker_class, sqs_msg)
55
89
  end