shoryuken 7.0.0.alpha1 → 7.0.0.rc1

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +3 -3
  3. data/.github/workflows/specs.yml +27 -17
  4. data/.github/workflows/verify-action-pins.yml +1 -1
  5. data/.rspec +2 -1
  6. data/.ruby-version +1 -1
  7. data/Appraisals +6 -18
  8. data/CHANGELOG.md +200 -142
  9. data/Gemfile +1 -0
  10. data/README.md +12 -13
  11. data/bin/cli/base.rb +1 -2
  12. data/bin/cli/sqs.rb +6 -5
  13. data/bin/shoryuken +3 -2
  14. data/gemfiles/rails_7_2.gemfile +1 -0
  15. data/gemfiles/rails_8_0.gemfile +1 -0
  16. data/gemfiles/{rails_7_1.gemfile → rails_8_1.gemfile} +2 -1
  17. data/lib/shoryuken/body_parser.rb +3 -1
  18. data/lib/shoryuken/client.rb +2 -0
  19. data/lib/shoryuken/default_exception_handler.rb +2 -0
  20. data/lib/shoryuken/default_worker_registry.rb +11 -11
  21. data/lib/shoryuken/environment_loader.rb +6 -6
  22. data/lib/shoryuken/extensions/active_job_adapter.rb +21 -6
  23. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +5 -5
  24. data/lib/shoryuken/extensions/active_job_extensions.rb +2 -0
  25. data/lib/shoryuken/fetcher.rb +4 -2
  26. data/lib/shoryuken/helpers/atomic_boolean.rb +44 -0
  27. data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
  28. data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
  29. data/lib/shoryuken/helpers/hash_utils.rb +56 -0
  30. data/lib/shoryuken/helpers/string_utils.rb +65 -0
  31. data/lib/shoryuken/helpers/timer_task.rb +66 -0
  32. data/lib/shoryuken/inline_message.rb +22 -0
  33. data/lib/shoryuken/launcher.rb +16 -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 +6 -12
  38. data/lib/shoryuken/manager.rb +6 -4
  39. data/lib/shoryuken/message.rb +116 -1
  40. data/lib/shoryuken/middleware/chain.rb +140 -43
  41. data/lib/shoryuken/middleware/entry.rb +30 -0
  42. data/lib/shoryuken/middleware/server/active_record.rb +2 -0
  43. data/lib/shoryuken/middleware/server/auto_delete.rb +2 -0
  44. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +11 -11
  45. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +5 -3
  46. data/lib/shoryuken/middleware/server/timing.rb +2 -0
  47. data/lib/shoryuken/options.rb +9 -5
  48. data/lib/shoryuken/polling/base_strategy.rb +126 -0
  49. data/lib/shoryuken/polling/queue_configuration.rb +103 -0
  50. data/lib/shoryuken/polling/strict_priority.rb +2 -0
  51. data/lib/shoryuken/polling/weighted_round_robin.rb +2 -0
  52. data/lib/shoryuken/processor.rb +5 -2
  53. data/lib/shoryuken/queue.rb +6 -4
  54. data/lib/shoryuken/runner.rb +12 -12
  55. data/lib/shoryuken/util.rb +6 -6
  56. data/lib/shoryuken/version.rb +3 -1
  57. data/lib/shoryuken/worker/default_executor.rb +2 -0
  58. data/lib/shoryuken/worker/inline_executor.rb +3 -1
  59. data/lib/shoryuken/worker.rb +173 -0
  60. data/lib/shoryuken/worker_registry.rb +2 -0
  61. data/lib/shoryuken.rb +8 -28
  62. data/shoryuken.gemspec +6 -6
  63. data/spec/integration/active_job_continuation_spec.rb +145 -0
  64. data/spec/integration/launcher_spec.rb +2 -3
  65. data/spec/shared_examples_for_active_job.rb +13 -8
  66. data/spec/shoryuken/body_parser_spec.rb +1 -2
  67. data/spec/shoryuken/client_spec.rb +1 -1
  68. data/spec/shoryuken/default_exception_handler_spec.rb +9 -10
  69. data/spec/shoryuken/default_worker_registry_spec.rb +1 -2
  70. data/spec/shoryuken/environment_loader_spec.rb +9 -8
  71. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +2 -1
  72. data/spec/shoryuken/extensions/active_job_base_spec.rb +2 -1
  73. data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +2 -1
  74. data/spec/shoryuken/extensions/active_job_continuation_spec.rb +110 -0
  75. data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +2 -1
  76. data/spec/shoryuken/fetcher_spec.rb +23 -26
  77. data/spec/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
  78. data/spec/shoryuken/helpers/atomic_counter_spec.rb +177 -0
  79. data/spec/shoryuken/helpers/atomic_hash_spec.rb +307 -0
  80. data/spec/shoryuken/helpers/hash_utils_spec.rb +145 -0
  81. data/spec/shoryuken/helpers/string_utils_spec.rb +124 -0
  82. data/spec/shoryuken/helpers/timer_task_spec.rb +298 -0
  83. data/spec/shoryuken/helpers_integration_spec.rb +96 -0
  84. data/spec/shoryuken/inline_message_spec.rb +196 -0
  85. data/spec/shoryuken/launcher_spec.rb +23 -2
  86. data/spec/shoryuken/manager_spec.rb +1 -2
  87. data/spec/shoryuken/middleware/chain_spec.rb +1 -1
  88. data/spec/shoryuken/middleware/server/auto_delete_spec.rb +1 -1
  89. data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +1 -1
  90. data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +1 -1
  91. data/spec/shoryuken/middleware/server/timing_spec.rb +1 -1
  92. data/spec/shoryuken/options_spec.rb +4 -4
  93. data/spec/shoryuken/polling/base_strategy_spec.rb +280 -0
  94. data/spec/shoryuken/polling/queue_configuration_spec.rb +195 -0
  95. data/spec/shoryuken/polling/strict_priority_spec.rb +1 -1
  96. data/spec/shoryuken/polling/weighted_round_robin_spec.rb +1 -1
  97. data/spec/shoryuken/processor_spec.rb +1 -1
  98. data/spec/shoryuken/queue_spec.rb +2 -3
  99. data/spec/shoryuken/runner_spec.rb +1 -3
  100. data/spec/shoryuken/util_spec.rb +1 -1
  101. data/spec/shoryuken/worker/default_executor_spec.rb +1 -1
  102. data/spec/shoryuken/worker/inline_executor_spec.rb +1 -1
  103. data/spec/shoryuken/worker_spec.rb +15 -11
  104. data/spec/shoryuken_spec.rb +1 -1
  105. data/spec/spec_helper.rb +16 -0
  106. metadata +72 -29
  107. data/.github/FUNDING.yml +0 -12
  108. data/gemfiles/rails_6_1.gemfile +0 -18
  109. data/gemfiles/rails_7_0.gemfile +0 -19
  110. data/lib/shoryuken/core_ext.rb +0 -69
  111. data/lib/shoryuken/polling/base.rb +0 -67
  112. data/shoryuken.jpg +0 -0
  113. data/spec/shoryuken/core_ext_spec.rb +0 -40
