shoryuken 7.0.0.alpha2 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +2 -2
  3. data/.github/workflows/specs.yml +38 -43
  4. data/.github/workflows/verify-action-pins.yml +1 -1
  5. data/.gitignore +3 -0
  6. data/.rspec +1 -0
  7. data/.ruby-version +1 -1
  8. data/.yard-lint.yml +279 -0
  9. data/CHANGELOG.md +69 -1
  10. data/Gemfile +1 -1
  11. data/README.md +2 -7
  12. data/Rakefile +4 -10
  13. data/bin/clean_localstack +52 -0
  14. data/bin/cli/base.rb +21 -0
  15. data/bin/cli/sqs.rb +61 -2
  16. data/bin/integrations +275 -0
  17. data/bin/scenario +154 -0
  18. data/bin/shoryuken +1 -1
  19. data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +15 -4
  20. data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
  21. data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
  22. data/lib/shoryuken/active_job/current_attributes.rb +139 -0
  23. data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
  24. data/lib/shoryuken/body_parser.rb +8 -0
  25. data/lib/shoryuken/client.rb +14 -0
  26. data/lib/shoryuken/default_exception_handler.rb +9 -0
  27. data/lib/shoryuken/default_worker_registry.rb +29 -1
  28. data/lib/shoryuken/environment_loader.rb +78 -8
  29. data/lib/shoryuken/errors.rb +33 -0
  30. data/lib/shoryuken/fetcher.rb +37 -1
  31. data/lib/shoryuken/helpers/atomic_boolean.rb +19 -5
  32. data/lib/shoryuken/helpers/timer_task.rb +80 -0
  33. data/lib/shoryuken/launcher.rb +53 -0
  34. data/lib/shoryuken/logging/base.rb +26 -0
  35. data/lib/shoryuken/logging/pretty.rb +25 -0
  36. data/lib/shoryuken/logging/without_timestamp.rb +25 -0
  37. data/lib/shoryuken/logging.rb +39 -25
  38. data/lib/shoryuken/manager.rb +70 -1
  39. data/lib/shoryuken/message.rb +114 -1
  40. data/lib/shoryuken/middleware/chain.rb +139 -43
  41. data/lib/shoryuken/middleware/entry.rb +30 -0
  42. data/lib/shoryuken/middleware/server/active_record.rb +8 -0
  43. data/lib/shoryuken/middleware/server/auto_delete.rb +10 -0
  44. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +27 -1
  45. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +29 -0
  46. data/lib/shoryuken/middleware/server/timing.rb +11 -0
  47. data/lib/shoryuken/options.rb +129 -6
  48. data/lib/shoryuken/polling/base_strategy.rb +1 -0
  49. data/lib/shoryuken/polling/strict_priority.rb +39 -0
  50. data/lib/shoryuken/polling/weighted_round_robin.rb +42 -0
  51. data/lib/shoryuken/processor.rb +32 -1
  52. data/lib/shoryuken/queue.rb +93 -4
  53. data/lib/shoryuken/runner.rb +45 -4
  54. data/lib/shoryuken/util.rb +26 -1
  55. data/lib/shoryuken/version.rb +2 -1
  56. data/lib/shoryuken/worker/default_executor.rb +21 -1
  57. data/lib/shoryuken/worker/inline_executor.rb +24 -0
  58. data/lib/shoryuken/worker.rb +193 -0
  59. data/lib/shoryuken/worker_registry.rb +33 -0
  60. data/lib/shoryuken.rb +18 -6
  61. data/renovate.json +29 -2
  62. data/shoryuken.gemspec +2 -1
  63. data/spec/integration/.rspec +1 -0
  64. data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
  65. data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
  66. data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
  67. data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
  68. data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
  69. data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
  70. data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
  71. data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
  72. data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
  73. data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
  74. data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
  75. data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
  76. data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
  77. data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
  78. data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
  79. data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
  80. data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
  81. data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
  82. data/spec/integration/aws_config/aws_config_spec.rb +59 -0
  83. data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
  84. data/spec/integration/body_parser/json_parser_spec.rb +45 -0
  85. data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
  86. data/spec/integration/body_parser/text_parser_spec.rb +43 -0
  87. data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
  88. data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
  89. data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
  90. data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
  91. data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
  92. data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
  93. data/spec/integration/launcher/launcher_spec.rb +40 -0
  94. data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
  95. data/spec/integration/message_operations/message_operations_spec.rb +59 -0
  96. data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
  97. data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
  98. data/spec/integration/middleware_chain/removal_spec.rb +31 -0
  99. data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
  100. data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
  101. data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
  102. data/spec/integration/rails/rails_72/Gemfile +6 -0
  103. data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
  104. data/spec/integration/rails/rails_80/Gemfile +6 -0
  105. data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
  106. data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
  107. data/spec/integration/rails/rails_81/Gemfile +6 -0
  108. data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
  109. data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
  110. data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
  111. data/spec/integration/spec_helper.rb +7 -0
  112. data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
  113. data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
  114. data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
  115. data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
  116. data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
  117. data/spec/integrations_helper.rb +243 -0
  118. data/spec/lib/active_job/extensions_spec.rb +149 -0
  119. data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
  120. data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +3 -3
  121. data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +4 -4
  122. data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +1 -1
  123. data/spec/{shoryuken → lib/shoryuken}/helpers/hash_utils_spec.rb +14 -14
  124. data/spec/{shoryuken → lib/shoryuken}/helpers/string_utils_spec.rb +3 -3
  125. data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
  126. data/spec/{shoryuken → lib/shoryuken}/helpers_integration_spec.rb +9 -9
  127. data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +22 -0
  128. data/spec/lib/shoryuken/logging_spec.rb +242 -0
  129. data/spec/lib/shoryuken/message_spec.rb +109 -0
  130. data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
  131. data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
  132. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +50 -0
  133. data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +2 -2
  134. data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +1 -1
  135. data/spec/lib/shoryuken/version_spec.rb +17 -0
  136. data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
  137. data/spec/shared_examples_for_active_job.rb +29 -9
  138. data/spec/spec_helper.rb +34 -3
  139. metadata +230 -91
  140. data/.devcontainer/Dockerfile +0 -17
  141. data/.devcontainer/base.Dockerfile +0 -43
  142. data/.devcontainer/devcontainer.json +0 -35
  143. data/Appraisals +0 -23
  144. data/gemfiles/.gitignore +0 -1
  145. data/gemfiles/rails_7_0.gemfile +0 -19
  146. data/gemfiles/rails_7_1.gemfile +0 -19
  147. data/gemfiles/rails_7_2.gemfile +0 -19
  148. data/gemfiles/rails_8_0.gemfile +0 -19
  149. data/lib/shoryuken/extensions/active_job_adapter.rb +0 -110
  150. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
  151. data/spec/integration/launcher_spec.rb +0 -127
  152. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -8
  153. data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -85
  154. /data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +0 -0
  155. /data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +0 -0
  156. /data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +0 -0
  157. /data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +0 -0
  158. /data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +0 -0
  159. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_boolean_spec.rb +0 -0
  160. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_counter_spec.rb +0 -0
  161. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_hash_spec.rb +0 -0
  162. /data/spec/{shoryuken → lib/shoryuken}/inline_message_spec.rb +0 -0
  163. /data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +0 -0
  164. /data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +0 -0
  165. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +0 -0
  166. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +0 -0
  167. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +0 -0
  168. /data/spec/{shoryuken → lib/shoryuken}/polling/base_strategy_spec.rb +0 -0
  169. /data/spec/{shoryuken → lib/shoryuken}/polling/queue_configuration_spec.rb +0 -0
  170. /data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +0 -0
  171. /data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +0 -0
  172. /data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +0 -0
  173. /data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +0 -0
  174. /data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +0 -0
  175. /data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +0 -0
  176. /data/spec/{shoryuken → lib/shoryuken}/worker/inline_executor_spec.rb +0 -0
  177. /data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +0 -0
  178. /data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +0 -0
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Middleware
5
+ # Represents an entry in a middleware chain, storing the middleware class
6
+ # and any arguments needed for its instantiation.
7
+ #
8
+ # @api private
9
+ class Entry
10
+ # @return [Class] The middleware class this entry represents
11
+ attr_reader :klass
12
+
13
+ # Creates a new middleware entry.
14
+ #
15
+ # @param klass [Class] The middleware class
16
+ # @param args [Array] Arguments to pass to the middleware constructor
17
+ def initialize(klass, *args)
18
+ @klass = klass
19
+ @args = args
20
+ end
21
+
22
+ # Creates a new instance of the middleware class with the stored arguments.
23
+ #
24
+ # @return [Object] A new instance of the middleware class
25
+ def make_new
26
+ @klass.new(*@args)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,8 +2,16 @@
2
2
 
