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.
- checksums.yaml +4 -4
- data/.github/workflows/push.yml +3 -3
- data/.github/workflows/specs.yml +27 -17
- data/.github/workflows/verify-action-pins.yml +1 -1
- data/.rspec +2 -1
- data/.ruby-version +1 -1
- data/Appraisals +6 -18
- data/CHANGELOG.md +200 -142
- data/Gemfile +1 -0
- data/README.md +12 -13
- data/bin/cli/base.rb +1 -2
- data/bin/cli/sqs.rb +6 -5
- data/bin/shoryuken +3 -2
- data/gemfiles/rails_7_2.gemfile +1 -0
- data/gemfiles/rails_8_0.gemfile +1 -0
- data/gemfiles/{rails_7_1.gemfile → rails_8_1.gemfile} +2 -1
- data/lib/shoryuken/body_parser.rb +3 -1
- data/lib/shoryuken/client.rb +2 -0
- data/lib/shoryuken/default_exception_handler.rb +2 -0
- data/lib/shoryuken/default_worker_registry.rb +11 -11
- data/lib/shoryuken/environment_loader.rb +6 -6
- data/lib/shoryuken/extensions/active_job_adapter.rb +21 -6
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +5 -5
- data/lib/shoryuken/extensions/active_job_extensions.rb +2 -0
- data/lib/shoryuken/fetcher.rb +4 -2
- data/lib/shoryuken/helpers/atomic_boolean.rb +44 -0
- data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
- data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
- data/lib/shoryuken/helpers/hash_utils.rb +56 -0
- data/lib/shoryuken/helpers/string_utils.rb +65 -0
- data/lib/shoryuken/helpers/timer_task.rb +66 -0
- data/lib/shoryuken/inline_message.rb +22 -0
- data/lib/shoryuken/launcher.rb +16 -0
- data/lib/shoryuken/logging/base.rb +26 -0
- data/lib/shoryuken/logging/pretty.rb +25 -0
- data/lib/shoryuken/logging/without_timestamp.rb +25 -0
- data/lib/shoryuken/logging.rb +6 -12
- data/lib/shoryuken/manager.rb +6 -4
- data/lib/shoryuken/message.rb +116 -1
- data/lib/shoryuken/middleware/chain.rb +140 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +2 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +2 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +11 -11
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +5 -3
- data/lib/shoryuken/middleware/server/timing.rb +2 -0
- data/lib/shoryuken/options.rb +9 -5
- data/lib/shoryuken/polling/base_strategy.rb +126 -0
- data/lib/shoryuken/polling/queue_configuration.rb +103 -0
- data/lib/shoryuken/polling/strict_priority.rb +2 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +2 -0
- data/lib/shoryuken/processor.rb +5 -2
- data/lib/shoryuken/queue.rb +6 -4
- data/lib/shoryuken/runner.rb +12 -12
- data/lib/shoryuken/util.rb +6 -6
- data/lib/shoryuken/version.rb +3 -1
- data/lib/shoryuken/worker/default_executor.rb +2 -0
- data/lib/shoryuken/worker/inline_executor.rb +3 -1
- data/lib/shoryuken/worker.rb +173 -0
- data/lib/shoryuken/worker_registry.rb +2 -0
- data/lib/shoryuken.rb +8 -28
- data/shoryuken.gemspec +6 -6
- data/spec/integration/active_job_continuation_spec.rb +145 -0
- data/spec/integration/launcher_spec.rb +2 -3
- data/spec/shared_examples_for_active_job.rb +13 -8
- data/spec/shoryuken/body_parser_spec.rb +1 -2
- data/spec/shoryuken/client_spec.rb +1 -1
- data/spec/shoryuken/default_exception_handler_spec.rb +9 -10
- data/spec/shoryuken/default_worker_registry_spec.rb +1 -2
- data/spec/shoryuken/environment_loader_spec.rb +9 -8
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +2 -1
- data/spec/shoryuken/extensions/active_job_base_spec.rb +2 -1
- data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +2 -1
- data/spec/shoryuken/extensions/active_job_continuation_spec.rb +110 -0
- data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +2 -1
- data/spec/shoryuken/fetcher_spec.rb +23 -26
- data/spec/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
- data/spec/shoryuken/helpers/atomic_counter_spec.rb +177 -0
- data/spec/shoryuken/helpers/atomic_hash_spec.rb +307 -0
- data/spec/shoryuken/helpers/hash_utils_spec.rb +145 -0
- data/spec/shoryuken/helpers/string_utils_spec.rb +124 -0
- data/spec/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/shoryuken/helpers_integration_spec.rb +96 -0
- data/spec/shoryuken/inline_message_spec.rb +196 -0
- data/spec/shoryuken/launcher_spec.rb +23 -2
- data/spec/shoryuken/manager_spec.rb +1 -2
- data/spec/shoryuken/middleware/chain_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/timing_spec.rb +1 -1
- data/spec/shoryuken/options_spec.rb +4 -4
- data/spec/shoryuken/polling/base_strategy_spec.rb +280 -0
- data/spec/shoryuken/polling/queue_configuration_spec.rb +195 -0
- data/spec/shoryuken/polling/strict_priority_spec.rb +1 -1
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +1 -1
- data/spec/shoryuken/processor_spec.rb +1 -1
- data/spec/shoryuken/queue_spec.rb +2 -3
- data/spec/shoryuken/runner_spec.rb +1 -3
- data/spec/shoryuken/util_spec.rb +1 -1
- data/spec/shoryuken/worker/default_executor_spec.rb +1 -1
- data/spec/shoryuken/worker/inline_executor_spec.rb +1 -1
- data/spec/shoryuken/worker_spec.rb +15 -11
- data/spec/shoryuken_spec.rb +1 -1
- data/spec/spec_helper.rb +16 -0
- metadata +72 -29
- data/.github/FUNDING.yml +0 -12
- data/gemfiles/rails_6_1.gemfile +0 -18
- data/gemfiles/rails_7_0.gemfile +0 -19
- data/lib/shoryuken/core_ext.rb +0 -69
- data/lib/shoryuken/polling/base.rb +0 -67
- data/shoryuken.jpg +0 -0
- data/spec/shoryuken/core_ext_spec.rb +0 -40
data/lib/shoryuken/message.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
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
|
-
#
|
|
23
|
+
# ## Per-Worker Middleware Configuration
|
|
18
24
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
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
|
|
@@ -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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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 =>
|
|
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: #{
|
|
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 {
|
|
30
|
+
logger.debug { e.backtrace.join("\n") } unless e.backtrace.nil?
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
private
|
data/lib/shoryuken/options.rb
CHANGED
|
@@ -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,
|
|
21
|
-
:launcher_executor, :reloader, :enable_reloading,
|
|
22
|
-
:start_callback, :stop_callback, :worker_executor, :worker_registry,
|
|
23
|
-
|
|
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
|