@@ -1,7 +1,70 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
4
+ # Represents an SQS message received by a Shoryuken worker.
5
+ # This class wraps the raw AWS SQS message data and provides convenient methods
6
+ # for interacting with the message, including deletion and visibility timeout management.
7
+ #
8
+ # Message instances are automatically created by Shoryuken and passed to your
9
+ # worker's `perform` method as the first argument.
10
+ #
11
+ # @example Basic worker with message handling
12
+ # class MyWorker
13
+ # include Shoryuken::Worker
14
+ # shoryuken_options queue: 'my_queue'
15
+ #
16
+ # def perform(sqs_msg, body)
17
+ # puts "Processing message #{sqs_msg.message_id}"
18
+ # puts "Message body: #{body}"
19
+ # puts "Queue: #{sqs_msg.queue_name}"
20
+ #
21
+ # # Process the message...
22
+ #
23
+ # # Delete the message when done (if auto_delete is false)
24
+ # sqs_msg.delete unless auto_delete?
25
+ # end
26
+ # end
27
+ #
28
+ # @example Working with message attributes
29
+ # def perform(sqs_msg, body)
30
+ # # Access standard SQS attributes
31
+ # sender_id = sqs_msg.attributes['SenderId']
32
+ # sent_timestamp = sqs_msg.attributes['SentTimestamp']
33
+ #
34
+ # # Access custom message attributes
35
+ # priority = sqs_msg.message_attributes['Priority']&.[]('StringValue')
36
+ # user_id = sqs_msg.message_attributes['UserId']&.[]('StringValue')
37
+ # end
2
38
  class Message