3
3
  module Shoryuken
4
4
  module Middleware
5
+ # Server-side middleware that runs around message processing
5
6
  module Server
7
+ # Middleware that clears ActiveRecord connections after message processing.
8
+ # Ensures database connections are returned to the pool after each job.
6
9
  class ActiveRecord
10
+ # Processes a message and clears database connections afterwards
11
+ #
12
+ # @param _args [Array<Object>] middleware call arguments (unused)
13
+ # @yield continues to the next middleware in the chain
14
+ # @return [Object] return value from the next middleware or worker in the chain
7
15
  def call(*_args)
8
16
  yield
9
17
  ensure
@@ -3,7 +3,17 @@
3
3
  module Shoryuken
4
4
  module Middleware
5
5
  module Server
6
+ # Middleware that automatically deletes messages after successful processing.
7
+ # Only deletes messages when the worker has auto_delete enabled.
6
8
  class AutoDelete
9
+ # Processes a message and deletes it if auto_delete is enabled
10
+ #
11
+ # @param worker [Object] the worker instance
12
+ # @param queue [String] the queue name
13
+ # @param sqs_msg [Shoryuken::Message, Array<Shoryuken::Message>] the message or batch
14
+ # @param _body [Object] the parsed message body (unused)
15
+ # @yield continues to the next middleware in the chain
16
+ # @return [void]
7
17
  def call(worker, queue, sqs_msg, _body)
