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,18 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
4
+ # Represents an SQS queue and provides methods for sending and receiving messages.
5
+ # Handles both standard and FIFO queues, automatically adding required FIFO attributes.
2
6
  class Queue
3
7
  include Util
4
8
 
5
- FIFO_ATTR = 'FifoQueue'.freeze
6
- MESSAGE_GROUP_ID = 'ShoryukenMessage'.freeze
7
- VISIBILITY_TIMEOUT_ATTR = 'VisibilityTimeout'.freeze
9
+ # SQS attribute name for FIFO queue identification
10
+ FIFO_ATTR = 'FifoQueue'
11
+
12
+ # Default message group ID used for FIFO queues
13
+ MESSAGE_GROUP_ID = 'ShoryukenMessage'
14
+
15
+ # SQS attribute name for visibility timeout
16
+ VISIBILITY_TIMEOUT_ATTR = 'VisibilityTimeout'
17
+
18
+ # @return [String] the queue name
19
+ attr_accessor :name
20
+
21
+ # @return [Aws::SQS::Client] the SQS client
22
+ attr_accessor :client
8
23
 
9
- attr_accessor :name, :client, :url
24
+ # @return [String] the queue URL
25
+ attr_accessor :url
10
26
 
27
+ # Initializes a new Queue instance
28
+ #
29
+ # @param client [Aws::SQS::Client] the SQS client
30
+ # @param name_or_url_or_arn [String] the queue name, URL, or ARN
11
31
  def initialize(client, name_or_url_or_arn)
12
32
  self.client = client
13
33
  set_name_and_url(name_or_url_or_arn)
14
34
  end
15
35
 
36
+ # Returns the visibility timeout for the queue
37
+ #
38
+ # @return [Integer] the visibility timeout in seconds
16
39
  def visibility_timeout
17
40
  # Always lookup for the latest visibility when cache is disabled
18
41
  # setting it to nil, forces re-lookup
@@ -20,6 +43,11 @@ module Shoryuken
20
43
  @_visibility_timeout ||= queue_attributes.attributes[VISIBILITY_TIMEOUT_ATTR].to_i
21
44
  end
22
45
 
46
+ # Deletes messages from the queue in batch
47
+ #
48
+ # @param options [Hash] options for delete_message_batch
49
+ # @option options [Array<Hash>] :entries entries to delete with id and receipt_handle
50
+ # @return [Boolean] true if any messages failed to delete
23
51
  def delete_messages(options)
