shoryuken 6.2.1 → 7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/push.yml +36 -0
- data/.github/workflows/specs.yml +49 -44
- data/.github/workflows/verify-action-pins.yml +16 -0
- data/.gitignore +4 -1
- data/.rspec +3 -1
- data/.rubocop.yml +6 -1
- data/.ruby-version +1 -0
- data/.yard-lint.yml +279 -0
- data/CHANGELOG.md +308 -139
- data/Gemfile +1 -8
- data/Gemfile.lint +9 -0
- data/Gemfile.lint.lock +69 -0
- data/README.md +16 -33
- data/Rakefile +6 -10
- data/bin/clean_sqs +52 -0
- data/bin/cli/base.rb +22 -2
- data/bin/cli/sqs.rb +74 -7
- data/bin/integrations +275 -0
- data/bin/scenario +154 -0
- data/bin/shoryuken +3 -2
- data/docker-compose.yml +6 -0
- data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +20 -6
- 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 +11 -1
- data/lib/shoryuken/client.rb +16 -0
- data/lib/shoryuken/default_exception_handler.rb +11 -0
- data/lib/shoryuken/default_worker_registry.rb +39 -11
- data/lib/shoryuken/environment_loader.rb +85 -15
- data/lib/shoryuken/errors.rb +36 -0
- data/lib/shoryuken/fetcher.rb +41 -3
- data/lib/shoryuken/helpers/atomic_boolean.rb +58 -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 +80 -0
- data/lib/shoryuken/inline_message.rb +22 -0
- data/lib/shoryuken/launcher.rb +55 -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 +43 -15
- data/lib/shoryuken/manager.rb +84 -5
- data/lib/shoryuken/message.rb +116 -1
- data/lib/shoryuken/middleware/chain.rb +141 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +10 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +12 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +37 -11
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +34 -3
- data/lib/shoryuken/middleware/server/non_retryable_exception.rb +95 -0
- data/lib/shoryuken/middleware/server/timing.rb +13 -0
- data/lib/shoryuken/options.rb +154 -13
- data/lib/shoryuken/polling/base_strategy.rb +127 -0
- data/lib/shoryuken/polling/queue_configuration.rb +103 -0
- data/lib/shoryuken/polling/strict_priority.rb +41 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +44 -0
- data/lib/shoryuken/processor.rb +37 -3
- data/lib/shoryuken/queue.rb +99 -8
- data/lib/shoryuken/runner.rb +54 -16
- data/lib/shoryuken/util.rb +32 -7
- data/lib/shoryuken/version.rb +4 -1
- data/lib/shoryuken/worker/default_executor.rb +23 -1
- data/lib/shoryuken/worker/inline_executor.rb +33 -2
- data/lib/shoryuken/worker.rb +224 -0
- data/lib/shoryuken/worker_registry.rb +35 -0
- data/lib/shoryuken.rb +27 -38
- data/renovate.json +62 -0
- data/shoryuken.gemspec +8 -4
- 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/keyword_arguments/keyword_arguments_spec.rb +63 -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/custom_group_polling_strategy/custom_group_polling_strategy_spec.rb +87 -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/non_retryable_exception/non_retryable_exception_spec.rb +149 -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 +225 -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} +5 -4
- data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +6 -5
- data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +2 -4
- data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +9 -10
- data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +1 -2
- data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +10 -9
- data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +23 -26
- data/spec/lib/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
- data/spec/lib/shoryuken/helpers/atomic_counter_spec.rb +177 -0
- data/spec/lib/shoryuken/helpers/atomic_hash_spec.rb +307 -0
- data/spec/lib/shoryuken/helpers/hash_utils_spec.rb +145 -0
- data/spec/lib/shoryuken/helpers/string_utils_spec.rb +124 -0
- data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/lib/shoryuken/helpers_integration_spec.rb +96 -0
- data/spec/lib/shoryuken/inline_message_spec.rb +196 -0
- data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +23 -2
- data/spec/lib/shoryuken/logging_spec.rb +242 -0
- data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +1 -2
- data/spec/lib/shoryuken/message_spec.rb +109 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +1 -1
- 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_delete_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +51 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +1 -1
- data/spec/lib/shoryuken/middleware/server/non_retryable_exception_spec.rb +214 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +49 -6
- data/spec/lib/shoryuken/polling/base_strategy_spec.rb +280 -0
- data/spec/lib/shoryuken/polling/queue_configuration_spec.rb +195 -0
- data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +2 -3
- data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +1 -3
- data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +2 -2
- data/spec/lib/shoryuken/version_spec.rb +17 -0
- data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +1 -1
- data/spec/lib/shoryuken/worker/inline_executor_spec.rb +105 -0
- data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
- data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +15 -11
- data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +1 -1
- data/spec/shared_examples_for_active_job.rb +40 -15
- data/spec/spec_helper.rb +48 -2
- metadata +295 -101
- data/.codeclimate.yml +0 -20
- data/.devcontainer/Dockerfile +0 -17
- data/.devcontainer/base.Dockerfile +0 -43
- data/.devcontainer/devcontainer.json +0 -35
- data/.github/FUNDING.yml +0 -12
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/stale.yml +0 -20
- data/.reek.yml +0 -5
- data/Appraisals +0 -42
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/aws_sdk_core_2.gemfile +0 -21
- data/gemfiles/rails_4_2.gemfile +0 -20
- data/gemfiles/rails_5_2.gemfile +0 -21
- data/gemfiles/rails_6_0.gemfile +0 -21
- data/gemfiles/rails_6_1.gemfile +0 -21
- data/gemfiles/rails_7_0.gemfile +0 -22
- data/lib/shoryuken/core_ext.rb +0 -69
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -103
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
- data/lib/shoryuken/polling/base.rb +0 -67
- data/shoryuken.jpg +0 -0
- data/spec/integration/launcher_spec.rb +0 -128
- data/spec/shoryuken/core_ext_spec.rb +0 -40
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -7
- data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -84
- data/spec/shoryuken/worker/inline_executor_spec.rb +0 -49
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoryuken
|
|
4
|
+
# Polling strategies for determining which queue to fetch messages from next
|
|
5
|
+
module Polling
|
|
6
|
+
# Abstract base class for queue polling strategies.
|
|
7
|
+
#
|
|
8
|
+
# This class defines the interface that all polling strategies must implement
|
|
9
|
+
# to manage queue selection and message flow control in Shoryuken workers.
|
|
10
|
+
# Polling strategies determine which queue to fetch messages from next and
|
|
11
|
+
# how to handle scenarios where queues have no messages available.
|
|
12
|
+
#
|
|
13
|
+
# @abstract Subclass and override {#next_queue}, {#messages_found}, and {#active_queues}
|
|
14
|
+
# to implement a custom polling strategy.
|
|
15
|
+
#
|
|
16
|
+
# @example Implementing a custom polling strategy
|
|
17
|
+
# class CustomStrategy < BaseStrategy
|
|
18
|
+
# def initialize(queues)
|
|
19
|
+
# @queues = queues
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# def next_queue
|
|
23
|
+
# # Return next queue to poll
|
|
24
|
+
# @queues.sample
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# def messages_found(queue, count)
|
|
28
|
+
# # Handle result of polling
|
|
29
|
+
# logger.info "Found #{count} messages in #{queue}"
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# def active_queues
|
|
33
|
+
# # Return list of active queues
|
|
34
|
+
# @queues
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# @see WeightedRoundRobin
|
|
39
|
+
# @see StrictPriority
|
|
40
|
+
class BaseStrategy
|
|
41
|
+
include Util
|
|
42
|
+
|
|
43
|
+
# Returns the next queue to poll for messages.
|
|
44
|
+
#
|
|
45
|
+
# This method should return a QueueConfiguration object representing
|
|
46
|
+
# the next queue that should be polled for messages, or nil if no
|
|
47
|
+
# queues are currently available for polling.
|
|
48
|
+
#
|
|
49
|
+
# @abstract Subclasses must implement this method
|
|
50
|
+
# @return [QueueConfiguration, nil] Next queue to poll, or nil if none available
|
|
51
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
|
52
|
+
def next_queue
|
|
53
|
+
fail NotImplementedError
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Called when messages are found (or not found) in a queue.
|
|
57
|
+
#
|
|
58
|
+
# This method is invoked after polling a queue to inform the strategy
|
|
59
|
+
# about the number of messages that were retrieved. Strategies can use
|
|
60
|
+
# this information to make decisions about future polling behavior,
|
|
61
|
+
# such as pausing empty queues or adjusting queue weights.
|
|
62
|
+
#
|
|
63
|
+
# @abstract Subclasses must implement this method
|
|
64
|
+
# @param _queue [String] The name of the queue that was polled
|
|
65
|
+
# @param _messages_found [Integer] The number of messages retrieved
|
|
66
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
|
67
|
+
def messages_found(_queue, _messages_found)
|
|
68
|
+
fail NotImplementedError
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Called when a message from a queue has been processed.
|
|
72
|
+
#
|
|
73
|
+
# This optional callback is invoked after a message has been successfully
|
|
74
|
+
# processed by a worker. Strategies can use this information for cleanup
|
|
75
|
+
# or to adjust their polling behavior.
|
|
76
|
+
#
|
|
77
|
+
# @param _queue [String] The name of the queue whose message was processed
|
|
78
|
+
# @return [void]
|
|
79
|
+
def message_processed(_queue); end
|
|
80
|
+
|
|
81
|
+
# Returns the list of currently active queues.
|
|
82
|
+
#
|
|
83
|
+
# This method should return an array representing the queues that are
|
|
84
|
+
# currently active and available for polling. The format may vary by
|
|
85
|
+
# strategy implementation.
|
|
86
|
+
#
|
|
87
|
+
# @abstract Subclasses must implement this method
|
|
88
|
+
# @return [Array] List of active queues
|
|
89
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
|
90
|
+
def active_queues
|
|
91
|
+
fail NotImplementedError
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Compares this strategy with another object for equality.
|
|
95
|
+
#
|
|
96
|
+
# Two strategies are considered equal if they have the same active queues.
|
|
97
|
+
# This method also supports comparison with Array objects for backward
|
|
98
|
+
# compatibility.
|
|
99
|
+
#
|
|
100
|
+
# @param other [Object] Object to compare with
|
|
101
|
+
# @return [Boolean] true if strategies are equivalent
|
|
102
|
+
def ==(other)
|
|
103
|
+
case other
|
|
104
|
+
when Array
|
|
105
|
+
@queues == other
|
|
106
|
+
else
|
|
107
|
+
if other.respond_to?(:active_queues)
|
|
108
|
+
active_queues == other.active_queues
|
|
109
|
+
else
|
|
110
|
+
false
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns the delay time for pausing empty queues.
|
|
116
|
+
#
|
|
117
|
+
# This method returns the amount of time (in seconds) that empty queues
|
|
118
|
+
# should be paused before being polled again. The delay can be set at
|
|
119
|
+
# the strategy level or falls back to the global Shoryuken delay setting.
|
|
120
|
+
#
|
|
121
|
+
# @return [Float] Delay time in seconds
|
|
122
|
+
def delay
|
|
123
|
+
@delay || Shoryuken.options[:delay].to_f
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoryuken
|
|
4
|
+
module Polling
|
|
5
|
+
# Configuration object representing a queue and its associated options.
|
|
6
|
+
#
|
|
7
|
+
# This class encapsulates a queue name along with any polling-specific
|
|
8
|
+
# options or metadata. It provides a structured way to pass queue
|
|
9
|
+
# information between polling strategies and the message fetching system.
|
|
10
|
+
#
|
|
11
|
+
# The class extends Struct to provide attribute accessors for name and options
|
|
12
|
+
# while adding custom behavior for equality comparison and string representation.
|
|
13
|
+
#
|
|
14
|
+
# @example Creating a basic queue configuration
|
|
15
|
+
# config = QueueConfiguration.new('my_queue', {})
|
|
16
|
+
# config.name # => 'my_queue'
|
|
17
|
+
# config.options # => {}
|
|
18
|
+
#
|
|
19
|
+
# @example Creating a queue configuration with options
|
|
20
|
+
# config = QueueConfiguration.new('priority_queue', { priority: :high })
|
|
21
|
+
# config.name # => 'priority_queue'
|
|
22
|
+
# config.options # => { priority: :high }
|
|
23
|
+
#
|
|
24
|
+
# @example Comparing configurations
|
|
25
|
+
# config1 = QueueConfiguration.new('queue', {})
|
|
26
|
+
# config2 = QueueConfiguration.new('queue', {})
|
|
27
|
+
# config1 == config2 # => true
|
|
28
|
+
# config1 == 'queue' # => true (when options are empty)
|
|
29
|
+
#
|
|
30
|
+
# @attr_reader [String] name The name of the queue
|
|
31
|
+
# @attr_reader [Hash] options Additional options or metadata for the queue
|
|
32
|
+
QueueConfiguration = Struct.new(:name, :options) do
|
|
33
|
+
# Generates a hash value based on the queue name.
|
|
34
|
+
#
|
|
35
|
+
# This method ensures that QueueConfiguration objects can be used
|
|
36
|
+
# as hash keys and that configurations with the same queue name
|
|
37
|
+
# will have the same hash value regardless of their options.
|
|
38
|
+
#
|
|
39
|
+
# @return [Integer] Hash value based on the queue name
|
|
40
|
+
def hash
|
|
41
|
+
name.hash
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Compares this configuration with another object for equality.
|
|
45
|
+
#
|
|
46
|
+
# Two QueueConfiguration objects are equal if they have the same name
|
|
47
|
+
# and options. For convenience, a configuration with empty options can
|
|
48
|
+
# also be compared directly with a string queue name.
|
|
49
|
+
#
|
|
50
|
+
# @param other [Object] The object to compare with
|
|
51
|
+
# @return [Boolean] true if the objects are considered equal
|
|
52
|
+
#
|
|
53
|
+
# @example Comparing with another QueueConfiguration
|
|
54
|
+
# config1 = QueueConfiguration.new('queue', {})
|
|
55
|
+
# config2 = QueueConfiguration.new('queue', {})
|
|
56
|
+
# config1 == config2 # => true
|
|
57
|
+
#
|
|
58
|
+
# @example Comparing with a string (only when options are empty)
|
|
59
|
+
# config = QueueConfiguration.new('queue', {})
|
|
60
|
+
# config == 'queue' # => true
|
|
61
|
+
#
|
|
62
|
+
# config_with_options = QueueConfiguration.new('queue', { weight: 5 })
|
|
63
|
+
# config_with_options == 'queue' # => false
|
|
64
|
+
def ==(other)
|
|
65
|
+
case other
|
|
66
|
+
when String
|
|
67
|
+
if options.empty?
|
|
68
|
+
name == other
|
|
69
|
+
else
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
alias_method :eql?, :==
|
|
78
|
+
|
|
79
|
+
# Returns a string representation of the queue configuration.
|
|
80
|
+
#
|
|
81
|
+
# For configurations with empty options, returns just the queue name.
|
|
82
|
+
# For configurations with options, returns a detailed representation
|
|
83
|
+
# showing both the name and the options hash.
|
|
84
|
+
#
|
|
85
|
+
# @return [String] String representation of the configuration
|
|
86
|
+
#
|
|
87
|
+
# @example Simple queue without options
|
|
88
|
+
# config = QueueConfiguration.new('simple_queue', {})
|
|
89
|
+
# config.to_s # => 'simple_queue'
|
|
90
|
+
#
|
|
91
|
+
# @example Queue with options
|
|
92
|
+
# config = QueueConfiguration.new('complex_queue', { priority: :high })
|
|
93
|
+
# config.to_s # => '#<QueueConfiguration complex_queue options={:priority=>:high}>'
|
|
94
|
+
def to_s
|
|
95
|
+
if options&.empty?
|
|
96
|
+
name
|
|
97
|
+
else
|
|
98
|
+
"#<QueueConfiguration #{name} options=#{options.inspect}>"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
2
4
|
module Polling
|
|
5
|
+
# A polling strategy that processes queues in strict priority order.
|
|
6
|
+
# Higher priority queues are always processed before lower priority queues.
|
|
7
|
+
# Queues are temporarily paused when no messages are found.
|
|
3
8
|
class StrictPriority < BaseStrategy
|
|
9
|
+
# Initializes a new StrictPriority polling strategy
|
|
10
|
+
#
|
|
11
|
+
# @param queues [Array<String>] array of queue names, with higher priority queues appearing more frequently
|
|
12
|
+
# @param delay [Float, nil] delay in seconds before unpausing empty queues
|
|
4
13
|
def initialize(queues, delay = nil)
|
|
5
14
|
# Priority ordering of the queues, highest priority first
|
|
6
15
|
@queues = queues
|
|
@@ -17,11 +26,19 @@ module Shoryuken
|
|
|
17
26
|
reset_next_queue
|
|
18
27
|
end
|
|
19
28
|
|
|
29
|
+
# Returns the next queue to poll based on strict priority
|
|
30
|
+
#
|
|
31
|
+
# @return [QueueConfiguration, nil] the next queue configuration or nil if all paused
|
|
20
32
|
def next_queue
|
|
21
33
|
next_queue = next_active_queue
|
|
22
34
|
next_queue.nil? ? nil : QueueConfiguration.new(next_queue, {})
|
|
23
35
|
end
|
|
24
36
|
|
|
37
|
+
# Handles the result of polling a queue
|
|
38
|
+
#
|
|
39
|
+
# @param queue [String] the queue name
|
|
40
|
+
# @param messages_found [Integer] number of messages found
|
|
41
|
+
# @return [void]
|
|
25
42
|
def messages_found(queue, messages_found)
|
|
26
43
|
if messages_found == 0
|
|
27
44
|
pause(queue)
|
|
@@ -30,6 +47,9 @@ module Shoryuken
|
|
|
30
47
|
end
|
|
31
48
|
end
|
|
32
49
|
|
|
50
|
+
# Returns the list of active (non-paused) queues with their priorities
|
|
51
|
+
#
|
|
52
|
+
# @return [Array<Array>] array of [queue_name, priority] pairs
|
|
33
53
|
def active_queues
|
|
34
54
|
@queues
|
|
35
55
|
.reverse
|
|
@@ -38,6 +58,10 @@ module Shoryuken
|
|
|
38
58
|
.reverse
|
|
39
59
|
end
|
|
40
60
|
|
|
61
|
+
# Called when a message from a queue has been processed
|
|
62
|
+
#
|
|
63
|
+
# @param queue [String] the queue name
|
|
64
|
+
# @return [void]
|
|
41
65
|
def message_processed(queue)
|
|
42
66
|
if queue_paused?(queue)
|
|
43
67
|
logger.debug "Unpausing #{queue}"
|
|
@@ -47,6 +71,9 @@ module Shoryuken
|
|
|
47
71
|
|
|
48
72
|
private
|
|
49
73
|
|
|
74
|
+
# Finds the next active (non-paused) queue
|
|
75
|
+
#
|
|
76
|
+
# @return [String, nil] the queue name or nil if all paused
|
|
50
77
|
def next_active_queue
|
|
51
78
|
reset_next_queue if queues_unpaused_since?
|
|
52
79
|
|
|
@@ -60,6 +87,9 @@ module Shoryuken
|
|
|
60
87
|
nil
|
|
61
88
|
end
|
|
62
89
|
|
|
90
|
+
# Checks if any queues have been unpaused since last check
|
|
91
|
+
#
|
|
92
|
+
# @return [Boolean] true if queues were unpaused
|
|
63
93
|
def queues_unpaused_since?
|
|
64
94
|
last = @last_unpause_check
|
|
65
95
|
now = @last_unpause_check = Time.now
|
|
@@ -67,14 +97,25 @@ module Shoryuken
|
|
|
67
97
|
last && @paused_until.values.any? { |t| t > last && t <= now }
|
|
68
98
|
end
|
|
69
99
|
|
|
100
|
+
# Resets the next queue index to start from the highest priority
|
|
101
|
+
#
|
|
102
|
+
# @return [void]
|
|
70
103
|
def reset_next_queue
|
|
71
104
|
@next_queue_index = 0
|
|
72
105
|
end
|
|
73
106
|
|
|
107
|
+
# Checks if a queue is currently paused
|
|
108
|
+
#
|
|
109
|
+
# @param queue [String] the queue name
|
|
110
|
+
# @return [Boolean] true if the queue is paused
|
|
74
111
|
def queue_paused?(queue)
|
|
75
112
|
@paused_until[queue] > Time.now
|
|
76
113
|
end
|
|
77
114
|
|
|
115
|
+
# Pauses a queue for the configured delay time
|
|
116
|
+
#
|
|
117
|
+
# @param queue [String] the queue name to pause
|
|
118
|
+
# @return [void]
|
|
78
119
|
def pause(queue)
|
|
79
120
|
return unless delay > 0
|
|
80
121
|
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
2
4
|
module Polling
|
|
5
|
+
# A polling strategy that processes queues in weighted round-robin order.
|
|
6
|
+
# Queue weights determine how often each queue is polled relative to others.
|
|
7
|
+
# Queues are temporarily paused when no messages are found.
|
|
3
8
|
class WeightedRoundRobin < BaseStrategy
|
|
9
|
+
# Initializes a new WeightedRoundRobin polling strategy
|
|
10
|
+
#
|
|
11
|
+
# @param queues [Array<String>] array of queue names, with weights indicated by repetition
|
|
12
|
+
# @param delay [Float, nil] delay in seconds before unpausing empty queues
|
|
4
13
|
def initialize(queues, delay = nil)
|
|
5
14
|
@initial_queues = queues
|
|
6
15
|
@queues = queues.dup.uniq
|
|
@@ -8,6 +17,9 @@ module Shoryuken
|
|
|
8
17
|
@delay = delay
|
|
9
18
|
end
|
|
10
19
|
|
|
20
|
+
# Returns the next queue to poll in round-robin order
|
|
21
|
+
#
|
|
22
|
+
# @return [QueueConfiguration, nil] the next queue configuration or nil if all paused
|
|
11
23
|
def next_queue
|
|
12
24
|
unpause_queues
|
|
13
25
|
queue = @queues.shift
|
|
@@ -17,6 +29,11 @@ module Shoryuken
|
|
|
17
29
|
QueueConfiguration.new(queue, {})
|
|
18
30
|
end
|
|
19
31
|
|
|
32
|
+
# Handles the result of polling a queue, adjusting weight if messages were found
|
|
33
|
+
#
|
|
34
|
+
# @param queue [String] the queue name
|
|
35
|
+
# @param messages_found [Integer] number of messages found
|
|
36
|
+
# @return [void]
|
|
20
37
|
def messages_found(queue, messages_found)
|
|
21
38
|
if messages_found == 0
|
|
22
39
|
pause(queue)
|
|
@@ -31,10 +48,17 @@ module Shoryuken
|
|
|
31
48
|
end
|
|
32
49
|
end
|
|
33
50
|
|
|
51
|
+
# Returns the list of active queues with their current weights
|
|
52
|
+
#
|
|
53
|
+
# @return [Array<Array>] array of [queue_name, weight] pairs
|
|
34
54
|
def active_queues
|
|
35
55
|
unparse_queues(@queues)
|
|
36
56
|
end
|
|
37
57
|
|
|
58
|
+
# Called when a message from a queue has been processed
|
|
59
|
+
#
|
|
60
|
+
# @param queue [String] the queue name
|
|
61
|
+
# @return [void]
|
|
38
62
|
def message_processed(queue)
|
|
39
63
|
paused_queue = @paused_queues.find { |_time, name| name == queue }
|
|
40
64
|
return unless paused_queue
|
|
@@ -44,6 +68,10 @@ module Shoryuken
|
|
|
44
68
|
|
|
45
69
|
private
|
|
46
70
|
|
|
71
|
+
# Pauses a queue by removing it from active rotation
|
|
72
|
+
#
|
|
73
|
+
# @param queue [String] the queue name to pause
|
|
74
|
+
# @return [void]
|
|
47
75
|
def pause(queue)
|
|
48
76
|
return unless @queues.delete(queue)
|
|
49
77
|
|
|
@@ -51,6 +79,9 @@ module Shoryuken
|
|
|
51
79
|
logger.debug "Paused #{queue}"
|
|
52
80
|
end
|
|
53
81
|
|
|
82
|
+
# Unpauses queues whose delay has expired
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
54
85
|
def unpause_queues
|
|
55
86
|
return if @paused_queues.empty?
|
|
56
87
|
return if Time.now < @paused_queues.first[0]
|
|
@@ -60,14 +91,27 @@ module Shoryuken
|
|
|
60
91
|
logger.debug "Unpaused #{pause[1]}"
|
|
61
92
|
end
|
|
62
93
|
|
|
94
|
+
# Returns the current weight of a queue in the active rotation
|
|
95
|
+
#
|
|
96
|
+
# @param queue [String] the queue name
|
|
97
|
+
# @return [Integer] the current weight
|
|
63
98
|
def current_queue_weight(queue)
|
|
64
99
|
queue_weight(@queues, queue)
|
|
65
100
|
end
|
|
66
101
|
|
|
102
|
+
# Returns the maximum configured weight of a queue
|
|
103
|
+
#
|
|
104
|
+
# @param queue [String] the queue name
|
|
105
|
+
# @return [Integer] the maximum weight
|
|
67
106
|
def maximum_queue_weight(queue)
|
|
68
107
|
queue_weight(@initial_queues, queue)
|
|
69
108
|
end
|
|
70
109
|
|
|
110
|
+
# Counts how many times a queue appears in the given array
|
|
111
|
+
#
|
|
112
|
+
# @param queues [Array<String>] the array to count in
|
|
113
|
+
# @param queue [String] the queue name to count
|
|
114
|
+
# @return [Integer] the count
|
|
71
115
|
def queue_weight(queues, queue)
|
|
72
116
|
queues.count { |q| q == queue }
|
|
73
117
|
end
|
data/lib/shoryuken/processor.rb
CHANGED
|
@@ -1,21 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
4
|
+
# Processes SQS messages by invoking the appropriate worker.
|
|
5
|
+
# Handles middleware chain execution and exception handling.
|
|
2
6
|
class Processor
|
|
3
7
|
include Util
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
# @return [String] the queue name
|
|
10
|
+
attr_reader :queue
|
|
11
|
+
|
|
12
|
+
# @return [Aws::SQS::Types::Message, Array<Aws::SQS::Types::Message>] the message or batch
|
|
13
|
+
attr_reader :sqs_msg
|
|
6
14
|
|
|
15
|
+
# Processes a message from a queue
|
|
16
|
+
#
|
|
17
|
+
# @param queue [String] the queue name
|
|
18
|
+
# @param sqs_msg [Aws::SQS::Types::Message, Array<Aws::SQS::Types::Message>] the message or batch
|
|
19
|
+
# @return [Object] the result of the worker's perform method
|
|
7
20
|
def self.process(queue, sqs_msg)
|
|
8
21
|
new(queue, sqs_msg).process
|
|
9
22
|
end
|
|
10
23
|
|
|
24
|
+
# Initializes a new Processor
|
|
25
|
+
#
|
|
26
|
+
# @param queue [String] the queue name
|
|
27
|
+
# @param sqs_msg [Aws::SQS::Types::Message, Array<Aws::SQS::Types::Message>] the message or batch
|
|
11
28
|
def initialize(queue, sqs_msg)
|
|
12
29
|
@queue = queue
|
|
13
30
|
@sqs_msg = sqs_msg
|
|
14
31
|
end
|
|
15
32
|
|
|
33
|
+
# Processes the message through the middleware chain and worker
|
|
34
|
+
#
|
|
35
|
+
# @return [Object] the result of the worker's perform method
|
|
16
36
|
def process
|
|
17
37
|
worker_perform = proc do
|
|
18
38
|
return logger.error { "No worker found for #{queue}" } unless worker
|
|
39
|
+
|
|
19
40
|
Shoryuken::Logging.with_context("#{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id}") do
|
|
20
41
|
worker.class.server_middleware.invoke(worker, queue, sqs_msg, body) do
|
|
21
42
|
worker.perform(sqs_msg, body)
|
|
@@ -30,26 +51,39 @@ module Shoryuken
|
|
|
30
51
|
else
|
|
31
52
|
worker_perform.call
|
|
32
53
|
end
|
|
33
|
-
rescue Exception =>
|
|
34
|
-
Array(Shoryuken.exception_handlers).each { |handler| handler.call(
|
|
54
|
+
rescue Exception => e
|
|
55
|
+
Array(Shoryuken.exception_handlers).each { |handler| handler.call(e, queue, sqs_msg) }
|
|
35
56
|
|
|
36
57
|
raise
|
|
37
58
|
end
|
|
38
59
|
|
|
39
60
|
private
|
|
40
61
|
|
|
62
|
+
# Fetches the worker instance for processing
|
|
63
|
+
#
|
|
64
|
+
# @return [Object, nil] the worker instance or nil if not found
|
|
41
65
|
def worker
|
|
42
66
|
@_worker ||= Shoryuken.worker_registry.fetch_worker(queue, sqs_msg)
|
|
43
67
|
end
|
|
44
68
|
|
|
69
|
+
# Returns the worker class
|
|
70
|
+
#
|
|
71
|
+
# @return [Class] the worker class
|
|
45
72
|
def worker_class
|
|
46
73
|
worker.class
|
|
47
74
|
end
|
|
48
75
|
|
|
76
|
+
# Parses the message body or bodies for batch processing
|
|
77
|
+
#
|
|
78
|
+
# @return [Object, Array<Object>] the parsed body or array of bodies
|
|
49
79
|
def body
|
|
50
80
|
@_body ||= sqs_msg.is_a?(Array) ? sqs_msg.map(&method(:parse_body)) : parse_body(sqs_msg)
|
|
51
81
|
end
|
|
52
82
|
|
|
83
|
+
# Parses a single message body
|
|
84
|
+
#
|
|
85
|
+
# @param sqs_msg [Aws::SQS::Types::Message] the message to parse
|
|
86
|
+
# @return [Object] the parsed message body
|
|
53
87
|
def parse_body(sqs_msg)
|
|
54
88
|
BodyParser.parse(worker_class, sqs_msg)
|
|
55
89
|
end
|