8
18
  yield
9
19
 
@@ -3,11 +3,22 @@
3
3
  module Shoryuken
4
4
  module Middleware
5
5
  module Server
6
+ # Middleware that automatically extends message visibility timeout during processing.
7
+ # Prevents messages from becoming visible to other consumers while still being processed.
6
8
  class AutoExtendVisibility
7
9
  include Util
8
10
 
11
+ # Number of seconds before timeout to extend visibility
9
12
  EXTEND_UPFRONT_SECONDS = 5
10
13
 
14
+ # Processes a message with automatic visibility timeout extension
15
+ #
16
+ # @param worker [Object] the worker instance
17
+ # @param queue [String] the queue name
18
+ # @param sqs_msg [Shoryuken::Message, Array<Shoryuken::Message>] the message or batch
19
+ # @param body [Object] the parsed message body
20
+ # @yield continues to the next middleware in the chain
21
+ # @return [void]
11
22
  def call(worker, queue, sqs_msg, body)
12
23
  return yield unless worker.class.auto_visibility_timeout?
13
24
 
@@ -24,13 +35,21 @@ module Shoryuken
24
35
 
25
36
  private
26
37
 
38
+ # Helper class for extending message visibility
27
39
  class MessageVisibilityExtender
28
40
  include Util
29
41
 