24
52
  failed_messages = client.delete_message_batch(
25
53
  options.merge(queue_url: url)
@@ -31,6 +59,15 @@ module Shoryuken
31
59
  end
32
60
  end
33
61
 
62
+ # Sends a single message to the queue
63
+ #
64
+ # @param options [Hash, String] message options or message body string
65
+ # @option options [String] :message_body the message body
66
+ # @option options [Integer] :delay_seconds delay before message becomes visible
67
+ # @option options [Hash] :message_attributes custom message attributes
68
+ # @option options [String] :message_group_id FIFO queue message group ID
69
+ # @option options [String] :message_deduplication_id FIFO queue deduplication ID
70
+ # @return [Aws::SQS::Types::SendMessageResult] the send result
34
71
  def send_message(options)
35
72
  options = sanitize_message!(options).merge(queue_url: url)
36
73
 
@@ -39,18 +76,35 @@ module Shoryuken
39
76
  end
40
77
  end
41
78
 
79
+ # Sends multiple messages to the queue in batch
80
+ #
81
+ # @param options [Hash, Array] batch options or array of message bodies/hashes
82
+ # @option options [Array<Hash>] :entries message entries to send
83
+ # @return [Aws::SQS::Types::SendMessageBatchResult] the batch send result
42
84
  def send_messages(options)
43
85
  client.send_message_batch(sanitize_messages!(options).merge(queue_url: url))
44
86
  end
45
87
 
88
+ # Receives messages from the queue
89
+ #
90
+ # @param options [Hash] options for receive_message
91
+ # @option options [Integer] :max_number_of_messages maximum messages to receive
92
+ # @option options [Integer] :visibility_timeout visibility timeout for received messages
93
+ # @option options [Integer] :wait_time_seconds long polling wait time
94
+ # @option options [Array<String>] :attribute_names SQS attributes to retrieve
95
+ # @option options [Array<String>] :message_attribute_names message attributes to retrieve
96
+ # @return [Array<Shoryuken::Message>] the received messages
46
97
  def receive_messages(options)
47
98
  messages = client.receive_message(options.merge(queue_url: url)).messages || []
48
99
  messages.map { |m| Message.new(client, self, m) }
49
100
  end
50
101
 
102
+ # Checks if the queue is a FIFO queue
103
+ #
104
+ # @return [Boolean] true if the queue is a FIFO queue
51
105
  def fifo?
52
106
  # Make sure the memoization work with boolean to avoid multiple calls to SQS
53
- # see https://github.com/phstc/shoryuken/pull/529
107
+ # see https://github.com/ruby-shoryuken/shoryuken/pull/529
54
108
  return @_fifo if defined?(@_fifo)
55
109
 
56
110
  @_fifo = queue_attributes.attributes[FIFO_ATTR] == 'true'
@@ -59,32 +113,51 @@ module Shoryuken
59
113
 
60
114
  private
61
115
 
116
+ # Initializes the FIFO attribute by calling fifo?
117
+ #
118
+ # @return [Boolean] whether the queue is FIFO
62
119
  def initialize_fifo_attribute
63
120
  # calling fifo? will also initialize it
64
121
  fifo?
65
122
  end
66
123
 
124
+ # Sets the queue name and URL from a queue name
125
+ #
126
+ # @param name [String] the queue name
127
+ # @return [void]
67
128
  def set_by_name(name) # rubocop:disable Naming/AccessorMethodName
68
129
  self.name = name
69
130
  self.url = client.get_queue_url(queue_name: name).queue_url
70
131
  end
71
132
 
133
+ # Sets the queue name and URL from a queue URL
134
+ #
135
+ # @param url [String] the queue URL
136
+ # @return [void]
72
137
  def set_by_url(url) # rubocop:disable Naming/AccessorMethodName
73
138
  self.name = url.split('/').last
74
139
  self.url = url
75
140
  end
76
141
 
142
+ # Converts an ARN to a queue URL
143
+ #
144
+ # @param arn_str [String] the ARN string
145
+ # @return [String] the queue URL
77
146
  def arn_to_url(arn_str)
78
147
  *, region, account_id, resource = arn_str.split(':')
79
148
 
80
149
  required = [region, account_id, resource].map(&:to_s)
81
150
  valid = required.none?(&:empty?)
82
151
 
83
- abort "Invalid ARN: #{arn_str}. A valid ARN must include: region, account_id and resource." unless valid
152
+ raise Errors::InvalidArnError, "Invalid ARN: #{arn_str}. A valid ARN must include: region, account_id and resource." unless valid
84
153
 
85
154
  "https://sqs.#{region}.amazonaws.com/#{account_id}/#{resource}"
86
155
  end
87
156
 
157
+ # Sets the queue name and URL from a name, URL, or ARN
158
+ #
159
+ # @param name_or_url_or_arn [String] the queue identifier
160
+ # @return [void]
88
161
  def set_name_and_url(name_or_url_or_arn) # rubocop:disable Naming/AccessorMethodName
89
162
  if name_or_url_or_arn.include?('://')
90
163
  set_by_url(name_or_url_or_arn)
@@ -104,16 +177,24 @@ module Shoryuken
104
177
  end
105
178
 
106
179
  set_by_name(name_or_url_or_arn)
107
- rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue => e
108
- raise e, "The specified queue #{name_or_url_or_arn} does not exist."
180
+ rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
181
+ raise Errors::QueueNotFoundError, "The specified queue #{name_or_url_or_arn} does not exist."
109
182
  end
110
183
 
184
+ # Returns the queue attributes from SQS
185
+ #
186
+ # @return [Aws::SQS::Types::GetQueueAttributesResult] the queue attributes
111
187
  def queue_attributes
112
188
  # Note: Retrieving all queue attributes as requesting `FifoQueue` on non-FIFO queue raises error.
113
189
  # See issue: https://github.com/aws/aws-sdk-ruby/issues/1350
114
190
  client.get_queue_attributes(queue_url: url, attribute_names: ['All'])
115
191
  end
116
192
 
193
+ # Sanitizes a batch of messages, converting to proper format
194
+ #
195
+ # @param options [Hash, Array] batch options or array of messages
196
+ # @option options [Array<Hash>] :entries message entries
197
+ # @return [Hash] the sanitized options with entries key
117
198
  def sanitize_messages!(options)
118
199
  if options.is_a?(Array)
119
200
  entries = options.map.with_index do |m, index|
@@ -128,6 +209,11 @@ module Shoryuken
128
209
  options
129
210
  end
130
211
 
212
+ # Adds FIFO attributes to message options if needed
213
+ #
214
+ # @param options [Hash] the message options
215
+ # @option options [String] :message_body the message body
216
+ # @return [Hash] the options with FIFO attributes added
131
217
  def add_fifo_attributes!(options)
132
218
  return unless fifo?
133
219
 
@@ -137,6 +223,11 @@ module Shoryuken
137
223
  options
138
224
  end
139
225
 
226
+ # Sanitizes a single message, converting body to JSON if needed
227
+ #
228
+ # @param options [Hash, String] message options or body string
229
+ # @option options [String, Hash] :message_body the message body
230
+ # @return [Hash] the sanitized message options
140
231
  def sanitize_message!(options)
141
232
  options = { message_body: options } if options.is_a?(String)
142
233
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $stdout.sync = true
2
4
 
3
5
  require 'singleton'
@@ -7,25 +9,32 @@ require 'erb'
7
9
  require 'shoryuken'
8
10
 
9
11
  module Shoryuken
10
- # rubocop:disable Lint/InheritException
11
- # See: https://github.com/mperham/sidekiq/blob/33f5d6b2b6c0dfaab11e5d39688cab7ebadc83ae/lib/sidekiq/cli.rb#L20
12
- class Shutdown < Interrupt; end
13
-
12
+ # Runs the Shoryuken server process.
13
+ # Handles signal trapping, daemonization, and lifecycle management.
14
14
  class Runner
15
15
  include Util
16
16
  include Singleton
17
17
 
18
+ # @return [Shoryuken::Launcher, nil] the launcher instance, or nil if not yet initialized
19
+ attr_reader :launcher
20
+
21
+ # Runs the Shoryuken server with the given options
22
+ #
23
+ # @param options [Hash] runtime configuration options
24
+ # @option options [Boolean] :daemon whether to daemonize the process
25
+ # @option options [String] :pidfile path to the PID file
26
+ # @option options [String] :logfile path to the log file
27
+ # @option options [String] :config_file path to the configuration file
28
+ # @return [void]
18
29
  def run(options)
19
30
  self_read, self_write = IO.pipe
20
31
 
21
32
  %w[INT TERM USR1 TSTP TTIN].each do |sig|
22
- begin
23
- trap sig do
24
- self_write.puts(sig)
25
- end
26
- rescue ArgumentError
27
- puts "Signal #{sig} not supported"
33
+ trap sig do
34
+ self_write.puts(sig)
28
35
  end
36
+ rescue ArgumentError
37
+ puts "Signal #{sig} not supported"
29
38
  end
30
39
 
31
40
  loader = EnvironmentLoader.setup_options(options)
@@ -52,12 +61,18 @@ module Shoryuken
52
61
  end
53
62
  end
54
63
 
64
+ # Checks if the server is healthy
65
+ #
66
+ # @return [Boolean] true if the launcher is running and healthy
55
67
  def healthy?
56
68
  (@launcher && @launcher.healthy?) || false
57
69
  end
58
70
 
59
71
  private
60
72
 
73
+ # Initializes the Concurrent Ruby logger
74
+ #
75
+ # @return [void]
61
76
  def initialize_concurrent_logger
62
77
  return unless Shoryuken.logger
63
78
 
@@ -66,6 +81,12 @@ module Shoryuken
66
81
  end
67
82
  end
68
83
 
84
+ # Daemonizes the process
85
+ #
86
+ # @param options [Hash] options containing daemon and logfile settings
87
+ # @option options [Boolean] :daemon whether to daemonize
88
+ # @option options [String] :logfile path to the log file for daemon output
89
+ # @return [void]
69
90
  def daemonize(options)
70
91
  return unless options[:daemon]
71
92
 
@@ -77,11 +98,9 @@ module Shoryuken
77
98
  Process.daemon(true, true)
78
99
 
79
100
  files_to_reopen.each do |file|
80
- begin
81
- file.reopen file.path, 'a+'
82
- file.sync = true
83
- rescue ::Exception
84
- end
101
+ file.reopen file.path, 'a+'
102
+ file.sync = true
103
+ rescue ::Exception
85
104
  end
86
105
 
87
106
  [$stdout, $stderr].each do |io|
@@ -93,25 +112,39 @@ module Shoryuken
93
112
  $stdin.reopen('/dev/null')
94
113
  end
95
114
 
115
+ # Writes the process ID to a file
116
+ #
117
+ # @param options [Hash] options containing the pidfile path
118
+ # @option options [String] :pidfile path to write the PID file
119
+ # @return [void]
96
120
  def write_pid(options)
97
121
  return unless (path = options[:pidfile])
98
122
 
99
123
  File.open(path, 'w') { |f| f.puts(Process.pid) }
100
124
  end
101
125
 
126
+ # Executes a soft shutdown on USR1 signal
127
+ #
128
+ # @return [void]
102
129
  def execute_soft_shutdown
103
- logger.info { 'Received USR1, will soft shutdown down' }
130
+ logger.info { 'Received USR1, will soft shutdown' }
104
131
 
105
132
  @launcher.stop
106
133
  exit 0
107
134
  end
108
135
 
136
+ # Executes a terminal stop on TSTP signal
137
+ #
138
+ # @return [void]
109
139
  def execute_terminal_stop
110
140
  logger.info { 'Received TSTP, will stop accepting new work' }
111
141
 
112
142
  @launcher.stop
113
143
  end
114
144
 
145
+ # Prints backtraces of all threads
146
+ #
147
+ # @return [void]
115
148
  def print_threads_backtrace
116
149
  Thread.list.each do |thread|
117
150
  logger.info { "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}" }
@@ -123,6 +156,11 @@ module Shoryuken
123
156
  end
124
157
  end
125
158
 
159
+ # Handles incoming signals
160
+ #
161
+ # @param sig [String] the signal name
162
+ # @return [void]
163
+ # @raise [Interrupt] on TERM or INT signals
126
164
  def handle_signal(sig)
127
165
  logger.debug "Got #{sig} signal"
128
166
 
@@ -1,41 +1,66 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
4
+ # Utility methods shared across Shoryuken classes.
5
+ # Provides logging, event firing, and helper methods.
2
6
  module Util
7
+ # Returns the Shoryuken logger
8
+ #
9
+ # @return [Logger] the configured logger
3
10
  def logger
4
11
  Shoryuken.logger
5
12
  end
6
13
 
14
+ # Fires a lifecycle event to all registered handlers
15
+ #
16
+ # @param event [Symbol] the event name to fire
17
+ # @param reverse [Boolean] whether to call handlers in reverse order
18
+ # @param event_options [Hash] options to pass to event handlers
19
+ # @return [void]
7
20
  def fire_event(event, reverse = false, event_options = {})
8
21
  logger.debug { "Firing '#{event}' lifecycle event" }
9
22
  arr = Shoryuken.options[:lifecycle_events][event]
10
23
  arr.reverse! if reverse
11
24
  arr.each do |block|
12
- begin
13
- block.call(event_options)
14
- rescue => ex
15
- logger.warn(event: event)
16
- logger.warn "#{ex.class.name}: #{ex.message}"
17
- end
25
+ block.call(event_options)
26
+ rescue => e
27
+ logger.warn(event: event)
28
+ logger.warn "#{e.class.name}: #{e.message}"
18
29
  end
19
30
  end
20
31
 
32
+ # Calculates elapsed time in milliseconds
33
+ #
34
+ # @param started_at [Time] the start time
35
+ # @return [Float] elapsed time in milliseconds
21
36
  def elapsed(started_at)
22
37
  # elapsed in ms
23
38
  (Time.now - started_at) * 1000
24
39
  end
25
40
 
41
+ # Converts a queue array to a hash of queue names and weights
42
+ #
43
+ # @param queues [Array<String>] array of queue names with possible duplicates
44
+ # @return [Array<Array>] array of [queue_name, weight] pairs
26
45
  def unparse_queues(queues)
27
46
  queues.each_with_object({}) do |name, queue_and_weights|
28
47
  queue_and_weights[name] = queue_and_weights[name].to_i + 1
29
48
  end.to_a
30
49
  end
31
50
 
51
+ # Returns a display name for the worker processing a message
52
+ #
53
+ # @param worker_class [Class] the worker class
54
+ # @param sqs_msg [Aws::SQS::Types::Message, Array] the message or batch
55
+ # @param body [Object, nil] the parsed message body
56
+ # @return [String] the worker display name
32
57
  def worker_name(worker_class, sqs_msg, body = nil)
33
58
  if Shoryuken.active_job? \
34
59
  && !sqs_msg.is_a?(Array) \
35
60
  && sqs_msg.message_attributes \
36
61
  && sqs_msg.message_attributes['shoryuken_class'] \
37
62
  && sqs_msg.message_attributes['shoryuken_class'][:string_value] \
38
- == ActiveJob::QueueAdapters::ShoryukenAdapter::JobWrapper.to_s \
63
+ == 'Shoryuken::ActiveJob::JobWrapper' \
39
64
  && body
40
65
 
41
66
  "ActiveJob/#{body['job_class']}"
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
- VERSION = '6.2.1'.freeze
4
+ # Current version of the Shoryuken gem
5
+ VERSION = '7.0.2'
3
6
  end
@@ -1,7 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Worker
5
+ # Default executor that sends messages to SQS for asynchronous processing.
6
+ # This is the standard executor used in production environments.
3
7
  class DefaultExecutor
4
8
  class << self
9
+ # Enqueues a job for asynchronous processing via SQS
10
+ #
11
+ # @param worker_class [Class] the worker class that will process the message
12
+ # @param body [Object] the message body
13
+ # @param options [Hash] additional SQS message options
14
+ # @option options [Hash] :message_attributes custom message attributes
15
+ # @option options [String] :queue override the default queue
16
+ # @return [Aws::SQS::Types::SendMessageResult] the send result
5
17
  def perform_async(worker_class, body, options = {})
6
18
  options[:message_attributes] ||= {}
7
19
  options[:message_attributes]['shoryuken_class'] = {
@@ -16,6 +28,16 @@ module Shoryuken
16
28
  Shoryuken::Client.queues(queue).send_message(options)
17
29
  end
18
30
 
31
+ # Enqueues a job for delayed processing via SQS
32
+ #
33
+ # @param worker_class [Class] the worker class that will process the message
34
+ # @param interval [Integer, Float] delay in seconds or timestamp
35
+ # @param body [Object] the message body
36
+ # @param options [Hash] SQS message options for the delayed job
37
+ # @option options [Hash] :message_attributes custom message attributes
38
+ # @option options [String] :queue override the default queue
39
+ # @return [Aws::SQS::Types::SendMessageResult] the send result
40
+ # @raise [Errors::InvalidDelayError] if delay exceeds 15 minutes
19
41
  def perform_in(worker_class, interval, body, options = {})
20
42
  interval = interval.to_f
21
43
  now = Time.now.to_f
@@ -23,7 +45,7 @@ module Shoryuken
23
45
 
24
46
  delay = (ts - now).ceil
25
47
 
26
- raise 'The maximum allowed delay is 15 minutes' if delay > 15 * 60
48
+ raise Errors::InvalidDelayError, 'The maximum allowed delay is 15 minutes' if delay > 15 * 60
27
49
 
28
50
  worker_class.perform_async(body, options.merge(delay_seconds: delay))
29
51
  end
@@ -1,17 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Worker
5
+ # Executor that processes jobs synchronously in the current thread.
6
+ # Useful for testing and development environments.
3
7
  class InlineExecutor
4
8
  class << self
9
+ # Processes a job synchronously in the current thread
10
+ #
11
+ # @param worker_class [Class] the worker class that will process the message
12
+ # @param body [Object] the message body
13
+ # @param options [Hash] inline execution options
14
+ # @option options [String] :queue override the default queue name
15
+ # @option options [Hash] :message_attributes custom message attributes
16
+ # @return [Object] the result of the worker's perform method
5
17
  def perform_async(worker_class, body, options = {})
6
18
  body = JSON.dump(body) if body.is_a?(Hash)
7
19
  queue_name = options.delete(:queue) || worker_class.get_shoryuken_options['queue']
20
+ message_attributes = options.delete(:message_attributes) || {}
21
+ message_attributes['shoryuken_class'] = {
22
+ string_value: worker_class.to_s,
23
+ data_type: 'String'
24
+ }
8
25
 
9
- sqs_msg = OpenStruct.new(
26
+ sqs_msg = InlineMessage.new(
10
27
  body: body,
11
28
  attributes: nil,
12
29
  md5_of_body: nil,
13
30
  md5_of_message_attributes: nil,
14
- message_attributes: nil,
31
+ message_attributes: message_attributes,
15
32
  message_id: nil,
16
33
  receipt_handle: nil,
17
34
  delete: nil,
@@ -21,12 +38,26 @@ module Shoryuken
21
38
  call(worker_class, sqs_msg)
22
39
  end
23
40
 
41
+ # Processes a job synchronously, ignoring the delay interval
42
+ #
43
+ # @param worker_class [Class] the worker class that will process the message
44
+ # @param _interval [Integer, Float] ignored for inline execution
45
+ # @param body [Object] the message body
46
+ # @param options [Hash] inline execution options
47
+ # @option options [String] :queue override the default queue name
48
+ # @option options [Hash] :message_attributes custom message attributes
49
+ # @return [Object] the result of the worker's perform method
24
50
  def perform_in(worker_class, _interval, body, options = {})
25
51
  worker_class.perform_async(body, options)
26
52
  end
27
53
 
28
54
  private
29
55
 
56
+ # Instantiates and calls the worker
57
+ #
58
+ # @param worker_class [Class] the worker class
59
+ # @param sqs_msg [Shoryuken::InlineMessage] the message
60
+ # @return [Object] the result of the worker's perform method
30
61
  def call(worker_class, sqs_msg)
31
62
  parsed_body = BodyParser.parse(worker_class, sqs_msg)
32
63
  batch = worker_class.shoryuken_options_hash['batch']