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
|
@@ -1,28 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
4
|
+
# Loads and configures the Shoryuken environment from configuration files
|
|
5
|
+
# and command line options. Handles Rails integration and queue setup.
|
|
2
6
|
class EnvironmentLoader
|
|
7
|
+
# @return [Hash] the configuration options
|
|
3
8
|
attr_reader :options
|
|
4
9
|
|
|
10
|
+
# Sets up a new EnvironmentLoader with the given options
|
|
11
|
+
#
|
|
12
|
+
# @param options [Hash] configuration options
|
|
13
|
+
# @option options [String] :config_file path to the configuration file
|
|
14
|
+
# @option options [Boolean] :rails whether to initialize Rails
|
|
15
|
+
# @option options [String] :logfile path to the log file
|
|
16
|
+
# @option options [Boolean] :verbose whether to enable verbose logging
|
|
17
|
+
# @option options [String] :require path to require workers from
|
|
18
|
+
# @option options [Integer] :concurrency number of concurrent workers
|
|
19
|
+
# @return [Shoryuken::EnvironmentLoader] the configured instance
|
|
5
20
|
def self.setup_options(options)
|
|
6
21
|
instance = new(options)
|
|
7
22
|
instance.setup_options
|
|
8
23
|
instance
|
|
9
24
|
end
|
|
10
25
|
|
|
26
|
+
# Loads the environment for Rails console usage
|
|
27
|
+
#
|
|
28
|
+
# @return [void]
|
|
11
29
|
def self.load_for_rails_console
|
|
12
30
|
instance = setup_options(config_file: (Rails.root + 'config' + 'shoryuken.yml'))
|
|
13
31
|
instance.load
|
|
14
32
|
end
|
|
15
33
|
|
|
34
|
+
# Initializes a new EnvironmentLoader with the given options
|
|
35
|
+
#
|
|
36
|
+
# @param options [Hash] configuration options
|
|
37
|
+
# @option options [String] :config_file path to the configuration file
|
|
38
|
+
# @option options [Boolean] :rails whether to initialize Rails
|
|
39
|
+
# @option options [String] :logfile path to the log file
|
|
40
|
+
# @option options [Boolean] :verbose whether to enable verbose logging
|
|
41
|
+
# @option options [String] :require path to require workers from
|
|
42
|
+
# @option options [Integer] :concurrency number of concurrent workers
|
|
16
43
|
def initialize(options)
|
|
17
44
|
@options = options
|
|
18
45
|
end
|
|
19
46
|
|
|
47
|
+
# Sets up configuration options from file and initializes components
|
|
48
|
+
#
|
|
49
|
+
# @return [void]
|
|
20
50
|
def setup_options
|
|
21
51
|
initialize_rails if load_rails?
|
|
22
52
|
initialize_options
|
|
23
53
|
initialize_logger
|
|
24
54
|
end
|
|
25
55
|
|
|
56
|
+
# Loads the environment including queues and workers
|
|
57
|
+
#
|
|
58
|
+
# @return [void]
|
|
26
59
|
def load
|
|
27
60
|
prefix_active_job_queue_names
|
|
28
61
|
parse_queues
|
|
@@ -33,28 +66,40 @@ module Shoryuken
|
|
|
33
66
|
|
|
34
67
|
private
|
|
35
68
|
|
|
69
|
+
# Merges configuration file options with runtime options
|
|
70
|
+
#
|
|
71
|
+
# @return [void]
|
|
36
72
|
def initialize_options
|
|
37
73
|
Shoryuken.options.merge!(config_file_options)
|
|
38
74
|
Shoryuken.options.merge!(options)
|
|
39
75
|
end
|
|
40
76
|
|
|
77
|
+
# Reads and parses the configuration file
|
|
78
|
+
#
|
|
79
|
+
# @return [Hash] the parsed configuration options
|
|
41
80
|
def config_file_options
|
|
42
81
|
return {} unless (path = options[:config_file])
|
|
43
82
|
|
|
44
|
-
|
|
83
|
+
raise Errors::InvalidConfigurationError, "The supplied config file #{path} does not exist" unless File.exist?(path)
|
|
45
84
|
|
|
46
85
|
if (result = YAML.load(ERB.new(IO.read(path)).result))
|
|
47
|
-
|
|
86
|
+
Shoryuken::Helpers::HashUtils.deep_symbolize_keys(result)
|
|
48
87
|
else
|
|
49
88
|
{}
|
|
50
89
|
end
|
|
51
90
|
end
|
|
52
91
|
|
|
92
|
+
# Initializes the logger with file output and verbosity settings
|
|
93
|
+
#
|
|
94
|
+
# @return [void]
|
|
53
95
|
def initialize_logger
|
|
54
96
|
Shoryuken::Logging.initialize_logger(Shoryuken.options[:logfile]) if Shoryuken.options[:logfile]
|
|
55
97
|
Shoryuken.logger.level = Logger::DEBUG if Shoryuken.options[:verbose]
|
|
56
98
|
end
|
|
57
99
|
|
|
100
|
+
# Initializes the Rails environment
|
|
101
|
+
#
|
|
102
|
+
# @return [void]
|
|
58
103
|
def initialize_rails
|
|
59
104
|
# Adapted from: https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/cli.rb
|
|
60
105
|
|
|
@@ -78,18 +123,26 @@ module Shoryuken
|
|
|
78
123
|
end
|
|
79
124
|
end
|
|
80
125
|
if Shoryuken.active_job?
|
|
81
|
-
require '
|
|
82
|
-
require '
|
|
83
|
-
require '
|
|
126
|
+
require 'active_job/extensions'
|
|
127
|
+
require 'active_job/queue_adapters/shoryuken_adapter'
|
|
128
|
+
require 'active_job/queue_adapters/shoryuken_concurrent_send_adapter'
|
|
84
129
|
end
|
|
85
130
|
require File.expand_path('config/environment.rb')
|
|
86
131
|
end
|
|
87
132
|
end
|
|
88
133
|
|
|
134
|
+
# Checks if Rails should be loaded
|
|
135
|
+
#
|
|
136
|
+
# @return [Boolean] true if Rails should be initialized
|
|
89
137
|
def load_rails?
|
|
90
138
|
options[:rails]
|
|
91
139
|
end
|
|
92
140
|
|
|
141
|
+
# Prefixes a queue name with ActiveJob queue name prefix
|
|
142
|
+
#
|
|
143
|
+
# @param queue_name [String] the queue name to prefix
|
|
144
|
+
# @param weight [Integer] the queue weight
|
|
145
|
+
# @return [Array<String, Integer>] the prefixed queue name and weight
|
|
93
146
|
def prefix_active_job_queue_name(queue_name, weight)
|
|
94
147
|
return [queue_name, weight] if queue_name.start_with?('https://', 'arn:')
|
|
95
148
|
|
|
@@ -102,6 +155,9 @@ module Shoryuken
|
|
|
102
155
|
[prefixed_queue_name, weight]
|
|
103
156
|
end
|
|
104
157
|
|
|
158
|
+
# Prefixes all queue names with ActiveJob prefix if enabled
|
|
159
|
+
#
|
|
160
|
+
# @return [void]
|
|
105
161
|
def prefix_active_job_queue_names
|
|
106
162
|
return unless Shoryuken.active_job?
|
|
107
163
|
return unless Shoryuken.active_job_queue_name_prefixing?
|
|
@@ -121,10 +177,19 @@ module Shoryuken
|
|
|
121
177
|
end
|
|
122
178
|
end
|
|
123
179
|
|
|
180
|
+
# Parses a single queue and adds it to a group
|
|
181
|
+
#
|
|
182
|
+
# @param queue [String] the queue name
|
|
183
|
+
# @param weight [Integer] the queue weight
|
|
184
|
+
# @param group [String] the group name
|
|
185
|
+
# @return [void]
|
|
124
186
|
def parse_queue(queue, weight, group)
|
|
125
187
|
Shoryuken.add_queue(queue, [weight.to_i, 1].max, group)
|
|
126
188
|
end
|
|
127
189
|
|
|
190
|
+
# Parses all queues from configuration and adds them to groups
|
|
191
|
+
#
|
|
192
|
+
# @return [void]
|
|
128
193
|
def parse_queues
|
|
129
194
|
if Shoryuken.options[:queues].to_a.any?
|
|
130
195
|
Shoryuken.add_group('default', Shoryuken.options[:concurrency])
|
|
@@ -135,7 +200,7 @@ module Shoryuken
|
|
|
135
200
|
end
|
|
136
201
|
|
|
137
202
|
Shoryuken.options[:groups].to_a.each do |group, options|
|
|
138
|
-
Shoryuken.add_group(group, options[:concurrency], delay: options[:delay])
|
|
203
|
+
Shoryuken.add_group(group, options[:concurrency], delay: options[:delay], polling_strategy: options[:polling_strategy])
|
|
139
204
|
|
|
140
205
|
options[:queues].to_a.each do |queue, weight|
|
|
141
206
|
parse_queue(queue, weight, group)
|
|
@@ -143,6 +208,9 @@ module Shoryuken
|
|
|
143
208
|
end
|
|
144
209
|
end
|
|
145
210
|
|
|
211
|
+
# Requires worker files from the configured path
|
|
212
|
+
#
|
|
213
|
+
# @return [void]
|
|
146
214
|
def require_workers
|
|
147
215
|
required = Shoryuken.options[:require]
|
|
148
216
|
|
|
@@ -155,17 +223,19 @@ module Shoryuken
|
|
|
155
223
|
end
|
|
156
224
|
end
|
|
157
225
|
|
|
226
|
+
# Validates that all configured queues exist in SQS
|
|
227
|
+
#
|
|
228
|
+
# @return [void]
|
|
229
|
+
# @raise [ArgumentError] if any queues do not exist
|
|
158
230
|
def validate_queues
|
|
159
231
|
return Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.ungrouped_queues.empty?
|
|
160
232
|
|
|
161
233
|
non_existent_queues = []
|
|
162
234
|
|
|
163
235
|
Shoryuken.ungrouped_queues.uniq.each do |queue|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
non_existent_queues << queue
|
|
168
|
-
end
|
|
236
|
+
Shoryuken::Client.queues(queue)
|
|
237
|
+
rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
|
|
238
|
+
non_existent_queues << queue
|
|
169
239
|
end
|
|
170
240
|
|
|
171
241
|
return if non_existent_queues.none?
|
|
@@ -178,12 +248,12 @@ module Shoryuken
|
|
|
178
248
|
It's also possible that you don't have permission to access the specified queues.
|
|
179
249
|
MSG
|
|
180
250
|
|
|
181
|
-
|
|
182
|
-
ArgumentError,
|
|
183
|
-
error_msg
|
|
184
|
-
)
|
|
251
|
+
raise Errors::QueueNotFoundError, error_msg
|
|
185
252
|
end
|
|
186
253
|
|
|
254
|
+
# Validates that all queues have registered workers
|
|
255
|
+
#
|
|
256
|
+
# @return [void]
|
|
187
257
|
def validate_workers
|
|
188
258
|
return if Shoryuken.active_job?
|
|
189
259
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoryuken
|
|
4
|
+
# Namespace for all Shoryuken-specific errors.
|
|
5
|
+
# These provide more meaningful error types than generic Ruby exceptions,
|
|
6
|
+
# making it easier to rescue and handle specific failure cases.
|
|
7
|
+
module Errors
|
|
8
|
+
# Base class for all Shoryuken errors
|
|
9
|
+
BaseError = Class.new(StandardError)
|
|
10
|
+
|
|
11
|
+
# Raised when there is a configuration validation failure
|
|
12
|
+
InvalidConfigurationError = Class.new(BaseError)
|
|
13
|
+
|
|
14
|
+
# Raised when a specified SQS queue does not exist or cannot be accessed
|
|
15
|
+
QueueNotFoundError = Class.new(BaseError)
|
|
16
|
+
|
|
17
|
+
# Raised when worker registration fails due to conflicts
|
|
18
|
+
# (e.g., registering multiple workers for a batch queue)
|
|
19
|
+
InvalidWorkerRegistrationError = Class.new(BaseError)
|
|
20
|
+
|
|
21
|
+
# Raised when an invalid polling strategy is specified
|
|
22
|
+
InvalidPollingStrategyError = Class.new(BaseError)
|
|
23
|
+
|
|
24
|
+
# Raised when an invalid lifecycle event name is used
|
|
25
|
+
InvalidEventError = Class.new(BaseError)
|
|
26
|
+
|
|
27
|
+
# Raised when a delay exceeds the maximum allowed by SQS (15 minutes)
|
|
28
|
+
InvalidDelayError = Class.new(BaseError)
|
|
29
|
+
|
|
30
|
+
# Raised when a delay is used with a FIFO queue
|
|
31
|
+
FifoDelayNotSupportedError = Class.new(BaseError)
|
|
32
|
+
|
|
33
|
+
# Raised when an ARN format is invalid
|
|
34
|
+
InvalidArnError = Class.new(BaseError)
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/shoryuken/fetcher.rb
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
4
|
+
# Fetches messages from SQS queues.
|
|
5
|
+
# Handles message retrieval with automatic retry on connectivity errors.
|
|
2
6
|
class Fetcher
|
|
3
7
|
include Util
|
|
4
8
|
|
|
9
|
+
# Maximum number of messages that can be fetched in a single SQS request
|
|
5
10
|
FETCH_LIMIT = 10
|
|
6
11
|
|
|
12
|
+
# Initializes a new Fetcher for a processing group
|
|
13
|
+
#
|
|
14
|
+
# @param group [String] the processing group name
|
|
7
15
|
def initialize(group)
|
|
8
16
|
@group = group
|
|
9
17
|
end
|
|
10
18
|
|
|
19
|
+
# Fetches messages from a queue with automatic retry
|
|
20
|
+
#
|
|
21
|
+
# @param queue [Shoryuken::Polling::QueueConfiguration] the queue configuration
|
|
22
|
+
# @param limit [Integer] the maximum number of messages to fetch
|
|
23
|
+
# @return [Array<Aws::SQS::Types::Message>] the fetched messages
|
|
11
24
|
def fetch(queue, limit)
|
|
12
25
|
fetch_with_auto_retry(3) do
|
|
13
26
|
started_at = Time.now
|
|
@@ -25,18 +38,23 @@ module Shoryuken
|
|
|
25
38
|
|
|
26
39
|
private
|
|
27
40
|
|
|
41
|
+
# Fetches with automatic retry on errors
|
|
42
|
+
#
|
|
43
|
+
# @param max_attempts [Integer] maximum number of retry attempts
|
|
44
|
+
# @yield the fetch operation to retry
|
|
45
|
+
# @return [Object] the result of the block
|
|
28
46
|
def fetch_with_auto_retry(max_attempts)
|
|
29
47
|
attempts = 0
|
|
30
48
|
|
|
31
49
|
begin
|
|
32
50
|
yield
|
|
33
|
-
rescue =>
|
|
51
|
+
rescue => e
|
|
34
52
|
# Tries to auto retry connectivity errors
|
|
35
53
|
raise if attempts >= max_attempts
|
|
36
54
|
|
|
37
55
|
attempts += 1
|
|
38
56
|
|
|
39
|
-
logger.debug { "Retrying fetch attempt #{attempts} for #{
|
|
57
|
+
logger.debug { "Retrying fetch attempt #{attempts} for #{e.message}" }
|
|
40
58
|
|
|
41
59
|
sleep((1..5).to_a.sample)
|
|
42
60
|
|
|
@@ -44,6 +62,11 @@ module Shoryuken
|
|
|
44
62
|
end
|
|
45
63
|
end
|
|
46
64
|
|
|
65
|
+
# Receives messages from an SQS queue
|
|
66
|
+
#
|
|
67
|
+
# @param queue [Shoryuken::Polling::QueueConfiguration] the queue configuration
|
|
68
|
+
# @param limit [Integer] the maximum number of messages to receive
|
|
69
|
+
# @return [Array<Aws::SQS::Types::Message>, nil] the received messages
|
|
47
70
|
def receive_messages(queue, limit)
|
|
48
71
|
options = receive_options(queue)
|
|
49
72
|
|
|
@@ -58,6 +81,13 @@ module Shoryuken
|
|
|
58
81
|
shoryuken_queue.receive_messages(options)
|
|
59
82
|
end
|
|
60
83
|
|
|
84
|
+
# Determines the maximum number of messages to fetch
|
|
85
|
+
#
|
|
86
|
+
# @param shoryuken_queue [Shoryuken::Queue] the queue instance
|
|
87
|
+
# @param limit [Integer] the requested limit
|
|
88
|
+
# @param options [Hash] receive options that may contain max_number_of_messages
|
|
89
|
+
# @option options [Integer] :max_number_of_messages optional override for max messages
|
|
90
|
+
# @return [Integer] the maximum number of messages to fetch
|
|
61
91
|
def max_number_of_messages(shoryuken_queue, limit, options)
|
|
62
92
|
# For FIFO queues we want to make sure we process one message per group at a time
|
|
63
93
|
# if we set max_number_of_messages greater than 1,
|
|
@@ -73,6 +103,10 @@ module Shoryuken
|
|
|
73
103
|
[limit, FETCH_LIMIT, options[:max_number_of_messages]].compact.min
|
|
74
104
|
end
|
|
75
105
|
|
|
106
|
+
# Returns the receive options for a queue
|
|
107
|
+
#
|
|
108
|
+
# @param queue [Shoryuken::Polling::QueueConfiguration] the queue configuration
|
|
109
|
+
# @return [Hash] the receive options hash
|
|
76
110
|
def receive_options(queue)
|
|
77
111
|
options = Shoryuken.sqs_client_receive_message_opts[queue.name]
|
|
78
112
|
options ||= Shoryuken.sqs_client_receive_message_opts[@group]
|
|
@@ -80,8 +114,12 @@ module Shoryuken
|
|
|
80
114
|
options.to_h.dup
|
|
81
115
|
end
|
|
82
116
|
|
|
117
|
+
# Checks if a queue uses batch message processing
|
|
118
|
+
#
|
|
119
|
+
# @param queue [Shoryuken::Queue] the queue to check
|
|
120
|
+
# @return [Boolean] true if the queue is configured for batch processing
|
|
83
121
|
def batched_queue?(queue)
|
|
84
122
|
Shoryuken.worker_registry.batch_receive_messages?(queue.name)
|
|
85
123
|
end
|
|
86
124
|
end
|
|
87
|
-
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoryuken
|
|
4
|
+
# Helper classes providing thread-safe utilities and data structures
|
|
5
|
+
module Helpers
|
|
6
|
+
# A thread-safe boolean implementation using AtomicCounter as base.
|
|
7
|
+
# Drop-in replacement for Concurrent::AtomicBoolean without external dependencies.
|
|
8
|
+
# Uses 1 for true and 0 for false internally.
|
|
9
|
+
class AtomicBoolean < AtomicCounter
|
|
10
|
+
# Prevent misuse of counter operations on a boolean
|
|
11
|
+
undef_method :increment, :decrement
|
|
12
|
+
|
|
13
|
+
# Initializes a new AtomicBoolean
|
|
14
|
+
#
|
|
15
|
+
# @param initial_value [Boolean] the initial value
|
|
16
|
+
def initialize(initial_value = false)
|
|
17
|
+
super(initial_value ? 1 : 0)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Gets the current value as a boolean
|
|
21
|
+
#
|
|
22
|
+
# @return [Boolean] the current value
|
|
23
|
+
def value
|
|
24
|
+
super != 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Sets the value to true
|
|
28
|
+
#
|
|
29
|
+
# @return [Boolean] true
|
|
30
|
+
def make_true
|
|
31
|
+
@mutex.synchronize { @value = 1 }
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Sets the value to false
|
|
36
|
+
#
|
|
37
|
+
# @return [Boolean] false
|
|
38
|
+
def make_false
|
|
39
|
+
@mutex.synchronize { @value = 0 }
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Checks if the value is true
|
|
44
|
+
#
|
|
45
|
+
# @return [Boolean] true if the value is true
|
|
46
|
+
def true?
|
|
47
|
+
@mutex.synchronize { @value != 0 }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Checks if the value is false
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean] true if the value is false
|
|
53
|
+
def false?
|
|
54
|
+
@mutex.synchronize { @value == 0 }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoryuken
|
|
4
|
+
module Helpers
|
|
5
|
+
# A thread-safe counter implementation using Ruby's Mutex.
|
|
6
|
+
#
|
|
7
|
+
# This class provides atomic operations for incrementing, decrementing, and reading
|
|
8
|
+
# integer values in a thread-safe manner. It serves as a drop-in replacement for
|
|
9
|
+
# Concurrent::AtomicFixnum without requiring external dependencies.
|
|
10
|
+
#
|
|
11
|
+
# The implementation uses a Mutex to ensure thread safety across all Ruby
|
|
12
|
+
# implementations including JRuby, where true parallelism makes atomic operations
|
|
13
|
+
# critical for data integrity.
|
|
14
|
+
#
|
|
15
|
+
# @note This class is optimized for scenarios with frequent atomic updates
|
|
16
|
+
# and occasional reads, such as tracking active worker counts.
|
|
17
|
+
#
|
|
18
|
+
# @example Basic usage
|
|
19
|
+
# counter = Shoryuken::Helpers::AtomicCounter.new(0)
|
|
20
|
+
# counter.increment # => 1
|
|
21
|
+
# counter.increment # => 2
|
|
22
|
+
# counter.value # => 2
|
|
23
|
+
# counter.decrement # => 1
|
|
24
|
+
#
|
|
25
|
+
# @example Tracking busy processors
|
|
26
|
+
# @busy_processors = Shoryuken::Helpers::AtomicCounter.new(0)
|
|
27
|
+
#
|
|
28
|
+
# # When starting work
|
|
29
|
+
# @busy_processors.increment
|
|
30
|
+
#
|
|
31
|
+
# # When work is done
|
|
32
|
+
# @busy_processors.decrement
|
|
33
|
+
#
|
|
34
|
+
# # Check current load
|
|
35
|
+
# current_busy = @busy_processors.value
|
|
36
|
+
class AtomicCounter
|
|
37
|
+
# Creates a new atomic counter with the specified initial value.
|
|
38
|
+
#
|
|
39
|
+
# @param initial_value [Integer] The starting value for the counter
|
|
40
|
+
# @return [AtomicCounter] A new atomic counter instance
|
|
41
|
+
#
|
|
42
|
+
# @example Create counter starting at zero
|
|
43
|
+
# counter = Shoryuken::Helpers::AtomicCounter.new
|
|
44
|
+
# counter.value # => 0
|
|
45
|
+
#
|
|
46
|
+
# @example Create counter with custom initial value
|
|
47
|
+
# counter = Shoryuken::Helpers::AtomicCounter.new(100)
|
|
48
|
+
# counter.value # => 100
|
|
49
|
+
def initialize(initial_value = 0)
|
|
50
|
+
@mutex = Mutex.new
|
|
51
|
+
@value = initial_value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns the current value of the counter.
|
|
55
|
+
#
|
|
56
|
+
# This operation is thread-safe and will return a consistent value
|
|
57
|
+
# even when called concurrently with increment/decrement operations.
|
|
58
|
+
#
|
|
59
|
+
# @return [Integer] The current counter value
|
|
60
|
+
#
|
|
61
|
+
# @example Reading the current value
|
|
62
|
+
# counter = Shoryuken::Helpers::AtomicCounter.new(42)
|
|
63
|
+
# counter.value # => 42
|
|
64
|
+
def value
|
|
65
|
+
@mutex.synchronize { @value }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Atomically increments the counter by 1 and returns the new value.
|
|
69
|
+
#
|
|
70
|
+
# This operation is thread-safe and can be called concurrently from
|
|
71
|
+
# multiple threads without risk of data corruption or lost updates.
|
|
72
|
+
#
|
|
73
|
+
# @return [Integer] The new counter value after incrementing
|
|
74
|
+
#
|
|
75
|
+
# @example Incrementing the counter
|
|
76
|
+
# counter = Shoryuken::Helpers::AtomicCounter.new(5)
|
|
77
|
+
# counter.increment # => 6
|
|
78
|
+
# counter.increment # => 7
|
|
79
|
+
def increment
|
|
80
|
+
@mutex.synchronize { @value += 1 }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Atomically decrements the counter by 1 and returns the new value.
|
|
84
|
+
#
|
|
85
|
+
# This operation is thread-safe and can be called concurrently from
|
|
86
|
+
# multiple threads without risk of data corruption or lost updates.
|
|
87
|
+
# The counter can go negative if decremented below zero.
|
|
88
|
+
#
|
|
89
|
+
# @return [Integer] The new counter value after decrementing
|
|
90
|
+
#
|
|
91
|
+
# @example Decrementing the counter
|
|
92
|
+
# counter = Shoryuken::Helpers::AtomicCounter.new(5)
|
|
93
|
+
# counter.decrement # => 4
|
|
94
|
+
# counter.decrement # => 3
|
|
95
|
+
#
|
|
96
|
+
# @example Counter can go negative
|
|
97
|
+
# counter = Shoryuken::Helpers::AtomicCounter.new(0)
|
|
98
|
+
# counter.decrement # => -1
|
|
99
|
+
def decrement
|
|
100
|
+
@mutex.synchronize { @value -= 1 }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|