42
+ # Creates a timer task that extends message visibility
43
+ #
44
+ # @param _worker [Object] the worker instance (unused)
45
+ # @param queue [String] the queue name
46
+ # @param sqs_msg [Shoryuken::Message] the message
47
+ # @param _body [Object] the parsed message body (unused)
48
+ # @return [Shoryuken::Helpers::TimerTask] the timer task
30
49
  def auto_extend(_worker, queue, sqs_msg, _body)
31
50
  queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
32
51
 
33
- Concurrent::TimerTask.new(execution_interval: queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
52
+ Shoryuken::Helpers::TimerTask.new(execution_interval: queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
34
53
  logger.debug do
35
54
  "Extending message #{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s"
36
55
  end
@@ -44,6 +63,13 @@ module Shoryuken
44
63
  end
45
64
  end
46
65
 
66
+ # Creates and starts a visibility extension timer
67
+ #
68
+ # @param worker [Object] the worker instance
69
+ # @param queue [String] the queue name
70
+ # @param sqs_msg [Shoryuken::Message] the message
71
+ # @param body [Object] the parsed message body
72
+ # @return [Shoryuken::Helpers::TimerTask] the started timer
47
73
  def auto_visibility_timer(worker, queue, sqs_msg, body)
48
74
  MessageVisibilityExtender.new.auto_extend(worker, queue, sqs_msg, body).tap(&:execute)
49
75
  end
@@ -3,9 +3,22 @@
3
3
  module Shoryuken
4
4
  module Middleware
5
5
  module Server
6
+ # Middleware that implements exponential backoff retry for failed messages.
7
+ # When a job fails, the message visibility timeout is adjusted based on
8
+ # configured retry intervals.
6
9
  class ExponentialBackoffRetry
7
10
  include Util
8
11
 
12
+ # Processes a message with exponential backoff retry on failure
13
+ #
14
+ # @param worker [Object] the worker instance
15
+ # @param _queue [String] the queue name (unused)
16
+ # @param sqs_msg [Shoryuken::Message, Array<Shoryuken::Message>] the message or batch
17
+ # @param _body [Object] the parsed message body (unused)
18
+ # @yield continues to the next middleware in the chain
19
+ # @return [void]
20
+ # @raise [StandardError] re-raises the original exception if retry intervals are not configured
21
+ # or if retry limit is exceeded
9
22
  def call(worker, _queue, sqs_msg, _body)
10
23
  return yield unless worker.class.exponential_backoff?
11
24
 
@@ -32,6 +45,11 @@ module Shoryuken
32
45
 
33
46
  private
34
47
 
48
+ # Gets the retry interval for a given attempt number
49
+ #
50
+ # @param retry_intervals [Array<Integer>, #call] the configured intervals or callable
51
+ # @param attempts [Integer] the current attempt number
52
+ # @return [Integer, nil] the interval in seconds or nil
35
53
  def get_interval(retry_intervals, attempts)
36
54
  return retry_intervals.call(attempts) if retry_intervals.respond_to?(:call)
37
55
 
@@ -42,12 +60,23 @@ module Shoryuken
42
60
  end
43
61
  end
44
62
 
63
+ # Calculates the next visibility timeout capped at SQS maximum
64
+ #
65
+ # @param interval [Integer] the desired interval
66
+ # @param started_at [Time] when processing started
67
+ # @return [Integer] the capped visibility timeout
45
68
  def next_visibility_timeout(interval, started_at)
46
69
  max_timeout = 43_200 - (Time.now - started_at).ceil - 1
47
70
  interval = max_timeout if interval > max_timeout
48
71
  interval.to_i
49
72
  end
50
73
 
74
+ # Handles a message failure by adjusting visibility timeout
75
+ #
76
+ # @param sqs_msg [Shoryuken::Message] the failed message
77
+ # @param started_at [Time] when processing started
78
+ # @param retry_intervals [Array<Integer>, #call] the configured intervals
79
+ # @return [Boolean] true if retry was scheduled, false otherwise
51
80
  def handle_failure(sqs_msg, started_at, retry_intervals)
52
81
  receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
53
82
 
@@ -3,9 +3,20 @@
3
3
  module Shoryuken
4
4
  module Middleware
5
5
  module Server
6
+ # Middleware that logs timing information for message processing.
7
+ # Records start time, completion time, and warns if processing
8
+ # exceeds the queue's visibility timeout.
6
9
  class Timing
7
10
  include Util
8
11
 
12
+ # Processes a message while logging timing information
13
+ #
14
+ # @param _worker [Object] the worker instance (unused)
15
+ # @param queue [String] the queue name
16
+ # @param _sqs_msg [Shoryuken::Message] the message being processed (unused)
17
+ # @param _body [Object] the parsed message body (unused)
18
+ # @yield continues to the next middleware in the chain
19
+ # @return [void]
9
20
  def call(_worker, queue, _sqs_msg, _body)
10
21
  started_at = Time.now
11
22
 
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shoryuken
4
+ # Stores and manages all Shoryuken configuration options.
5
+ # This class is used internally to hold settings for workers, queues,
6
+ # middleware, and other runtime configurations.
4
7
  class Options
8
+ # Default configuration values for Shoryuken
5
9
  DEFAULTS = {
6
10
  thread_priority: -1,
7
11
  concurrency: 25,
@@ -19,14 +23,31 @@ module Shoryuken
19
23
  }
20
24
  }.freeze
21
25
 
26
+ # @return [Boolean] whether to enable ActiveJob queue name prefixing
27
+ # @return [Boolean] whether to cache SQS visibility timeout
28
+ # @return [Hash{String => Hash}] the configured processing groups
29
+ # @return [Object] the executor used to launch workers
30
+ # @return [Proc] the code reloader proc for development environments
31
+ # @return [Boolean] whether code reloading is enabled
32
+ # @return [Proc, nil] callback to execute when server starts
33
+ # @return [Proc, nil] callback to execute when server stops
34
+ # @return [Class] the executor class for running workers
35
+ # @return [Shoryuken::WorkerRegistry] the registry for worker classes
36
+ # @return [Array<#call>] handlers for processing exceptions
22
37
  attr_accessor :active_job_queue_name_prefixing, :cache_visibility_timeout,
23
38
  :groups, :launcher_executor, :reloader, :enable_reloading,
24
39
  :start_callback, :stop_callback, :worker_executor, :worker_registry,
25
40
  :exception_handlers
26
41
 
42
+ # @return [Hash] the default options for workers
43
+ # @return [Aws::SQS::Client] the SQS client instance
44
+ # @return [Logger] the logger instance
27
45
  attr_writer :default_worker_options, :sqs_client, :logger
46
+
47
+ # @return [Hash] options passed to SQS receive_message calls
28
48
  attr_reader :sqs_client_receive_message_opts
29
49
 
50
+ # Initializes a new Options instance with default values
30
51
  def initialize
31
52
  self.groups = {}
32
53
  self.worker_registry = DefaultWorkerRegistry.new
@@ -40,10 +61,19 @@ module Shoryuken
40
61
  @sqs_client_receive_message_opts ||= {}
41
62
  end
42
63
 
64
+ # Checks if ActiveJob is available
65
+ #
66
+ # @return [Boolean] true if ActiveJob is defined
43
67
  def active_job?
44
68
  defined?(::ActiveJob)
45
69
  end
46
70
 
71
+ # Adds a processing group with the specified concurrency and delay
72
+ #
73
+ # @param group [String] the name of the group
74
+ # @param concurrency [Integer, nil] the number of concurrent workers for the group
75
+ # @param delay [Float, nil] the delay between polling cycles
76
+ # @return [Hash] the group configuration
47
77
  def add_group(group, concurrency = nil, delay: nil)
48
78
  concurrency ||= options[:concurrency]
49
79
  delay ||= options[:delay]
@@ -55,16 +85,29 @@ module Shoryuken
55
85
  }