3
39
  extend Forwardable
4
40
 
41
+ # @!method message_id
42
+ # Returns the unique SQS message ID.
43
+ # @return [String] The message ID assigned by SQS
44
+ #
45
+ # @!method receipt_handle
46
+ # Returns the receipt handle needed for deleting or modifying the message.
47
+ # @return [String] The receipt handle for this message
48
+ #
49
+ # @!method md5_of_body
50
+ # Returns the MD5 hash of the message body.
51
+ # @return [String] MD5 hash of the message body
52
+ #
53
+ # @!method body
54
+ # Returns the raw message body as received from SQS.
55
+ # @return [String] The raw message body
56
+ #
57
+ # @!method attributes
58
+ # Returns the SQS message attributes (system attributes).
59
+ # @return [Hash] System attributes like SenderId, SentTimestamp, etc.
60
+ #
61
+ # @!method md5_of_message_attributes
62
+ # Returns the MD5 hash of the message attributes.
63
+ # @return [String] MD5 hash of message attributes
64
+ #
65
+ # @!method message_attributes
66
+ # Returns custom message attributes set by the sender.
67
+ # @return [Hash] Custom message attributes with typed values
5
68
  def_delegators(:data,
6
69
  :message_id,
7
70
  :receipt_handle,
@@ -11,8 +74,24 @@ module Shoryuken
11
74
  :md5_of_message_attributes,
12
75
  :message_attributes)
13
76
 
14
- attr_accessor :client, :queue_url, :queue_name, :data
77
+ # @return [Aws::SQS::Client] The SQS client used for message operations
78
+ attr_accessor :client
79
+
80
+ # @return [String] The URL of the SQS queue this message came from
81
+ attr_accessor :queue_url
82
+
83
+ # @return [String] The name of the queue this message came from
84
+ attr_accessor :queue_name
85
+
86
+ # @return [Aws::SQS::Types::Message] The raw SQS message data
87
+ attr_accessor :data
15
88
 
89
+ # Creates a new Message instance wrapping SQS message data.
90
+ #
91
+ # @param client [Aws::SQS::Client] The SQS client for message operations
92
+ # @param queue [Shoryuken::Queue] The queue this message came from
93
+ # @param data [Aws::SQS::Types::Message] The raw SQS message data
94
+ # @api private
16
95
  def initialize(client, queue, data)
17
96
  self.client = client
18
97
  self.data = data
@@ -20,6 +99,12 @@ module Shoryuken
20
99
  self.queue_name = queue.name
21
100
  end
22
101
 
102
+ # Deletes this message from the SQS queue.
103
+ # Once deleted, the message will not be redelivered and cannot be retrieved again.
104
+ # This is typically called after successful message processing when auto_delete is disabled.
105
+ #
106
+ # @return [Aws::SQS::Types::DeleteMessageResult] The deletion result
107
+ # @raise [Aws::SQS::Errors::ServiceError] If the deletion fails
23
108
  def delete
24
109
  client.delete_message(
25
110
  queue_url: queue_url,
@@ -27,12 +112,42 @@ module Shoryuken
27
112
  )
