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
data/lib/shoryuken/queue.rb
CHANGED
|
@@ -1,18 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
4
|
+
# Represents an SQS queue and provides methods for sending and receiving messages.
|
|
5
|
+
# Handles both standard and FIFO queues, automatically adding required FIFO attributes.
|
|
2
6
|
class Queue
|
|
3
7
|
include Util
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
# SQS attribute name for FIFO queue identification
|
|
10
|
+
FIFO_ATTR = 'FifoQueue'
|
|
11
|
+
|
|
12
|
+
# Default message group ID used for FIFO queues
|
|
13
|
+
MESSAGE_GROUP_ID = 'ShoryukenMessage'
|
|
14
|
+
|
|
15
|
+
# SQS attribute name for visibility timeout
|
|
16
|
+
VISIBILITY_TIMEOUT_ATTR = 'VisibilityTimeout'
|
|
17
|
+
|
|
18
|
+
# @return [String] the queue name
|
|
19
|
+
attr_accessor :name
|
|
20
|
+
|
|
21
|
+
# @return [Aws::SQS::Client] the SQS client
|
|
22
|
+
attr_accessor :client
|
|
8
23
|
|
|
9
|
-
|
|
24
|
+
# @return [String] the queue URL
|
|
25
|
+
attr_accessor :url
|
|
10
26
|
|
|
27
|
+
# Initializes a new Queue instance
|
|
28
|
+
#
|
|
29
|
+
# @param client [Aws::SQS::Client] the SQS client
|
|
30
|
+
# @param name_or_url_or_arn [String] the queue name, URL, or ARN
|
|
11
31
|
def initialize(client, name_or_url_or_arn)
|
|
12
32
|
self.client = client
|
|
13
33
|
set_name_and_url(name_or_url_or_arn)
|
|
14
34
|
end
|
|
15
35
|
|
|
36
|
+
# Returns the visibility timeout for the queue
|
|
37
|
+
#
|
|
38
|
+
# @return [Integer] the visibility timeout in seconds
|
|
16
39
|
def visibility_timeout
|
|
17
40
|
# Always lookup for the latest visibility when cache is disabled
|
|
18
41
|
# setting it to nil, forces re-lookup
|
|
@@ -20,6 +43,11 @@ module Shoryuken
|
|
|
20
43
|
@_visibility_timeout ||= queue_attributes.attributes[VISIBILITY_TIMEOUT_ATTR].to_i
|
|
21
44
|
end
|
|
22
45
|
|
|
46
|
+
# Deletes messages from the queue in batch
|
|
47
|
+
#
|
|
48
|
+
# @param options [Hash] options for delete_message_batch
|
|
49
|
+
# @option options [Array<Hash>] :entries entries to delete with id and receipt_handle
|
|
50
|
+
# @return [Boolean] true if any messages failed to delete
|
|
23
51
|
def delete_messages(options)
|
|
24
52
|
failed_messages = client.delete_message_batch(
|
|
25
53
|
options.merge(queue_url: url)
|
|
@@ -31,6 +59,15 @@ module Shoryuken
|
|
|
31
59
|
end
|
|
32
60
|
end
|
|
33
61
|
|
|
62
|
+
# Sends a single message to the queue
|
|
63
|
+
#
|
|
64
|
+
# @param options [Hash, String] message options or message body string
|
|
65
|
+
# @option options [String] :message_body the message body
|
|
66
|
+
# @option options [Integer] :delay_seconds delay before message becomes visible
|
|
67
|
+
# @option options [Hash] :message_attributes custom message attributes
|
|
68
|
+
# @option options [String] :message_group_id FIFO queue message group ID
|
|
69
|
+
# @option options [String] :message_deduplication_id FIFO queue deduplication ID
|
|
70
|
+
# @return [Aws::SQS::Types::SendMessageResult] the send result
|
|
34
71
|
def send_message(options)
|
|
35
72
|
options = sanitize_message!(options).merge(queue_url: url)
|
|
36
73
|
|
|
@@ -39,18 +76,35 @@ module Shoryuken
|
|
|
39
76
|
end
|
|
40
77
|
end
|
|
41
78
|
|
|
79
|
+
# Sends multiple messages to the queue in batch
|
|
80
|
+
#
|
|
81
|
+
# @param options [Hash, Array] batch options or array of message bodies/hashes
|
|
82
|
+
# @option options [Array<Hash>] :entries message entries to send
|
|
83
|
+
# @return [Aws::SQS::Types::SendMessageBatchResult] the batch send result
|
|
42
84
|
def send_messages(options)
|
|
43
85
|
client.send_message_batch(sanitize_messages!(options).merge(queue_url: url))
|
|
44
86
|
end
|
|
45
87
|
|
|
88
|
+
# Receives messages from the queue
|
|
89
|
+
#
|
|
90
|
+
# @param options [Hash] options for receive_message
|
|
91
|
+
# @option options [Integer] :max_number_of_messages maximum messages to receive
|
|
92
|
+
# @option options [Integer] :visibility_timeout visibility timeout for received messages
|
|
93
|
+
# @option options [Integer] :wait_time_seconds long polling wait time
|
|
94
|
+
# @option options [Array<String>] :attribute_names SQS attributes to retrieve
|
|
95
|
+
# @option options [Array<String>] :message_attribute_names message attributes to retrieve
|
|
96
|
+
# @return [Array<Shoryuken::Message>] the received messages
|
|
46
97
|
def receive_messages(options)
|
|
47
98
|
messages = client.receive_message(options.merge(queue_url: url)).messages || []
|
|
48
99
|
messages.map { |m| Message.new(client, self, m) }
|
|
49
100
|
end
|
|
50
101
|
|
|
102
|
+
# Checks if the queue is a FIFO queue
|
|
103
|
+
#
|
|
104
|
+
# @return [Boolean] true if the queue is a FIFO queue
|
|
51
105
|
def fifo?
|
|
52
106
|
# Make sure the memoization work with boolean to avoid multiple calls to SQS
|
|
53
|
-
# see https://github.com/
|
|
107
|
+
# see https://github.com/ruby-shoryuken/shoryuken/pull/529
|
|
54
108
|
return @_fifo if defined?(@_fifo)
|
|
55
109
|
|
|
56
110
|
@_fifo = queue_attributes.attributes[FIFO_ATTR] == 'true'
|
|
@@ -59,32 +113,51 @@ module Shoryuken
|
|
|
59
113
|
|
|
60
114
|
private
|
|
61
115
|
|
|
116
|
+
# Initializes the FIFO attribute by calling fifo?
|
|
117
|
+
#
|
|
118
|
+
# @return [Boolean] whether the queue is FIFO
|
|
62
119
|
def initialize_fifo_attribute
|
|
63
120
|
# calling fifo? will also initialize it
|
|
64
121
|
fifo?
|
|
65
122
|
end
|
|
66
123
|
|
|
124
|
+
# Sets the queue name and URL from a queue name
|
|
125
|
+
#
|
|
126
|
+
# @param name [String] the queue name
|
|
127
|
+
# @return [void]
|
|
67
128
|
def set_by_name(name) # rubocop:disable Naming/AccessorMethodName
|
|
68
129
|
self.name = name
|
|
69
130
|
self.url = client.get_queue_url(queue_name: name).queue_url
|
|
70
131
|
end
|
|
71
132
|
|
|
133
|
+
# Sets the queue name and URL from a queue URL
|
|
134
|
+
#
|
|
135
|
+
# @param url [String] the queue URL
|
|
136
|
+
# @return [void]
|
|
72
137
|
def set_by_url(url) # rubocop:disable Naming/AccessorMethodName
|
|
73
138
|
self.name = url.split('/').last
|
|
74
139
|
self.url = url
|
|
75
140
|
end
|
|
76
141
|
|
|
142
|
+
# Converts an ARN to a queue URL
|
|
143
|
+
#
|
|
144
|
+
# @param arn_str [String] the ARN string
|
|
145
|
+
# @return [String] the queue URL
|
|
77
146
|
def arn_to_url(arn_str)
|
|
78
147
|
*, region, account_id, resource = arn_str.split(':')
|
|
79
148
|
|
|
80
149
|
required = [region, account_id, resource].map(&:to_s)
|
|
81
150
|
valid = required.none?(&:empty?)
|
|
82
151
|
|
|
83
|
-
|
|
152
|
+
raise Errors::InvalidArnError, "Invalid ARN: #{arn_str}. A valid ARN must include: region, account_id and resource." unless valid
|
|
84
153
|
|
|
85
154
|
"https://sqs.#{region}.amazonaws.com/#{account_id}/#{resource}"
|
|
86
155
|
end
|
|
87
156
|
|
|
157
|
+
# Sets the queue name and URL from a name, URL, or ARN
|
|
158
|
+
#
|
|
159
|
+
# @param name_or_url_or_arn [String] the queue identifier
|
|
160
|
+
# @return [void]
|
|
88
161
|
def set_name_and_url(name_or_url_or_arn) # rubocop:disable Naming/AccessorMethodName
|
|
89
162
|
if name_or_url_or_arn.include?('://')
|
|
90
163
|
set_by_url(name_or_url_or_arn)
|
|
@@ -104,16 +177,24 @@ module Shoryuken
|
|
|
104
177
|
end
|
|
105
178
|
|
|
106
179
|
set_by_name(name_or_url_or_arn)
|
|
107
|
-
rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
|
|
108
|
-
raise
|
|
180
|
+
rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
|
|
181
|
+
raise Errors::QueueNotFoundError, "The specified queue #{name_or_url_or_arn} does not exist."
|
|
109
182
|
end
|
|
110
183
|
|
|
184
|
+
# Returns the queue attributes from SQS
|
|
185
|
+
#
|
|
186
|
+
# @return [Aws::SQS::Types::GetQueueAttributesResult] the queue attributes
|
|
111
187
|
def queue_attributes
|
|
112
188
|
# Note: Retrieving all queue attributes as requesting `FifoQueue` on non-FIFO queue raises error.
|
|
113
189
|
# See issue: https://github.com/aws/aws-sdk-ruby/issues/1350
|
|
114
190
|
client.get_queue_attributes(queue_url: url, attribute_names: ['All'])
|
|
115
191
|
end
|
|
116
192
|
|
|
193
|
+
# Sanitizes a batch of messages, converting to proper format
|
|
194
|
+
#
|
|
195
|
+
# @param options [Hash, Array] batch options or array of messages
|
|
196
|
+
# @option options [Array<Hash>] :entries message entries
|
|
197
|
+
# @return [Hash] the sanitized options with entries key
|
|
117
198
|
def sanitize_messages!(options)
|
|
118
199
|
if options.is_a?(Array)
|
|
119
200
|
entries = options.map.with_index do |m, index|
|
|
@@ -128,6 +209,11 @@ module Shoryuken
|
|
|
128
209
|
options
|
|
129
210
|
end
|
|
130
211
|
|
|
212
|
+
# Adds FIFO attributes to message options if needed
|
|
213
|
+
#
|
|
214
|
+
# @param options [Hash] the message options
|
|
215
|
+
# @option options [String] :message_body the message body
|
|
216
|
+
# @return [Hash] the options with FIFO attributes added
|
|
131
217
|
def add_fifo_attributes!(options)
|
|
132
218
|
return unless fifo?
|
|
133
219
|
|
|
@@ -137,6 +223,11 @@ module Shoryuken
|
|
|
137
223
|
options
|
|
138
224
|
end
|
|
139
225
|
|
|
226
|
+
# Sanitizes a single message, converting body to JSON if needed
|
|
227
|
+
#
|
|
228
|
+
# @param options [Hash, String] message options or body string
|
|
229
|
+
# @option options [String, Hash] :message_body the message body
|
|
230
|
+
# @return [Hash] the sanitized message options
|
|
140
231
|
def sanitize_message!(options)
|
|
141
232
|
options = { message_body: options } if options.is_a?(String)
|
|
142
233
|
|
data/lib/shoryuken/runner.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
$stdout.sync = true
|
|
2
4
|
|
|
3
5
|
require 'singleton'
|
|
@@ -7,25 +9,32 @@ require 'erb'
|
|
|
7
9
|
require 'shoryuken'
|
|
8
10
|
|
|
9
11
|
module Shoryuken
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
class Shutdown < Interrupt; end
|
|
13
|
-
|
|
12
|
+
# Runs the Shoryuken server process.
|
|
13
|
+
# Handles signal trapping, daemonization, and lifecycle management.
|
|
14
14
|
class Runner
|
|
15
15
|
include Util
|
|
16
16
|
include Singleton
|
|
17
17
|
|
|
18
|
+
# @return [Shoryuken::Launcher, nil] the launcher instance, or nil if not yet initialized
|
|
19
|
+
attr_reader :launcher
|
|
20
|
+
|
|
21
|
+
# Runs the Shoryuken server with the given options
|
|
22
|
+
#
|
|
23
|
+
# @param options [Hash] runtime configuration options
|
|
24
|
+
# @option options [Boolean] :daemon whether to daemonize the process
|
|
25
|
+
# @option options [String] :pidfile path to the PID file
|
|
26
|
+
# @option options [String] :logfile path to the log file
|
|
27
|
+
# @option options [String] :config_file path to the configuration file
|
|
28
|
+
# @return [void]
|
|
18
29
|
def run(options)
|
|
19
30
|
self_read, self_write = IO.pipe
|
|
20
31
|
|
|
21
32
|
%w[INT TERM USR1 TSTP TTIN].each do |sig|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
self_write.puts(sig)
|
|
25
|
-
end
|
|
26
|
-
rescue ArgumentError
|
|
27
|
-
puts "Signal #{sig} not supported"
|
|
33
|
+
trap sig do
|
|
34
|
+
self_write.puts(sig)
|
|
28
35
|
end
|
|
36
|
+
rescue ArgumentError
|
|
37
|
+
puts "Signal #{sig} not supported"
|
|
29
38
|
end
|
|
30
39
|
|
|
31
40
|
loader = EnvironmentLoader.setup_options(options)
|
|
@@ -52,12 +61,18 @@ module Shoryuken
|
|
|
52
61
|
end
|
|
53
62
|
end
|
|
54
63
|
|
|
64
|
+
# Checks if the server is healthy
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean] true if the launcher is running and healthy
|
|
55
67
|
def healthy?
|
|
56
68
|
(@launcher && @launcher.healthy?) || false
|
|
57
69
|
end
|
|
58
70
|
|
|
59
71
|
private
|
|
60
72
|
|
|
73
|
+
# Initializes the Concurrent Ruby logger
|
|
74
|
+
#
|
|
75
|
+
# @return [void]
|
|
61
76
|
def initialize_concurrent_logger
|
|
62
77
|
return unless Shoryuken.logger
|
|
63
78
|
|
|
@@ -66,6 +81,12 @@ module Shoryuken
|
|
|
66
81
|
end
|
|
67
82
|
end
|
|
68
83
|
|
|
84
|
+
# Daemonizes the process
|
|
85
|
+
#
|
|
86
|
+
# @param options [Hash] options containing daemon and logfile settings
|
|
87
|
+
# @option options [Boolean] :daemon whether to daemonize
|
|
88
|
+
# @option options [String] :logfile path to the log file for daemon output
|
|
89
|
+
# @return [void]
|
|
69
90
|
def daemonize(options)
|
|
70
91
|
return unless options[:daemon]
|
|
71
92
|
|
|
@@ -77,11 +98,9 @@ module Shoryuken
|
|
|
77
98
|
Process.daemon(true, true)
|
|
78
99
|
|
|
79
100
|
files_to_reopen.each do |file|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
rescue ::Exception
|
|
84
|
-
end
|
|
101
|
+
file.reopen file.path, 'a+'
|
|
102
|
+
file.sync = true
|
|
103
|
+
rescue ::Exception
|
|
85
104
|
end
|
|
86
105
|
|
|
87
106
|
[$stdout, $stderr].each do |io|
|
|
@@ -93,25 +112,39 @@ module Shoryuken
|
|
|
93
112
|
$stdin.reopen('/dev/null')
|
|
94
113
|
end
|
|
95
114
|
|
|
115
|
+
# Writes the process ID to a file
|
|
116
|
+
#
|
|
117
|
+
# @param options [Hash] options containing the pidfile path
|
|
118
|
+
# @option options [String] :pidfile path to write the PID file
|
|
119
|
+
# @return [void]
|
|
96
120
|
def write_pid(options)
|
|
97
121
|
return unless (path = options[:pidfile])
|
|
98
122
|
|
|
99
123
|
File.open(path, 'w') { |f| f.puts(Process.pid) }
|
|
100
124
|
end
|
|
101
125
|
|
|
126
|
+
# Executes a soft shutdown on USR1 signal
|
|
127
|
+
#
|
|
128
|
+
# @return [void]
|
|
102
129
|
def execute_soft_shutdown
|
|
103
|
-
logger.info { 'Received USR1, will soft shutdown
|
|
130
|
+
logger.info { 'Received USR1, will soft shutdown' }
|
|
104
131
|
|
|
105
132
|
@launcher.stop
|
|
106
133
|
exit 0
|
|
107
134
|
end
|
|
108
135
|
|
|
136
|
+
# Executes a terminal stop on TSTP signal
|
|
137
|
+
#
|
|
138
|
+
# @return [void]
|
|
109
139
|
def execute_terminal_stop
|
|
110
140
|
logger.info { 'Received TSTP, will stop accepting new work' }
|
|
111
141
|
|
|
112
142
|
@launcher.stop
|
|
113
143
|
end
|
|
114
144
|
|
|
145
|
+
# Prints backtraces of all threads
|
|
146
|
+
#
|
|
147
|
+
# @return [void]
|
|
115
148
|
def print_threads_backtrace
|
|
116
149
|
Thread.list.each do |thread|
|
|
117
150
|
logger.info { "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}" }
|
|
@@ -123,6 +156,11 @@ module Shoryuken
|
|
|
123
156
|
end
|
|
124
157
|
end
|
|
125
158
|
|
|
159
|
+
# Handles incoming signals
|
|
160
|
+
#
|
|
161
|
+
# @param sig [String] the signal name
|
|
162
|
+
# @return [void]
|
|
163
|
+
# @raise [Interrupt] on TERM or INT signals
|
|
126
164
|
def handle_signal(sig)
|
|
127
165
|
logger.debug "Got #{sig} signal"
|
|
128
166
|
|
data/lib/shoryuken/util.rb
CHANGED
|
@@ -1,41 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
4
|
+
# Utility methods shared across Shoryuken classes.
|
|
5
|
+
# Provides logging, event firing, and helper methods.
|
|
2
6
|
module Util
|
|
7
|
+
# Returns the Shoryuken logger
|
|
8
|
+
#
|
|
9
|
+
# @return [Logger] the configured logger
|
|
3
10
|
def logger
|
|
4
11
|
Shoryuken.logger
|
|
5
12
|
end
|
|
6
13
|
|
|
14
|
+
# Fires a lifecycle event to all registered handlers
|
|
15
|
+
#
|
|
16
|
+
# @param event [Symbol] the event name to fire
|
|
17
|
+
# @param reverse [Boolean] whether to call handlers in reverse order
|
|
18
|
+
# @param event_options [Hash] options to pass to event handlers
|
|
19
|
+
# @return [void]
|
|
7
20
|
def fire_event(event, reverse = false, event_options = {})
|
|
8
21
|
logger.debug { "Firing '#{event}' lifecycle event" }
|
|
9
22
|
arr = Shoryuken.options[:lifecycle_events][event]
|
|
10
23
|
arr.reverse! if reverse
|
|
11
24
|
arr.each do |block|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
logger.warn "#{ex.class.name}: #{ex.message}"
|
|
17
|
-
end
|
|
25
|
+
block.call(event_options)
|
|
26
|
+
rescue => e
|
|
27
|
+
logger.warn(event: event)
|
|
28
|
+
logger.warn "#{e.class.name}: #{e.message}"
|
|
18
29
|
end
|
|
19
30
|
end
|
|
20
31
|
|
|
32
|
+
# Calculates elapsed time in milliseconds
|
|
33
|
+
#
|
|
34
|
+
# @param started_at [Time] the start time
|
|
35
|
+
# @return [Float] elapsed time in milliseconds
|
|
21
36
|
def elapsed(started_at)
|
|
22
37
|
# elapsed in ms
|
|
23
38
|
(Time.now - started_at) * 1000
|
|
24
39
|
end
|
|
25
40
|
|
|
41
|
+
# Converts a queue array to a hash of queue names and weights
|
|
42
|
+
#
|
|
43
|
+
# @param queues [Array<String>] array of queue names with possible duplicates
|
|
44
|
+
# @return [Array<Array>] array of [queue_name, weight] pairs
|
|
26
45
|
def unparse_queues(queues)
|
|
27
46
|
queues.each_with_object({}) do |name, queue_and_weights|
|
|
28
47
|
queue_and_weights[name] = queue_and_weights[name].to_i + 1
|
|
29
48
|
end.to_a
|
|
30
49
|
end
|
|
31
50
|
|
|
51
|
+
# Returns a display name for the worker processing a message
|
|
52
|
+
#
|
|
53
|
+
# @param worker_class [Class] the worker class
|
|
54
|
+
# @param sqs_msg [Aws::SQS::Types::Message, Array] the message or batch
|
|
55
|
+
# @param body [Object, nil] the parsed message body
|
|
56
|
+
# @return [String] the worker display name
|
|
32
57
|
def worker_name(worker_class, sqs_msg, body = nil)
|
|
33
58
|
if Shoryuken.active_job? \
|
|
34
59
|
&& !sqs_msg.is_a?(Array) \
|
|
35
60
|
&& sqs_msg.message_attributes \
|
|
36
61
|
&& sqs_msg.message_attributes['shoryuken_class'] \
|
|
37
62
|
&& sqs_msg.message_attributes['shoryuken_class'][:string_value] \
|
|
38
|
-
== ActiveJob::
|
|
63
|
+
== 'Shoryuken::ActiveJob::JobWrapper' \
|
|
39
64
|
&& body
|
|
40
65
|
|
|
41
66
|
"ActiveJob/#{body['job_class']}"
|
data/lib/shoryuken/version.rb
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
2
4
|
module Worker
|
|
5
|
+
# Default executor that sends messages to SQS for asynchronous processing.
|
|
6
|
+
# This is the standard executor used in production environments.
|
|
3
7
|
class DefaultExecutor
|
|
4
8
|
class << self
|
|
9
|
+
# Enqueues a job for asynchronous processing via SQS
|
|
10
|
+
#
|
|
11
|
+
# @param worker_class [Class] the worker class that will process the message
|
|
12
|
+
# @param body [Object] the message body
|
|
13
|
+
# @param options [Hash] additional SQS message options
|
|
14
|
+
# @option options [Hash] :message_attributes custom message attributes
|
|
15
|
+
# @option options [String] :queue override the default queue
|
|
16
|
+
# @return [Aws::SQS::Types::SendMessageResult] the send result
|
|
5
17
|
def perform_async(worker_class, body, options = {})
|
|
6
18
|
options[:message_attributes] ||= {}
|
|
7
19
|
options[:message_attributes]['shoryuken_class'] = {
|
|
@@ -16,6 +28,16 @@ module Shoryuken
|
|
|
16
28
|
Shoryuken::Client.queues(queue).send_message(options)
|
|
17
29
|
end
|
|
18
30
|
|
|
31
|
+
# Enqueues a job for delayed processing via SQS
|
|
32
|
+
#
|
|
33
|
+
# @param worker_class [Class] the worker class that will process the message
|
|
34
|
+
# @param interval [Integer, Float] delay in seconds or timestamp
|
|
35
|
+
# @param body [Object] the message body
|
|
36
|
+
# @param options [Hash] SQS message options for the delayed job
|
|
37
|
+
# @option options [Hash] :message_attributes custom message attributes
|
|
38
|
+
# @option options [String] :queue override the default queue
|
|
39
|
+
# @return [Aws::SQS::Types::SendMessageResult] the send result
|
|
40
|
+
# @raise [Errors::InvalidDelayError] if delay exceeds 15 minutes
|
|
19
41
|
def perform_in(worker_class, interval, body, options = {})
|
|
20
42
|
interval = interval.to_f
|
|
21
43
|
now = Time.now.to_f
|
|
@@ -23,7 +45,7 @@ module Shoryuken
|
|
|
23
45
|
|
|
24
46
|
delay = (ts - now).ceil
|
|
25
47
|
|
|
26
|
-
raise 'The maximum allowed delay is 15 minutes' if delay > 15 * 60
|
|
48
|
+
raise Errors::InvalidDelayError, 'The maximum allowed delay is 15 minutes' if delay > 15 * 60
|
|
27
49
|
|
|
28
50
|
worker_class.perform_async(body, options.merge(delay_seconds: delay))
|
|
29
51
|
end
|
|
@@ -1,17 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
2
4
|
module Worker
|
|
5
|
+
# Executor that processes jobs synchronously in the current thread.
|
|
6
|
+
# Useful for testing and development environments.
|
|
3
7
|
class InlineExecutor
|
|
4
8
|
class << self
|
|
9
|
+
# Processes a job synchronously in the current thread
|
|
10
|
+
#
|
|
11
|
+
# @param worker_class [Class] the worker class that will process the message
|
|
12
|
+
# @param body [Object] the message body
|
|
13
|
+
# @param options [Hash] inline execution options
|
|
14
|
+
# @option options [String] :queue override the default queue name
|
|
15
|
+
# @option options [Hash] :message_attributes custom message attributes
|
|
16
|
+
# @return [Object] the result of the worker's perform method
|
|
5
17
|
def perform_async(worker_class, body, options = {})
|
|
6
18
|
body = JSON.dump(body) if body.is_a?(Hash)
|
|
7
19
|
queue_name = options.delete(:queue) || worker_class.get_shoryuken_options['queue']
|
|
20
|
+
message_attributes = options.delete(:message_attributes) || {}
|
|
21
|
+
message_attributes['shoryuken_class'] = {
|
|
22
|
+
string_value: worker_class.to_s,
|
|
23
|
+
data_type: 'String'
|
|
24
|
+
}
|
|
8
25
|
|
|
9
|
-
sqs_msg =
|
|
26
|
+
sqs_msg = InlineMessage.new(
|
|
10
27
|
body: body,
|
|
11
28
|
attributes: nil,
|
|
12
29
|
md5_of_body: nil,
|
|
13
30
|
md5_of_message_attributes: nil,
|
|
14
|
-
message_attributes:
|
|
31
|
+
message_attributes: message_attributes,
|
|
15
32
|
message_id: nil,
|
|
16
33
|
receipt_handle: nil,
|
|
17
34
|
delete: nil,
|
|
@@ -21,12 +38,26 @@ module Shoryuken
|
|
|
21
38
|
call(worker_class, sqs_msg)
|
|
22
39
|
end
|
|
23
40
|
|
|
41
|
+
# Processes a job synchronously, ignoring the delay interval
|
|
42
|
+
#
|
|
43
|
+
# @param worker_class [Class] the worker class that will process the message
|
|
44
|
+
# @param _interval [Integer, Float] ignored for inline execution
|
|
45
|
+
# @param body [Object] the message body
|
|
46
|
+
# @param options [Hash] inline execution options
|
|
47
|
+
# @option options [String] :queue override the default queue name
|
|
48
|
+
# @option options [Hash] :message_attributes custom message attributes
|
|
49
|
+
# @return [Object] the result of the worker's perform method
|
|
24
50
|
def perform_in(worker_class, _interval, body, options = {})
|
|
25
51
|
worker_class.perform_async(body, options)
|
|
26
52
|
end
|
|
27
53
|
|
|
28
54
|
private
|
|
29
55
|
|
|
56
|
+
# Instantiates and calls the worker
|
|
57
|
+
#
|
|
58
|
+
# @param worker_class [Class] the worker class
|
|
59
|
+
# @param sqs_msg [Shoryuken::InlineMessage] the message
|
|
60
|
+
# @return [Object] the result of the worker's perform method
|
|
30
61
|
def call(worker_class, sqs_msg)
|
|
31
62
|
parsed_body = BodyParser.parse(worker_class, sqs_msg)
|
|
32
63
|
batch = worker_class.shoryuken_options_hash['batch']
|