56
86
  end
57
87
 
88
+ # Adds a queue to a processing group with the specified weight
89
+ #
90
+ # @param queue [String] the name of the queue
91
+ # @param weight [Integer] the weight (priority) of the queue
92
+ # @param group [String] the name of the group to add the queue to
93
+ # @return [void]
58
94
  def add_queue(queue, weight, group)
59
95
  weight.times do
60
96
  groups[group][:queues] << queue
61
97
  end
62
98
  end
63
99
 
100
+ # Returns all queues from all groups
101
+ #
102
+ # @return [Array<String>] flat array of all queue names
64
103
  def ungrouped_queues
65
104
  groups.values.flat_map { |options| options[:queues] }
66
105
  end
67
106
 
107
+ # Returns the polling strategy class for a group
108
+ #
109
+ # @param group [String] the name of the group
110
+ # @return [Class] the polling strategy class to use
68
111
  def polling_strategy(group)
69
112
  strategy = (group == 'default' ? options : options[:groups].to_h[group]).to_h[:polling_strategy]
70
113
  case strategy
@@ -76,61 +119,111 @@ module Shoryuken
76
119
  begin
77
120
  Object.const_get(strategy)
78
121
  rescue NameError
79
- raise ArgumentError, "#{strategy} is not a valid polling_strategy"
122
+ raise Errors::InvalidPollingStrategyError, "#{strategy} is not a valid polling_strategy"
80
123
  end