28
113
  end
29
114
 
115
+ # Changes the visibility timeout of this message with additional options.
116
+ # This allows you to hide the message from other consumers for a longer or shorter period.
117
+ #
118
+ # @param options [Hash] Options to pass to change_message_visibility
119
+ # @option options [Integer] :visibility_timeout New visibility timeout in seconds
120
+ # @return [Aws::SQS::Types::ChangeMessageVisibilityResult] The change result
121
+ # @raise [Aws::SQS::Errors::ServiceError] If the change fails
122
+ #
123
+ # @example Extending visibility with additional options
124
+ # sqs_msg.change_visibility(visibility_timeout: 300)
125
+ #
126
+ # @see #visibility_timeout= For a simpler interface
30
127
  def change_visibility(options)
31
128
  client.change_message_visibility(
32
129
  options.merge(queue_url: queue_url, receipt_handle: data.receipt_handle)
33
130
  )
34
131
  end
35
132
 
133
+ # Sets the visibility timeout for this message.
134
+ # This is a convenience method for changing only the visibility timeout.
135
+ #
136
+ # @param timeout [Integer] New visibility timeout in seconds (0-43200)
137
+ # @return [Aws::SQS::Types::ChangeMessageVisibilityResult] The change result
138
+ # @raise [Aws::SQS::Errors::ServiceError] If the change fails
139
+ #
140
+ # @example Extending processing time
141
+ # def perform(sqs_msg, body)
142
+ # if complex_processing_needed?(body)
143
+ # sqs_msg.visibility_timeout = 1800 # 30 minutes
144
+ # end
145
+ #
146
+ # process_message(body)
147
+ # end
148
+ #
149
+ # @example Making message immediately visible again
150
+ # sqs_msg.visibility_timeout = 0 # Make visible immediately
36
151
  def visibility_timeout=(timeout)
