shoryuken 7.0.0.alpha2 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/push.yml +2 -2
- data/.github/workflows/specs.yml +38 -43
- data/.github/workflows/verify-action-pins.yml +1 -1
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -1
- data/.yard-lint.yml +279 -0
- data/CHANGELOG.md +69 -1
- data/Gemfile +1 -1
- data/README.md +2 -7
- data/Rakefile +4 -10
- data/bin/clean_localstack +52 -0
- data/bin/cli/base.rb +21 -0
- data/bin/cli/sqs.rb +61 -2
- data/bin/integrations +275 -0
- data/bin/scenario +154 -0
- data/bin/shoryuken +1 -1
- data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +15 -4
- data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
- data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
- data/lib/shoryuken/active_job/current_attributes.rb +139 -0
- data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
- data/lib/shoryuken/body_parser.rb +8 -0
- data/lib/shoryuken/client.rb +14 -0
- data/lib/shoryuken/default_exception_handler.rb +9 -0
- data/lib/shoryuken/default_worker_registry.rb +29 -1
- data/lib/shoryuken/environment_loader.rb +78 -8
- data/lib/shoryuken/errors.rb +33 -0
- data/lib/shoryuken/fetcher.rb +37 -1
- data/lib/shoryuken/helpers/atomic_boolean.rb +19 -5
- data/lib/shoryuken/helpers/timer_task.rb +80 -0
- data/lib/shoryuken/launcher.rb +53 -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 +39 -25
- data/lib/shoryuken/manager.rb +70 -1
- data/lib/shoryuken/message.rb +114 -1
- data/lib/shoryuken/middleware/chain.rb +139 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +8 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +10 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +27 -1
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +29 -0
- data/lib/shoryuken/middleware/server/timing.rb +11 -0
- data/lib/shoryuken/options.rb +129 -6
- data/lib/shoryuken/polling/base_strategy.rb +1 -0
- data/lib/shoryuken/polling/strict_priority.rb +39 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +42 -0
- data/lib/shoryuken/processor.rb +32 -1
- data/lib/shoryuken/queue.rb +93 -4
- data/lib/shoryuken/runner.rb +45 -4
- data/lib/shoryuken/util.rb +26 -1
- data/lib/shoryuken/version.rb +2 -1
- data/lib/shoryuken/worker/default_executor.rb +21 -1
- data/lib/shoryuken/worker/inline_executor.rb +24 -0
- data/lib/shoryuken/worker.rb +193 -0
- data/lib/shoryuken/worker_registry.rb +33 -0
- data/lib/shoryuken.rb +18 -6
- data/renovate.json +29 -2
- data/shoryuken.gemspec +2 -1
- data/spec/integration/.rspec +1 -0
- data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
- data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
- data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
- data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
- data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
- data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
- data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
- data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
- data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
- data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
- data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
- data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
- data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
- data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
- data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
- data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
- data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
- data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
- data/spec/integration/aws_config/aws_config_spec.rb +59 -0
- data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
- data/spec/integration/body_parser/json_parser_spec.rb +45 -0
- data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
- data/spec/integration/body_parser/text_parser_spec.rb +43 -0
- data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
- data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
- data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
- data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
- data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
- data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
- data/spec/integration/launcher/launcher_spec.rb +40 -0
- data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
- data/spec/integration/message_operations/message_operations_spec.rb +59 -0
- data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
- data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
- data/spec/integration/middleware_chain/removal_spec.rb +31 -0
- data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
- data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
- data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
- data/spec/integration/rails/rails_72/Gemfile +6 -0
- data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/Gemfile +6 -0
- data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
- data/spec/integration/rails/rails_81/Gemfile +6 -0
- data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
- data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
- data/spec/integration/spec_helper.rb +7 -0
- data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
- data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
- data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
- data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
- data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
- data/spec/integrations_helper.rb +243 -0
- data/spec/lib/active_job/extensions_spec.rb +149 -0
- data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
- data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +3 -3
- data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +4 -4
- data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/helpers/hash_utils_spec.rb +14 -14
- data/spec/{shoryuken → lib/shoryuken}/helpers/string_utils_spec.rb +3 -3
- data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/{shoryuken → lib/shoryuken}/helpers_integration_spec.rb +9 -9
- data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +22 -0
- data/spec/lib/shoryuken/logging_spec.rb +242 -0
- data/spec/lib/shoryuken/message_spec.rb +109 -0
- data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
- data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +50 -0
- data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +2 -2
- data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +1 -1
- data/spec/lib/shoryuken/version_spec.rb +17 -0
- data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
- data/spec/shared_examples_for_active_job.rb +29 -9
- data/spec/spec_helper.rb +34 -3
- metadata +230 -91
- data/.devcontainer/Dockerfile +0 -17
- data/.devcontainer/base.Dockerfile +0 -43
- data/.devcontainer/devcontainer.json +0 -35
- data/Appraisals +0 -23
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/rails_7_0.gemfile +0 -19
- data/gemfiles/rails_7_1.gemfile +0 -19
- data/gemfiles/rails_7_2.gemfile +0 -19
- data/gemfiles/rails_8_0.gemfile +0 -19
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -110
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
- data/spec/integration/launcher_spec.rb +0 -127
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -8
- data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -85
- /data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_boolean_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_counter_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_hash_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/inline_message_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/base_strategy_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/queue_configuration_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker/inline_executor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +0 -0
- /data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +0 -0
data/lib/shoryuken/logging.rb
CHANGED
|
@@ -2,40 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
require 'time'
|
|
4
4
|
require 'logger'
|
|
5
|
+
require_relative 'logging/base'
|
|
6
|
+
require_relative 'logging/pretty'
|
|
7
|
+
require_relative 'logging/without_timestamp'
|
|
5
8
|
|
|
6
9
|
module Shoryuken
|
|
10
|
+
# Provides logging functionality for Shoryuken.
|
|
11
|
+
# Manages the global logger instance and fiber-local context.
|
|
7
12
|
module Logging
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# Provide a call() method that returns the formatted message.
|
|
21
|
-
def call(severity, time, _program_name, message)
|
|
22
|
-
"#{time.utc.iso8601} #{Process.pid} TID-#{tid}#{context} #{severity}: #{message}\n"
|
|
23
|
-
end
|
|
13
|
+
# Executes a block with a fiber-local logging context.
|
|
14
|
+
# Uses Fiber storage (Ruby 3.2+) for proper isolation in async environments.
|
|
15
|
+
#
|
|
16
|
+
# @param msg [String] the context message to set
|
|
17
|
+
# @yield the block to execute within the context
|
|
18
|
+
# @return [Object] the result of the block
|
|
19
|
+
def self.with_context(msg)
|
|
20
|
+
previous = context_storage[:shoryuken_context]
|
|
21
|
+
context_storage[:shoryuken_context] = msg
|
|
22
|
+
yield
|
|
23
|
+
ensure
|
|
24
|
+
context_storage[:shoryuken_context] = previous
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
# Returns the current logging context value
|
|
28
|
+
#
|
|
29
|
+
# @return [String, nil] the current context or nil if not set
|
|
30
|
+
def self.current_context
|
|
31
|
+
context_storage[:shoryuken_context]
|
|
30
32
|
end
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
# Returns the Fiber class for fiber-local context storage.
|
|
35
|
+
# Uses Fiber[] and Fiber[]= (Ruby 3.2+) for proper isolation in async environments.
|
|
36
|
+
#
|
|
37
|
+
# @return [Class] the Fiber class
|
|
38
|
+
def self.context_storage
|
|
39
|
+
Fiber
|
|
37
40
|
end
|
|
38
41
|
|
|
42
|
+
# Initializes a new logger instance
|
|
43
|
+
#
|
|
44
|
+
# @param log_target [IO, String] the logging target (file path or IO object)
|
|
45
|
+
# @return [Logger] the initialized logger
|
|
39
46
|
def self.initialize_logger(log_target = STDOUT)
|
|
40
47
|
@logger = Logger.new(log_target)
|
|
41
48
|
@logger.level = Logger::INFO
|
|
@@ -43,10 +50,17 @@ module Shoryuken
|
|
|
43
50
|
@logger
|
|
44
51
|
end
|
|
45
52
|
|
|
53
|
+
# Returns the current logger instance, initializing if needed
|
|
54
|
+
#
|
|
55
|
+
# @return [Logger] the logger instance
|
|
46
56
|
def self.logger
|
|
47
57
|
@logger ||= initialize_logger
|
|
48
58
|
end
|
|
49
59
|
|
|
60
|
+
# Sets the logger instance
|
|
61
|
+
#
|
|
62
|
+
# @param log [Logger, nil] the logger to use, or nil for null logger
|
|
63
|
+
# @return [Logger] the logger instance
|
|
50
64
|
def self.logger=(log)
|
|
51
65
|
@logger = log || Logger.new('/dev/null')
|
|
52
66
|
end
|
data/lib/shoryuken/manager.rb
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Shoryuken
|
|
4
|
+
# Manages message dispatching and processing for a single processing group.
|
|
5
|
+
# Coordinates between the fetcher, polling strategy, and processor.
|
|
4
6
|
class Manager
|
|
5
7
|
include Util
|
|
6
8
|
|
|
9
|
+
# Maximum number of messages to fetch in a single batch request
|
|
7
10
|
BATCH_LIMIT = 10
|
|
8
|
-
|
|
11
|
+
|
|
12
|
+
# Minimum interval between dispatch cycles
|
|
13
|
+
# @see https://github.com/ruby-shoryuken/shoryuken/issues/348#issuecomment-292847028
|
|
9
14
|
MIN_DISPATCH_INTERVAL = 0.1
|
|
10
15
|
|
|
16
|
+
# @return [String] the processing group name
|
|
11
17
|
attr_reader :group
|
|
12
18
|
|
|
19
|
+
# Initializes a new Manager for a processing group
|
|
20
|
+
#
|
|
21
|
+
# @param group [String] the processing group name
|
|
22
|
+
# @param fetcher [Shoryuken::Fetcher] the message fetcher
|
|
23
|
+
# @param polling_strategy [Shoryuken::Polling::BaseStrategy] the polling strategy
|
|
24
|
+
# @param concurrency [Integer] the maximum number of concurrent processors
|
|
25
|
+
# @param executor [Concurrent::ExecutorService] the executor for async operations
|
|
13
26
|
def initialize(group, fetcher, polling_strategy, concurrency, executor)
|
|
14
27
|
@group = group
|
|
15
28
|
@fetcher = fetcher
|
|
@@ -22,15 +35,24 @@ module Shoryuken
|
|
|
22
35
|
@dispatching_release_signal = ::Queue.new
|
|
23
36
|
end
|
|
24
37
|
|
|
38
|
+
# Starts the dispatch loop
|
|
39
|
+
#
|
|
40
|
+
# @return [void]
|
|
25
41
|
def start
|
|
26
42
|
fire_utilization_update_event
|
|
27
43
|
dispatch_loop
|
|
28
44
|
end
|
|
29
45
|
|
|
46
|
+
# Signals the manager to stop dispatching new messages
|
|
47
|
+
#
|
|
48
|
+
# @return [void]
|
|
30
49
|
def stop_new_dispatching
|
|
31
50
|
@stop_new_dispatching.make_true
|
|
32
51
|
end
|
|
33
52
|
|
|
53
|
+
# Waits for any in-progress dispatching to complete
|
|
54
|
+
#
|
|
55
|
+
# @return [void]
|
|
34
56
|
def await_dispatching_in_progress
|
|
35
57
|
# There might still be a dispatching on-going, as the response from SQS could take some time
|
|
36
58
|
# We don't want to stop the process before processing incoming messages, as they would stay "in-flight" for some time on SQS
|
|
@@ -38,12 +60,18 @@ module Shoryuken
|
|
|
38
60
|
@dispatching_release_signal.pop
|
|
39
61
|
end
|
|
40
62
|
|
|
63
|
+
# Checks if the manager is still running
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean] true if the manager is running
|
|
41
66
|
def running?
|
|
42
67
|
@running.true? && @executor.running?
|
|
43
68
|
end
|
|
44
69
|
|
|
45
70
|
private
|
|
46
71
|
|
|
72
|
+
# The main dispatch loop
|
|
73
|
+
#
|
|
74
|
+
# @return [void]
|
|
47
75
|
def dispatch_loop
|
|
48
76
|
if @stop_new_dispatching.true? || !running?
|
|
49
77
|
@dispatching_release_signal << 1
|
|
@@ -53,6 +81,9 @@ module Shoryuken
|
|
|
53
81
|
@executor.post { dispatch }
|
|
54
82
|
end
|
|
55
83
|
|
|
84
|
+
# Dispatches messages from a queue
|
|
85
|
+
#
|
|
86
|
+
# @return [void]
|
|
56
87
|
def dispatch
|
|
57
88
|
return unless running?
|
|
58
89
|
|
|
@@ -71,14 +102,24 @@ module Shoryuken
|
|
|
71
102
|
dispatch_loop
|
|
72
103
|
end
|
|
73
104
|
|
|
105
|
+
# Returns the count of busy processors
|
|
106
|
+
#
|
|
107
|
+
# @return [Integer] the number of busy processors
|
|
74
108
|
def busy
|
|
75
109
|
@busy_processors.value
|
|
76
110
|
end
|
|
77
111
|
|
|
112
|
+
# Returns the count of ready processors
|
|
113
|
+
#
|
|
114
|
+
# @return [Integer] the number of available processors
|
|
78
115
|
def ready
|
|
79
116
|
@max_processors - busy
|
|
80
117
|
end
|
|
81
118
|
|
|
119
|
+
# Handles completion of processor work
|
|
120
|
+
#
|
|
121
|
+
# @param queue [String] the queue name
|
|
122
|
+
# @return [void]
|
|
82
123
|
def processor_done(queue)
|
|
83
124
|
@busy_processors.decrement
|
|
84
125
|
fire_utilization_update_event
|
|
@@ -90,6 +131,11 @@ module Shoryuken
|
|
|
90
131
|
@polling_strategy.message_processed(queue)
|
|
91
132
|
end
|
|
92
133
|
|
|
134
|
+
# Assigns a message to a processor
|
|
135
|
+
#
|
|
136
|
+
# @param queue_name [String] the queue name
|
|
137
|
+
# @param sqs_msg [Aws::SQS::Types::Message, Array<Aws::SQS::Types::Message>] the message or batch
|
|
138
|
+
# @return [Concurrent::Promise, nil] the processing promise or nil if not running
|
|
93
139
|
def assign(queue_name, sqs_msg)
|
|
94
140
|
return unless running?
|
|
95
141
|
|
|
@@ -112,22 +158,38 @@ module Shoryuken
|
|
|
112
158
|
.rescue { processor_done(queue_name) }
|
|
113
159
|
end
|
|
114
160
|
|
|
161
|
+
# Dispatches a batch of messages from a queue
|
|
162
|
+
#
|
|
163
|
+
# @param queue [Shoryuken::Polling::QueueConfiguration] the queue configuration
|
|
164
|
+
# @return [void]
|
|
115
165
|
def dispatch_batch(queue)
|
|
116
166
|
batch = @fetcher.fetch(queue, BATCH_LIMIT)
|
|
117
167
|
@polling_strategy.messages_found(queue.name, batch.size)
|
|
118
168
|
assign(queue.name, patch_batch!(batch)) if batch.any?
|
|
119
169
|
end
|
|
120
170
|
|
|
171
|
+
# Dispatches individual messages from a queue
|
|
172
|
+
#
|
|
173
|
+
# @param queue [Shoryuken::Polling::QueueConfiguration] the queue configuration
|
|
174
|
+
# @return [void]
|
|
121
175
|
def dispatch_single_messages(queue)
|
|
122
176
|
messages = @fetcher.fetch(queue, ready)
|
|
123
177
|
@polling_strategy.messages_found(queue.name, messages.size)
|
|
124
178
|
messages.each { |message| assign(queue.name, message) }
|
|
125
179
|
end
|
|
126
180
|
|
|
181
|
+
# Checks if a queue uses batch message processing
|
|
182
|
+
#
|
|
183
|
+
# @param queue [Shoryuken::Polling::QueueConfiguration] the queue configuration
|
|
184
|
+
# @return [Boolean] true if the queue is configured for batch processing
|
|
127
185
|
def batched_queue?(queue)
|
|
128
186
|
Shoryuken.worker_registry.batch_receive_messages?(queue.name)
|
|
129
187
|
end
|
|
130
188
|
|
|
189
|
+
# Patches a batch array with a message_id method
|
|
190
|
+
#
|
|
191
|
+
# @param sqs_msgs [Array<Aws::SQS::Types::Message>] the batch of messages
|
|
192
|
+
# @return [Array<Aws::SQS::Types::Message>] the patched batch
|
|
131
193
|
def patch_batch!(sqs_msgs)
|
|
132
194
|
sqs_msgs.instance_eval do
|
|
133
195
|
def message_id
|
|
@@ -138,6 +200,10 @@ module Shoryuken
|
|
|
138
200
|
sqs_msgs
|
|
139
201
|
end
|
|
140
202
|
|
|
203
|
+
# Handles errors during dispatch
|
|
204
|
+
#
|
|
205
|
+
# @param ex [Exception] the exception that occurred
|
|
206
|
+
# @return [void]
|
|
141
207
|
def handle_dispatch_error(ex)
|
|
142
208
|
logger.error { "Manager failed: #{ex.message}" }
|
|
143
209
|
logger.error { ex.backtrace.join("\n") } unless ex.backtrace.nil?
|
|
@@ -147,6 +213,9 @@ module Shoryuken
|
|
|
147
213
|
@running.make_false
|
|
148
214
|
end
|
|
149
215
|
|
|
216
|
+
# Fires a utilization update event
|
|
217
|
+
#
|
|
218
|
+
# @return [void]
|
|
150
219
|
def fire_utilization_update_event
|
|
151
220
|
fire_event :utilization_update, false, {
|
|
152
221
|
group: @group,
|
data/lib/shoryuken/message.rb
CHANGED
|
@@ -1,9 +1,70 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
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
|
|
4
38
|
class Message
|
|
5
39
|
extend Forwardable
|
|
6
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
|
|
7
68
|
def_delegators(:data,
|
|
8
69
|
:message_id,
|
|
9
70
|
:receipt_handle,
|
|
@@ -13,8 +74,24 @@ module Shoryuken
|
|
|
13
74
|
:md5_of_message_attributes,
|
|
14
75
|
:message_attributes)
|
|
15
76
|
|
|
16
|
-
|
|
77
|
+
# @return [Aws::SQS::Client] The SQS client used for message operations
|
|
78
|
+
attr_accessor :client
|
|
17
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
|
|
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
|
|
18
95
|
def initialize(client, queue, data)
|
|
19
96
|
self.client = client
|
|
20
97
|
self.data = data
|
|
@@ -22,6 +99,12 @@ module Shoryuken
|
|
|
22
99
|
self.queue_name = queue.name
|
|
23
100
|
end
|
|
24
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
|
|
25
108
|
def delete
|
|
26
109
|
client.delete_message(
|
|
27
110
|
queue_url: queue_url,
|
|
@@ -29,12 +112,42 @@ module Shoryuken
|
|
|
29
112
|
)
|
|
30
113
|
end
|
|
31
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
|
|
32
127
|
def change_visibility(options)
|
|
33
128
|
client.change_message_visibility(
|
|
34
129
|
options.merge(queue_url: queue_url, receipt_handle: data.receipt_handle)
|
|
35
130
|
)
|
|
36
131
|
end
|
|
37
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
|
|
38
151
|
def visibility_timeout=(timeout)
|
|
39
152
|
client.change_message_visibility(
|
|
40
153
|
queue_url: queue_url,
|
|
@@ -1,72 +1,156 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Shoryuken
|
|
4
|
-
# Middleware
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
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
|
|
16
21
|
# end
|
|
17
|
-
# end
|
|
18
22
|
#
|
|
19
|
-
#
|
|
23
|
+
# ## Per-Worker Middleware Configuration
|
|
24
|
+
#
|
|
25
|
+
# Configure middleware for specific workers:
|
|
20
26
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
27
|
+
# class MyWorker
|
|
28
|
+
# include Shoryuken::Worker
|
|
29
|
+
#
|
|
30
|
+
# server_middleware do |chain|
|
|
31
|
+
# chain.add MyWorkerSpecificMiddleware
|
|
32
|
+
# end
|
|
24
33
|
# end
|
|
25
|
-
# end
|
|
26
34
|
#
|
|
27
|
-
#
|
|
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
|
|
28
44
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
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
|
|
32
58
|
# end
|
|
33
|
-
# end
|
|
34
59
|
#
|
|
35
|
-
#
|
|
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
|
|
36
73
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
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
|
|
42
82
|
# end
|
|
43
|
-
# end
|
|
44
83
|
#
|
|
84
|
+
# @see Shoryuken::Middleware::Chain Middleware chain management
|
|
85
|
+
# @see https://github.com/ruby-shoryuken/shoryuken/wiki/Middleware Comprehensive middleware guide
|
|
45
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.
|
|
46
90
|
class Chain
|
|
91
|
+
# @return [Array<Entry>] The ordered list of middleware entries
|
|
47
92
|
attr_reader :entries
|
|
48
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
|
|
49
102
|
def initialize
|
|
50
103
|
@entries = []
|
|
51
104
|
yield self if block_given?
|
|
52
105
|
end
|
|
53
106
|
|
|
107
|
+
# Creates a copy of this middleware chain.
|
|
108
|
+
#
|
|
109
|
+
# @return [Chain] A new chain with the same middleware entries
|
|
54
110
|
def dup
|
|
55
111
|
self.class.new.tap { |new_chain| new_chain.entries.replace(entries) }
|
|
56
112
|
end
|
|
57
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
|
|
58
120
|
def remove(klass)
|
|
59
121
|
entries.delete_if { |entry| entry.klass == klass }
|
|
60
122
|
end
|
|
61
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
|
|
62
131
|
def add(klass, *args)
|
|
63
132
|
entries << Entry.new(klass, *args) unless exists?(klass)
|
|
64
133
|
end
|
|
65
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
|
|
66
142
|
def prepend(klass, *args)
|
|
67
143
|
entries.insert(0, Entry.new(klass, *args)) unless exists?(klass)
|
|
68
144
|
end
|
|
69
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
|
|
70
154
|
def insert_before(oldklass, newklass, *args)
|
|
71
155
|
i = entries.index { |entry| entry.klass == newklass }
|
|
72
156
|
new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
|
|
@@ -74,6 +158,14 @@ module Shoryuken
|
|
|
74
158
|
entries.insert(i, new_entry)
|
|
75
159
|
end
|
|
76
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
|
|
77
169
|
def insert_after(oldklass, newklass, *args)
|
|
78
170
|
i = entries.index { |entry| entry.klass == newklass }
|
|
79
171
|
new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
|
|
@@ -81,18 +173,35 @@ module Shoryuken
|
|
|
81
173
|
entries.insert(i + 1, new_entry)
|
|
82
174
|
end
|
|
83
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
|
|
84
180
|
def exists?(klass)
|
|
85
181
|
entries.any? { |entry| entry.klass == klass }
|
|
86
182
|
end
|
|
87
183
|
|
|
184
|
+
# Creates instances of all middleware classes in the chain.
|
|
185
|
+
#
|
|
186
|
+
# @return [Array] Array of middleware instances
|
|
88
187
|
def retrieve
|
|
89
188
|
entries.map(&:make_new)
|
|
90
189
|
end
|
|
91
190
|
|
|
191
|
+
# Removes all middleware from the chain.
|
|
192
|
+
#
|
|
193
|
+
# @return [Array] Empty array
|
|
92
194
|
def clear
|
|
93
195
|
entries.clear
|
|
94
196
|
end
|
|
95
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
|
+
# @param final_action [Proc] the final action to perform after all middleware
|
|
204
|
+
# @return [void]
|
|
96
205
|
def invoke(*args, &final_action)
|
|
97
206
|
chain = retrieve.dup
|
|
98
207
|
traverse_chain = lambda do
|
|
@@ -105,18 +214,5 @@ module Shoryuken
|
|
|
105
214
|
traverse_chain.call
|
|
106
215
|
end
|
|
107
216
|
end
|
|
108
|
-
|
|
109
|
-
class Entry
|
|
110
|
-
attr_reader :klass
|
|
111
|
-
|
|
112
|
-
def initialize(klass, *args)
|
|
113
|
-
@klass = klass
|
|
114
|
-
@args = args
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def make_new
|
|
118
|
-
@klass.new(*@args)
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
217
|
end
|
|
122
218
|
end
|