81
124
  when Class
82
125
  strategy
83
126
  end
84
127
  end
85
128
 
129
+ # Returns the polling delay for a group
130
+ #
131
+ # @param group [String] the name of the group
132
+ # @return [Float] the delay in seconds
86
133
  def delay(group)
87
134
  groups[group].to_h.fetch(:delay, options[:delay]).to_f
88
135
  end
89
136
 
137
+ # Returns the SQS client, initializing a default one if needed.
138
+ # Uses AWS configuration from options[:aws] if available.
139
+ #
140
+ # @return [Aws::SQS::Client] the SQS client
90
141
  def sqs_client
91
- @sqs_client ||= Aws::SQS::Client.new
142
+ @sqs_client ||= Aws::SQS::Client.new(options[:aws])
92
143
  end
93
144
 
145
+ # Sets the SQS client receive message options for the default group
146
+ #
147
+ # @param sqs_client_receive_message_opts [Hash] the options hash
148
+ # @return [Hash] the options hash
94
149
  def sqs_client_receive_message_opts=(sqs_client_receive_message_opts)
95
150
  @sqs_client_receive_message_opts['default'] = sqs_client_receive_message_opts
96
151
  end
97
152
 
153
+ # Returns the global options hash
154
+ #
155
+ # @return [Hash] the options hash
98
156
  def options
99
157
  @options ||= DEFAULTS.dup
100
158
  end
101
159
 
160
+ # Returns the logger instance
161
+ #
162
+ # @return [Logger] the logger
102
163
  def logger
103
164
  @logger ||= Shoryuken::Logging.logger
104
165
  end
105
166
 
167
+ # Returns the thread priority setting
168
+ #
169
+ # @return [Integer] the thread priority
106
170
  def thread_priority
107
171
  @thread_priority ||= options[:thread_priority]
108
172
  end
109
173
 
174
+ # Sets the thread priority
175
+ #
176
+ # @param value [Integer] the thread priority value
177
+ # @return [Integer] the thread priority
178
+ attr_writer :thread_priority
179
+
180
+ # Registers a worker class with the worker registry
181
+ #
182
+ # @param args [Array] arguments to pass to the registry
183
+ # @return [void]
110
184
  def register_worker(*args)
111
185
  worker_registry.register_worker(*args)
112
186
  end
113
187
 