37
152
  client.change_message_visibility(
38
153
  queue_url: queue_url,
@@ -1,70 +1,156 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
- # Middleware is code configured to run before/after
3
- # a message is processed. It is patterned after Rack
4
- # middleware. Middleware exists for the server
5
- # side (when jobs are actually processed).
6
- #
7
- # To modify middleware for the server, just call
8
- # with another block:
9
- #
10
- # Shoryuken.configure_server do |config|
11
- # config.server_middleware do |chain|
12
- # chain.add MyServerHook
13
- # chain.remove ActiveRecord
4
+ # Middleware provides a way to wrap message processing with custom logic,
5
+ # similar to Rack middleware in web applications. Middleware runs on the server
6
+ # side and can perform setup, teardown, error handling, and monitoring around
7
+ # job execution.
8
+ #
9
+ # Middleware classes must implement a `call` method that accepts the worker instance,
10
+ # queue name, and SQS message, and must yield to continue the middleware chain.
11
+ #
12
+ # ## Global Middleware Configuration
13
+ #
14
+ # Configure middleware globally for all workers:
15
+ #
16
+ # Shoryuken.configure_server do |config|
17
+ # config.server_middleware do |chain|
18
+ # chain.add MyServerHook
19
+ # chain.remove Shoryuken::Middleware::Server::ActiveRecord
20
+ # end
14
21
  # end
15
- # end
16
22
  #
17
- # To insert immediately preceding another entry:
23
+ # ## Per-Worker Middleware Configuration
18
24
  #
19
- # Shoryuken.configure_server do |config|
20
- # config.server_middleware do |chain|
21
- # chain.insert_before ActiveRecord, MyServerHook
25
+ # Configure middleware for specific workers:
26
+ #
27
+ # class MyWorker
28
+ # include Shoryuken::Worker
29
+ #
30
+ # server_middleware do |chain|
31
+ # chain.add MyWorkerSpecificMiddleware
32
+ # end
22
33
  # end
23
- # end
24
34
  #
25
- # To insert immediately after another entry:
35
+ # ## Middleware Ordering
36
+ #
37
+ # Insert middleware at specific positions in the chain:
38
+ #
39
+ # # Insert before existing middleware
40
+ # chain.insert_before Shoryuken::Middleware::Server::ActiveRecord, MyDatabaseSetup
41
+ #
42
+ # # Insert after existing middleware
43
+ # chain.insert_after Shoryuken::Middleware::Server::Timing, MyMetricsCollector
26
44
  #
27
- # Shoryuken.configure_server do |config|
28
- # config.server_middleware do |chain|
29
- # chain.insert_after ActiveRecord, MyServerHook
45
+ # # Add to beginning of chain
46
+ # chain.prepend MyFirstMiddleware
47
+ #
48
+ # ## Example Middleware Implementations
49
+ #
50
+ # # Basic logging middleware
51
+ # class LoggingMiddleware
52
+ # def call(worker_instance, queue, sqs_msg, body)
53
+ # puts "Processing #{sqs_msg.message_id} on #{queue}"
54
+ # start_time = Time.now
55
+ # yield
56
+ # puts "Completed in #{Time.now - start_time}s"
57
+ # end
30
58
  # end
31
- # end
32
59
  #
33
- # This is an example of a minimal server middleware:
60
+ # # Error reporting middleware
61
+ # class ErrorReportingMiddleware
62
+ # def call(worker_instance, queue, sqs_msg, body)
63
+ # yield
64
+ # rescue => error
65
+ # ErrorReporter.notify(error, {
66
+ # worker: worker_instance.class.name,
67
+ # queue: queue,
68
+ # message_id: sqs_msg.message_id
69
+ # })
70
+ # raise
71
+ # end
72
+ # end
34
73
  #
35
- # class MyServerHook
36
- # def call(worker_instance, queue, sqs_msg)
37
- # puts 'Before work'
38
- # yield
39
- # puts 'After work'
74
+ # # Performance monitoring middleware
75
+ # class MetricsMiddleware
76
+ # def call(worker_instance, queue, sqs_msg, body)
77
+ # start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
78
+ # yield
79
+ # duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
80
+ # StatsD.timing("shoryuken.#{worker_instance.class.name.underscore}.duration", duration)
81
+ # end
40
82
  # end
41
- # end
42
83
  #
84
+ # @see Shoryuken::Middleware::Chain Middleware chain management
85
+ # @see https://github.com/ruby-shoryuken/shoryuken/wiki/Middleware Comprehensive middleware guide
43
86
  module Middleware
87
+ # Manages a chain of middleware classes that will be instantiated and invoked
88
+ # in sequence around message processing. Provides methods for adding, removing,
89
+ # and reordering middleware.
44
90
  class Chain
91
+ # @return [Array<Entry>] The ordered list of middleware entries
45
92
  attr_reader :entries
46
93
 
94
+ # Creates a new middleware chain.
95
+ #
96
+ # @yield [Chain] The chain instance for configuration
97
+ # @example Creating and configuring a chain
98
+ # chain = Shoryuken::Middleware::Chain.new do |c|
99
+ # c.add MyMiddleware
100
+ # c.add AnotherMiddleware, option: 'value'
101
+ # end
47
102
  def initialize
48
103
  @entries = []
49
104
  yield self if block_given?
50
105
  end
51
106
 
107
+ # Creates a copy of this middleware chain.
108
+ #
109
+ # @return [Chain] A new chain with the same middleware entries
52
110
  def dup
53
111
  self.class.new.tap { |new_chain| new_chain.entries.replace(entries) }
54
112
  end
55
113
 
114
+ # Removes all instances of the specified middleware class from the chain.
115
+ #
116
+ # @param klass [Class] The middleware class to remove
117
+ # @return [Array<Entry>] The removed entries
118
+ # @example Removing ActiveRecord middleware
119
+ # chain.remove Shoryuken::Middleware::Server::ActiveRecord
56
120
  def remove(klass)
57
121
  entries.delete_if { |entry| entry.klass == klass }
58
122
  end
59
123
 
124
+ # Adds middleware to the end of the chain. Does nothing if the middleware
125
+ # class is already present in the chain.
126
+ #
127
+ # @param klass [Class] The middleware class to add
128
+ # @param args [Array] Arguments to pass to the middleware constructor
129
+ # @example Adding middleware with arguments
130
+ # chain.add MyMiddleware, timeout: 30, retries: 3
60
131
  def add(klass, *args)
61
132
  entries << Entry.new(klass, *args) unless exists?(klass)
62
133
  end
63
134
 
135
+ # Adds middleware to the beginning of the chain. Does nothing if the middleware
136
+ # class is already present in the chain.
137
+ #
138
+ # @param klass [Class] The middleware class to prepend
139
+ # @param args [Array] Arguments to pass to the middleware constructor
140
+ # @example Adding middleware to run first
141
+ # chain.prepend AuthenticationMiddleware
64
142
  def prepend(klass, *args)
65
143
  entries.insert(0, Entry.new(klass, *args)) unless exists?(klass)
66
144
  end
67
145
 
146
+ # Inserts middleware immediately before another middleware class.
147
+ # If the new middleware already exists, it's moved to the new position.
148
+ #
149
+ # @param oldklass [Class] The existing middleware to insert before
150
+ # @param newklass [Class] The middleware class to insert
151
+ # @param args [Array] Arguments to pass to the middleware constructor
152
+ # @example Insert database setup before ActiveRecord middleware
153
+ # chain.insert_before Shoryuken::Middleware::Server::ActiveRecord, DatabaseSetup
68
154
  def insert_before(oldklass, newklass, *args)
69
155
  i = entries.index { |entry| entry.klass == newklass }
70
156
  new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
@@ -72,6 +158,14 @@ module Shoryuken
72
158
  entries.insert(i, new_entry)
73
159
  end
74
160
 
161
+ # Inserts middleware immediately after another middleware class.
162
+ # If the new middleware already exists, it's moved to the new position.
163
+ #
164
+ # @param oldklass [Class] The existing middleware to insert after
165
+ # @param newklass [Class] The middleware class to insert
166
+ # @param args [Array] Arguments to pass to the middleware constructor
167
+ # @example Insert metrics collection after timing middleware
168
+ # chain.insert_after Shoryuken::Middleware::Server::Timing, MetricsCollector
75
169
  def insert_after(oldklass, newklass, *args)
76
170
  i = entries.index { |entry| entry.klass == newklass }
77
171
  new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
@@ -79,18 +173,34 @@ module Shoryuken
79
173
  entries.insert(i + 1, new_entry)
80
174
  end
81
175
 
176
+ # Checks if a middleware class is already in the chain.
177
+ #
178
+ # @param klass [Class] The middleware class to check for
179
+ # @return [Boolean] True if the middleware is in the chain
82
180
  def exists?(klass)
83
181
  entries.any? { |entry| entry.klass == klass }
84
182
  end
85
183
 
184
+ # Creates instances of all middleware classes in the chain.
185
+ #
186
+ # @return [Array] Array of middleware instances
86
187
  def retrieve
87
188
  entries.map(&:make_new)
88
189
  end
89
190
 
191
+ # Removes all middleware from the chain.
192
+ #
193
+ # @return [Array] Empty array
90
194
  def clear
91
195
  entries.clear
92
196
  end
93
197
 
198
+ # Invokes the middleware chain with the given arguments.
199
+ # Each middleware's call method will be invoked in sequence,
200
+ # with control passed through yielding.
201
+ #
202
+ # @param args [Array] Arguments to pass to each middleware
203
+ # @yield The final action to perform after all middleware
94
204
  def invoke(*args, &final_action)
95
205
  chain = retrieve.dup
96
206
  traverse_chain = lambda do
@@ -103,18 +213,5 @@ module Shoryuken
103
213
  traverse_chain.call
104
214
  end
105
215
  end
106
-
107
- class Entry
108
- attr_reader :klass
109
-
110
- def initialize(klass, *args)
111
- @klass = klass
112
- @args = args
113
- end
114
-
115
- def make_new
116
- @klass.new(*@args)
117
- end
118
- end
119
216
  end
120
217
  end
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -28,17 +30,15 @@ module Shoryuken
28
30
  def auto_extend(_worker, queue, sqs_msg, _body)
29
31
  queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
30
32
 
31
- Concurrent::TimerTask.new(execution_interval: queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
32
- begin
33
- logger.debug do
34
- "Extending message #{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s"
35
- end
36
-
37
- sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
38
- rescue => ex
39
- logger.error do
40
- "Could not auto extend the message #{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{ex.message}"
41
- end
33
+ Shoryuken::Helpers::TimerTask.new(execution_interval: queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
34
+ logger.debug do
35
+ "Extending message #{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s"
36
+ end
37
+
38
+ sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
39
+ rescue => e
40
+ logger.error do
41
+ "Could not auto extend the message #{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{e.message}"
42
42
  end
43
43
  end
44
44
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -14,7 +16,7 @@ module Shoryuken
14
16
 
15
17
  started_at = Time.now
16
18
  yield
17
- rescue => ex
19
+ rescue => e
18
20
  retry_intervals = worker.class.get_shoryuken_options['retry_intervals']
19
21
 
20
22
  if retry_intervals.nil? || !handle_failure(sqs_msg, started_at, retry_intervals)
@@ -23,9 +25,9 @@ module Shoryuken
23
25
  raise
24
26
  end
25
27
 
26
- logger.warn { "Message #{sqs_msg.message_id} will attempt retry due to error: #{ex.message}" }
28
+ logger.warn { "Message #{sqs_msg.message_id} will attempt retry due to error: #{e.message}" }
27
29
  # since we didn't raise, lets log the backtrace for debugging purposes.
28
- logger.debug { ex.backtrace.join("\n") } unless ex.backtrace.nil?
30
+ logger.debug { e.backtrace.join("\n") } unless e.backtrace.nil?
29
31
  end
30
32
 
31
33
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  class Options
3
5
  DEFAULTS = {
@@ -17,10 +19,12 @@ module Shoryuken
17
19
  }
18
20
  }.freeze
19
21
 
20
- attr_accessor :active_job_queue_name_prefixing, :cache_visibility_timeout, :groups,
21
- :launcher_executor, :reloader, :enable_reloading,
22
- :start_callback, :stop_callback, :worker_executor, :worker_registry, :exception_handlers
23
- attr_writer :default_worker_options, :sqs_client
22
+ attr_accessor :active_job_queue_name_prefixing, :cache_visibility_timeout,
23
+ :groups, :launcher_executor, :reloader, :enable_reloading,
24
+ :start_callback, :stop_callback, :worker_executor, :worker_registry,
25
+ :exception_handlers
26
+
27
+ attr_writer :default_worker_options, :sqs_client, :logger
24
28
  attr_reader :sqs_client_receive_message_opts
25
29
 
26
30
  def initialize
@@ -96,7 +100,7 @@ module Shoryuken
96
100
  end
97
101
 
98
102
  def logger
99
- Shoryuken::Logging.logger
103
+ @logger ||= Shoryuken::Logging.logger
100
104
  end
101
105
 
102
106
  def thread_priority
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Polling
5
+ # Abstract base class for queue polling strategies.
6
+ #
7
+ # This class defines the interface that all polling strategies must implement
8
+ # to manage queue selection and message flow control in Shoryuken workers.
9
+ # Polling strategies determine which queue to fetch messages from next and
10
+ # how to handle scenarios where queues have no messages available.
11
+ #
12
+ # @abstract Subclass and override {#next_queue}, {#messages_found}, and {#active_queues}
13
+ # to implement a custom polling strategy.
14
+ #
15
+ # @example Implementing a custom polling strategy
16
+ # class CustomStrategy < BaseStrategy
17
+ # def initialize(queues)
18
+ # @queues = queues
19
+ # end
20
+ #
21
+ # def next_queue
22
+ # # Return next queue to poll
23
+ # @queues.sample
24
+ # end
25
+ #
26
+ # def messages_found(queue, count)
27
+ # # Handle result of polling
28
+ # logger.info "Found #{count} messages in #{queue}"
29
+ # end
30
+ #
31
+ # def active_queues
32
+ # # Return list of active queues
33
+ # @queues
34
+ # end
35
+ # end
36
+ #
37
+ # @see WeightedRoundRobin
38
+ # @see StrictPriority
39
+ class BaseStrategy
40
+ include Util
41
+
42
+ # Returns the next queue to poll for messages.
43
+ #
44
+ # This method should return a QueueConfiguration object representing
45
+ # the next queue that should be polled for messages, or nil if no
46
+ # queues are currently available for polling.
47
+ #
48
+ # @abstract Subclasses must implement this method
49
+ # @return [QueueConfiguration, nil] Next queue to poll, or nil if none available
50
+ # @raise [NotImplementedError] if not implemented by subclass
51
+ def next_queue
52
+ fail NotImplementedError
53
+ end
54
+
55
+ # Called when messages are found (or not found) in a queue.
56
+ #
57
+ # This method is invoked after polling a queue to inform the strategy
58
+ # about the number of messages that were retrieved. Strategies can use
59
+ # this information to make decisions about future polling behavior,
60
+ # such as pausing empty queues or adjusting queue weights.
61
+ #
62
+ # @abstract Subclasses must implement this method
63
+ # @param _queue [String] The name of the queue that was polled
64
+ # @param _messages_found [Integer] The number of messages retrieved
65
+ # @raise [NotImplementedError] if not implemented by subclass
66
+ def messages_found(_queue, _messages_found)
67
+ fail NotImplementedError
68
+ end
69
+
70
+ # Called when a message from a queue has been processed.
71
+ #
72
+ # This optional callback is invoked after a message has been successfully
73
+ # processed by a worker. Strategies can use this information for cleanup
74
+ # or to adjust their polling behavior.
75
+ #
76
+ # @param _queue [String] The name of the queue whose message was processed
77
+ # @return [void]
78
+ def message_processed(_queue); end
79
+
80
+ # Returns the list of currently active queues.
81
+ #
82
+ # This method should return an array representing the queues that are
83
+ # currently active and available for polling. The format may vary by
84
+ # strategy implementation.
85
+ #
86
+ # @abstract Subclasses must implement this method
87
+ # @return [Array] List of active queues
88
+ # @raise [NotImplementedError] if not implemented by subclass
89
+ def active_queues
90
+ fail NotImplementedError
91
+ end
92
+
93
+ # Compares this strategy with another object for equality.
94
+ #
95
+ # Two strategies are considered equal if they have the same active queues.
96
+ # This method also supports comparison with Array objects for backward
97
+ # compatibility.
98
+ #
99
+ # @param other [Object] Object to compare with
100
+ # @return [Boolean] true if strategies are equivalent
101
+ def ==(other)
102
+ case other
103
+ when Array
104
+ @queues == other
105
+ else
106
+ if other.respond_to?(:active_queues)
107
+ active_queues == other.active_queues
108
+ else
109
+ false
110
+ end
111
+ end
112
+ end
113
+
114
+ # Returns the delay time for pausing empty queues.
115
+ #
116
+ # This method returns the amount of time (in seconds) that empty queues
117
+ # should be paused before being polled again. The delay can be set at
118
+ # the strategy level or falls back to the global Shoryuken delay setting.
119
+ #
120
+ # @return [Float] Delay time in seconds
121
+ def delay
122
+ @delay || Shoryuken.options[:delay].to_f
123
+ end
124
+ end
125
+ end
126
+ end