188
+ # Yields self if running as a server for server-specific configuration
189
+ #
190
+ # @yield [Shoryuken::Options] the options instance
191
+ # @return [void]
114
192
  def configure_server
115
193
  yield self if server?
116
194
  end
117
195
 
196
+ # Returns the server middleware chain
197
+ #
198
+ # @yield [Shoryuken::Middleware::Chain] the middleware chain for configuration
199
+ # @return [Shoryuken::Middleware::Chain] the server middleware chain
118
200
  def server_middleware
119
201
  @_server_chain ||= default_server_middleware
120
202
  yield @_server_chain if block_given?
121
203
  @_server_chain
122
204
  end
123
205
 
206
+ # Yields self unless running as a server for client-specific configuration
207
+ #
208
+ # @yield [Shoryuken::Options] the options instance
209
+ # @return [void]
124
210
  def configure_client
125
211
  yield self unless server?
126
212
  end
127
213
 
214
+ # Returns the client middleware chain
215
+ #
216
+ # @yield [Shoryuken::Middleware::Chain] the middleware chain for configuration
217
+ # @return [Shoryuken::Middleware::Chain] the client middleware chain
128
218
  def client_middleware
129
219
  @_client_chain ||= default_client_middleware
130
220
  yield @_client_chain if block_given?
131
221
  @_client_chain
132
222
  end
133
223
 
224
+ # Returns the default worker options hash
225
+ #
226
+ # @return [Hash{String => Object}] the default worker options
134
227
  def default_worker_options
135
228
  @default_worker_options ||= {
136
229
  'queue' => 'default',
@@ -142,43 +235,70 @@ module Shoryuken
142
235
  }
143
236
  end
144
237
 
238
+ # Registers a callback to run when the server starts
239
+ #
240
+ # @param block [Proc] the block to execute on start
241
+ # @return [void]
242
+ # @yield the block to execute on start
145
243
  def on_start(&block)
146
244
  self.start_callback = block
147
245
  end
148
246
 
247
+ # Registers a callback to run when the server stops
248
+ #
249
+ # @param block [Proc] the block to execute on stop
250
+ # @return [void]
251
+ # @yield the block to execute on stop
149
252
  def on_stop(&block)
150
253
  self.stop_callback = block
151
254
  end
152
255
 
153
- # Register a block to run at a point in the Shoryuken lifecycle.
154
- # :startup, :quiet, :shutdown or :stopped are valid events.
256
+ # Registers a block to run at a point in the Shoryuken lifecycle.
155
257
  #
258
+ # @param event [Symbol] the lifecycle event (:startup, :quiet, :shutdown, or :stopped)
259
+ # @param block [Proc] the block to execute for the event
260
+ # @return [void]
261
+ # @raise [ArgumentError] if event is not a Symbol or not a valid event name
262
+ # @yield the block to execute for the event
263
+ # @example
156
264
  # Shoryuken.configure_server do |config|
157
265
  # config.on(:shutdown) do
158
266
  # puts "Goodbye cruel world!"
159
267
  # end
160
268
  # end
161
269
  def on(event, &block)
162
- fail ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
163
- fail ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
270
+ raise Errors::InvalidEventError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
271
+ raise Errors::InvalidEventError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
164
272
 
165
273
  options[:lifecycle_events][event] << block
166
274
  end
167
275
 
276
+ # Checks if running as a server (CLI mode)
277
+ #
278
+ # @return [Boolean] true if Shoryuken::CLI is defined
168
279
  def server?
169
280
  defined?(Shoryuken::CLI)
170
281
  end
171
282
 
283
+ # Checks if visibility timeout caching is enabled
284
+ #
285
+ # @return [Boolean] true if caching is enabled
172
286
  def cache_visibility_timeout?
173
287
  @cache_visibility_timeout
174
288
  end
175
289
 
290
+ # Checks if ActiveJob queue name prefixing is enabled
291
+ #
292
+ # @return [Boolean] true if prefixing is enabled
176
293
  def active_job_queue_name_prefixing?
177
294
  @active_job_queue_name_prefixing
178
295
  end
179
296
 
180
297
  private
181
298
 
299
+ # Creates the default server middleware chain
300
+ #
301
+ # @return [Shoryuken::Middleware::Chain] the default middleware chain
182
302
  def default_server_middleware
183
303
  Middleware::Chain.new do |m|
184
304
  m.add Middleware::Server::Timing
@@ -192,6 +312,9 @@ module Shoryuken
192
312
  end
193
313
  end
194
314
 
315
+ # Creates the default client middleware chain
316
+ #
317
+ # @return [Shoryuken::Middleware::Chain] an empty middleware chain
195
318
  def default_client_middleware
196
319
  Middleware::Chain.new
197
320
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shoryuken
4
+ # Polling strategies for determining which queue to fetch messages from next
4
5
  module Polling
5
6
  # Abstract base class for queue polling strategies.
6
7
  #
@@ -2,7 +2,14 @@
2
2
 
3
3
  module Shoryuken
4
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.
5
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
6
13
  def initialize(queues, delay = nil)
7
14
  # Priority ordering of the queues, highest priority first
8
15
  @queues = queues
@@ -19,11 +26,19 @@ module Shoryuken
19
26
  reset_next_queue
20
27
  end
21
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
22
32
  def next_queue
23
33
  next_queue = next_active_queue
24
34
  next_queue.nil? ? nil : QueueConfiguration.new(next_queue, {})
25
35
  end
26
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]
27
42
  def messages_found(queue, messages_found)
28
43
  if messages_found == 0
29
44
  pause(queue)
@@ -32,6 +47,9 @@ module Shoryuken
32
47
  end
33
48
  end
34
49
 
50
+ # Returns the list of active (non-paused) queues with their priorities
51
+ #
52
+ # @return [Array<Array>] array of [queue_name, priority] pairs
35
53
  def active_queues
36
54
  @queues
37
55
  .reverse
@@ -40,6 +58,10 @@ module Shoryuken
40
58
  .reverse
41
59
  end
42
60
 
61
+ # Called when a message from a queue has been processed
62
+ #
63
+ # @param queue [String] the queue name
64
+ # @return [void]
43
65
  def message_processed(queue)
44
66
  if queue_paused?(queue)
45
67
  logger.debug "Unpausing #{queue}"
@@ -49,6 +71,9 @@ module Shoryuken
49
71
 
50
72
  private
51
73
 
74
+ # Finds the next active (non-paused) queue
75
+ #
76
+ # @return [String, nil] the queue name or nil if all paused
52
77
  def next_active_queue
53
78
  reset_next_queue if queues_unpaused_since?
54
79
 
@@ -62,6 +87,9 @@ module Shoryuken
62
87
  nil
63
88
  end
64
89
 
90
+ # Checks if any queues have been unpaused since last check
91
+ #
92
+ # @return [Boolean] true if queues were unpaused
65
93
  def queues_unpaused_since?
66
94
  last = @last_unpause_check
67
95
  now = @last_unpause_check = Time.now
@@ -69,14 +97,25 @@ module Shoryuken
69
97
  last && @paused_until.values.any? { |t| t > last && t <= now }
70
98
  end
71
99
 
100
+ # Resets the next queue index to start from the highest priority
101
+ #
102
+ # @return [void]
72
103
  def reset_next_queue
73
104
  @next_queue_index = 0
74
105
  end
75
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
76
111
  def queue_paused?(queue)
77
112
  @paused_until[queue] > Time.now
78
113
  end
79
114
 
115
+ # Pauses a queue for the configured delay time
116
+ #
117
+ # @param queue [String] the queue name to pause
118
+ # @return [void]
80
119
  def pause(queue)
81
120
  return unless delay > 0
82
121