shoryuken 7.0.0.alpha2 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/push.yml +2 -2
- data/.github/workflows/specs.yml +38 -43
- data/.github/workflows/verify-action-pins.yml +1 -1
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -1
- data/.yard-lint.yml +279 -0
- data/CHANGELOG.md +69 -1
- data/Gemfile +1 -1
- data/README.md +2 -7
- data/Rakefile +4 -10
- data/bin/clean_localstack +52 -0
- data/bin/cli/base.rb +21 -0
- data/bin/cli/sqs.rb +61 -2
- data/bin/integrations +275 -0
- data/bin/scenario +154 -0
- data/bin/shoryuken +1 -1
- data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +15 -4
- data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
- data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
- data/lib/shoryuken/active_job/current_attributes.rb +139 -0
- data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
- data/lib/shoryuken/body_parser.rb +8 -0
- data/lib/shoryuken/client.rb +14 -0
- data/lib/shoryuken/default_exception_handler.rb +9 -0
- data/lib/shoryuken/default_worker_registry.rb +29 -1
- data/lib/shoryuken/environment_loader.rb +78 -8
- data/lib/shoryuken/errors.rb +33 -0
- data/lib/shoryuken/fetcher.rb +37 -1
- data/lib/shoryuken/helpers/atomic_boolean.rb +19 -5
- data/lib/shoryuken/helpers/timer_task.rb +80 -0
- data/lib/shoryuken/launcher.rb +53 -0
- data/lib/shoryuken/logging/base.rb +26 -0
- data/lib/shoryuken/logging/pretty.rb +25 -0
- data/lib/shoryuken/logging/without_timestamp.rb +25 -0
- data/lib/shoryuken/logging.rb +39 -25
- data/lib/shoryuken/manager.rb +70 -1
- data/lib/shoryuken/message.rb +114 -1
- data/lib/shoryuken/middleware/chain.rb +139 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +8 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +10 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +27 -1
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +29 -0
- data/lib/shoryuken/middleware/server/timing.rb +11 -0
- data/lib/shoryuken/options.rb +129 -6
- data/lib/shoryuken/polling/base_strategy.rb +1 -0
- data/lib/shoryuken/polling/strict_priority.rb +39 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +42 -0
- data/lib/shoryuken/processor.rb +32 -1
- data/lib/shoryuken/queue.rb +93 -4
- data/lib/shoryuken/runner.rb +45 -4
- data/lib/shoryuken/util.rb +26 -1
- data/lib/shoryuken/version.rb +2 -1
- data/lib/shoryuken/worker/default_executor.rb +21 -1
- data/lib/shoryuken/worker/inline_executor.rb +24 -0
- data/lib/shoryuken/worker.rb +193 -0
- data/lib/shoryuken/worker_registry.rb +33 -0
- data/lib/shoryuken.rb +18 -6
- data/renovate.json +29 -2
- data/shoryuken.gemspec +2 -1
- data/spec/integration/.rspec +1 -0
- data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
- data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
- data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
- data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
- data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
- data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
- data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
- data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
- data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
- data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
- data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
- data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
- data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
- data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
- data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
- data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
- data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
- data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
- data/spec/integration/aws_config/aws_config_spec.rb +59 -0
- data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
- data/spec/integration/body_parser/json_parser_spec.rb +45 -0
- data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
- data/spec/integration/body_parser/text_parser_spec.rb +43 -0
- data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
- data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
- data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
- data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
- data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
- data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
- data/spec/integration/launcher/launcher_spec.rb +40 -0
- data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
- data/spec/integration/message_operations/message_operations_spec.rb +59 -0
- data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
- data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
- data/spec/integration/middleware_chain/removal_spec.rb +31 -0
- data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
- data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
- data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
- data/spec/integration/rails/rails_72/Gemfile +6 -0
- data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/Gemfile +6 -0
- data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
- data/spec/integration/rails/rails_81/Gemfile +6 -0
- data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
- data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
- data/spec/integration/spec_helper.rb +7 -0
- data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
- data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
- data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
- data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
- data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
- data/spec/integrations_helper.rb +243 -0
- data/spec/lib/active_job/extensions_spec.rb +149 -0
- data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
- data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +3 -3
- data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +4 -4
- data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/helpers/hash_utils_spec.rb +14 -14
- data/spec/{shoryuken → lib/shoryuken}/helpers/string_utils_spec.rb +3 -3
- data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/{shoryuken → lib/shoryuken}/helpers_integration_spec.rb +9 -9
- data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +22 -0
- data/spec/lib/shoryuken/logging_spec.rb +242 -0
- data/spec/lib/shoryuken/message_spec.rb +109 -0
- data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
- data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +50 -0
- data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +2 -2
- data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +1 -1
- data/spec/lib/shoryuken/version_spec.rb +17 -0
- data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
- data/spec/shared_examples_for_active_job.rb +29 -9
- data/spec/spec_helper.rb +34 -3
- metadata +230 -91
- data/.devcontainer/Dockerfile +0 -17
- data/.devcontainer/base.Dockerfile +0 -43
- data/.devcontainer/devcontainer.json +0 -35
- data/Appraisals +0 -23
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/rails_7_0.gemfile +0 -19
- data/gemfiles/rails_7_1.gemfile +0 -19
- data/gemfiles/rails_7_2.gemfile +0 -19
- data/gemfiles/rails_8_0.gemfile +0 -19
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -110
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
- data/spec/integration/launcher_spec.rb +0 -127
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -8
- data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -85
- /data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_boolean_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_counter_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_hash_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/inline_message_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/base_strategy_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/queue_configuration_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker/inline_executor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +0 -0
- /data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +0 -0
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Shoryuken
|
|
4
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.
|
|
5
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
|
|
6
13
|
def initialize(queues, delay = nil)
|
|
7
14
|
@initial_queues = queues
|
|
8
15
|
@queues = queues.dup.uniq
|
|
@@ -10,6 +17,9 @@ module Shoryuken
|
|
|
10
17
|
@delay = delay
|
|
11
18
|
end
|
|
12
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
|
|
13
23
|
def next_queue
|
|
14
24
|
unpause_queues
|
|
15
25
|
queue = @queues.shift
|
|
@@ -19,6 +29,11 @@ module Shoryuken
|
|
|
19
29
|
QueueConfiguration.new(queue, {})
|
|
20
30
|
end
|
|
21
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]
|
|
22
37
|
def messages_found(queue, messages_found)
|
|
23
38
|
if messages_found == 0
|
|
24
39
|
pause(queue)
|
|
@@ -33,10 +48,17 @@ module Shoryuken
|
|
|
33
48
|
end
|
|
34
49
|
end
|
|
35
50
|
|
|
51
|
+
# Returns the list of active queues with their current weights
|
|
52
|
+
#
|
|
53
|
+
# @return [Array<Array>] array of [queue_name, weight] pairs
|
|
36
54
|
def active_queues
|
|
37
55
|
unparse_queues(@queues)
|
|
38
56
|
end
|
|
39
57
|
|
|
58
|
+
# Called when a message from a queue has been processed
|
|
59
|
+
#
|
|
60
|
+
# @param queue [String] the queue name
|
|
61
|
+
# @return [void]
|
|
40
62
|
def message_processed(queue)
|
|
41
63
|
paused_queue = @paused_queues.find { |_time, name| name == queue }
|
|
42
64
|
return unless paused_queue
|
|
@@ -46,6 +68,10 @@ module Shoryuken
|
|
|
46
68
|
|
|
47
69
|
private
|
|
48
70
|
|
|
71
|
+
# Pauses a queue by removing it from active rotation
|
|
72
|
+
#
|
|
73
|
+
# @param queue [String] the queue name to pause
|
|
74
|
+
# @return [void]
|
|
49
75
|
def pause(queue)
|
|
50
76
|
return unless @queues.delete(queue)
|
|
51
77
|
|
|
@@ -53,6 +79,9 @@ module Shoryuken
|
|
|
53
79
|
logger.debug "Paused #{queue}"
|
|
54
80
|
end
|
|
55
81
|
|
|
82
|
+
# Unpauses queues whose delay has expired
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
56
85
|
def unpause_queues
|
|
57
86
|
return if @paused_queues.empty?
|
|
58
87
|
return if Time.now < @paused_queues.first[0]
|
|
@@ -62,14 +91,27 @@ module Shoryuken
|
|
|
62
91
|
logger.debug "Unpaused #{pause[1]}"
|
|
63
92
|
end
|
|
64
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
|
|
65
98
|
def current_queue_weight(queue)
|
|
66
99
|
queue_weight(@queues, queue)
|
|
67
100
|
end
|
|
68
101
|
|
|
102
|
+
# Returns the maximum configured weight of a queue
|
|
103
|
+
#
|
|
104
|
+
# @param queue [String] the queue name
|
|
105
|
+
# @return [Integer] the maximum weight
|
|
69
106
|
def maximum_queue_weight(queue)
|
|
70
107
|
queue_weight(@initial_queues, queue)
|
|
71
108
|
end
|
|
72
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
|
|
73
115
|
def queue_weight(queues, queue)
|
|
74
116
|
queues.count { |q| q == queue }
|
|
75
117
|
end
|
data/lib/shoryuken/processor.rb
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Shoryuken
|
|
4
|
+
# Processes SQS messages by invoking the appropriate worker.
|
|
5
|
+
# Handles middleware chain execution and exception handling.
|
|
4
6
|
class Processor
|
|
5
7
|
include Util
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
# @return [String] the queue name
|
|
10
|
+
attr_reader :queue
|
|
8
11
|
|
|
12
|
+
# @return [Aws::SQS::Types::Message, Array<Aws::SQS::Types::Message>] the message or batch
|
|
13
|
+
attr_reader :sqs_msg
|
|
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
|
|
9
20
|
def self.process(queue, sqs_msg)
|
|
10
21
|
new(queue, sqs_msg).process
|
|
11
22
|
end
|
|
12
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
|
|
13
28
|
def initialize(queue, sqs_msg)
|
|
14
29
|
@queue = queue
|
|
15
30
|
@sqs_msg = sqs_msg
|
|
16
31
|
end
|
|
17
32
|
|
|
33
|
+
# Processes the message through the middleware chain and worker
|
|
34
|
+
#
|
|
35
|
+
# @return [Object] the result of the worker's perform method
|
|
18
36
|
def process
|
|
19
37
|
worker_perform = proc do
|
|
20
38
|
return logger.error { "No worker found for #{queue}" } unless worker
|
|
@@ -41,18 +59,31 @@ module Shoryuken
|
|
|
41
59
|
|
|
42
60
|
private
|
|
43
61
|
|
|
62
|
+
# Fetches the worker instance for processing
|
|
63
|
+
#
|
|
64
|
+
# @return [Object, nil] the worker instance or nil if not found
|
|
44
65
|
def worker
|
|
45
66
|
@_worker ||= Shoryuken.worker_registry.fetch_worker(queue, sqs_msg)
|
|
46
67
|
end
|
|
47
68
|
|
|
69
|
+
# Returns the worker class
|
|
70
|
+
#
|
|
71
|
+
# @return [Class] the worker class
|
|
48
72
|
def worker_class
|
|
49
73
|
worker.class
|
|
50
74
|
end
|
|
51
75
|
|
|
76
|
+
# Parses the message body or bodies for batch processing
|
|
77
|
+
#
|
|
78
|
+
# @return [Object, Array<Object>] the parsed body or array of bodies
|
|
52
79
|
def body
|
|
53
80
|
@_body ||= sqs_msg.is_a?(Array) ? sqs_msg.map(&method(:parse_body)) : parse_body(sqs_msg)
|
|
54
81
|
end
|
|
55
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
|
|
56
87
|
def parse_body(sqs_msg)
|
|
57
88
|
BodyParser.parse(worker_class, sqs_msg)
|
|
58
89
|
end
|
data/lib/shoryuken/queue.rb
CHANGED
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
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.
|
|
4
6
|
class Queue
|
|
5
7
|
include Util
|
|
6
8
|
|
|
9
|
+
# SQS attribute name for FIFO queue identification
|
|
7
10
|
FIFO_ATTR = 'FifoQueue'
|
|
11
|
+
|
|
12
|
+
# Default message group ID used for FIFO queues
|
|
8
13
|
MESSAGE_GROUP_ID = 'ShoryukenMessage'
|
|
14
|
+
|
|
15
|
+
# SQS attribute name for visibility timeout
|
|
9
16
|
VISIBILITY_TIMEOUT_ATTR = 'VisibilityTimeout'
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
# @return [String] the queue name
|
|
19
|
+
attr_accessor :name
|
|
20
|
+
|
|
21
|
+
# @return [Aws::SQS::Client] the SQS client
|
|
22
|
+
attr_accessor :client
|
|
23
|
+
|
|
24
|
+
# @return [String] the queue URL
|
|
25
|
+
attr_accessor :url
|
|
12
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
|
|
13
31
|
def initialize(client, name_or_url_or_arn)
|
|
14
32
|
self.client = client
|
|
15
33
|
set_name_and_url(name_or_url_or_arn)
|
|
16
34
|
end
|
|
17
35
|
|
|
36
|
+
# Returns the visibility timeout for the queue
|
|
37
|
+
#
|
|
38
|
+
# @return [Integer] the visibility timeout in seconds
|
|
18
39
|
def visibility_timeout
|
|
19
40
|
# Always lookup for the latest visibility when cache is disabled
|
|
20
41
|
# setting it to nil, forces re-lookup
|
|
@@ -22,6 +43,11 @@ module Shoryuken
|
|
|
22
43
|
@_visibility_timeout ||= queue_attributes.attributes[VISIBILITY_TIMEOUT_ATTR].to_i
|
|
23
44
|
end
|
|
24
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
|
|
25
51
|
def delete_messages(options)
|
|
26
52
|
failed_messages = client.delete_message_batch(
|
|
27
53
|
options.merge(queue_url: url)
|
|
@@ -33,6 +59,15 @@ module Shoryuken
|
|
|
33
59
|
end
|
|
34
60
|
end
|
|
35
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
|
|
36
71
|
def send_message(options)
|
|
37
72
|
options = sanitize_message!(options).merge(queue_url: url)
|
|
38
73
|
|
|
@@ -41,15 +76,32 @@ module Shoryuken
|
|
|
41
76
|
end
|
|
42
77
|
end
|
|
43
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
|
|
44
84
|
def send_messages(options)
|
|
45
85
|
client.send_message_batch(sanitize_messages!(options).merge(queue_url: url))
|
|
46
86
|
end
|
|
47
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
|
|
48
97
|
def receive_messages(options)
|
|
49
98
|
messages = client.receive_message(options.merge(queue_url: url)).messages || []
|
|
50
99
|
messages.map { |m| Message.new(client, self, m) }
|
|
51
100
|
end
|
|
52
101
|
|
|
102
|
+
# Checks if the queue is a FIFO queue
|
|
103
|
+
#
|
|
104
|
+
# @return [Boolean] true if the queue is a FIFO queue
|
|
53
105
|
def fifo?
|
|
54
106
|
# Make sure the memoization work with boolean to avoid multiple calls to SQS
|
|
55
107
|
# see https://github.com/ruby-shoryuken/shoryuken/pull/529
|
|
@@ -61,32 +113,51 @@ module Shoryuken
|
|
|
61
113
|
|
|
62
114
|
private
|
|
63
115
|
|
|
116
|
+
# Initializes the FIFO attribute by calling fifo?
|
|
117
|
+
#
|
|
118
|
+
# @return [Boolean] whether the queue is FIFO
|
|
64
119
|
def initialize_fifo_attribute
|
|
65
120
|
# calling fifo? will also initialize it
|
|
66
121
|
fifo?
|
|
67
122
|
end
|
|
68
123
|
|
|
124
|
+
# Sets the queue name and URL from a queue name
|
|
125
|
+
#
|
|
126
|
+
# @param name [String] the queue name
|
|
127
|
+
# @return [void]
|
|
69
128
|
def set_by_name(name) # rubocop:disable Naming/AccessorMethodName
|
|
70
129
|
self.name = name
|
|
71
130
|
self.url = client.get_queue_url(queue_name: name).queue_url
|
|
72
131
|
end
|
|
73
132
|
|
|
133
|
+
# Sets the queue name and URL from a queue URL
|
|
134
|
+
#
|
|
135
|
+
# @param url [String] the queue URL
|
|
136
|
+
# @return [void]
|
|
74
137
|
def set_by_url(url) # rubocop:disable Naming/AccessorMethodName
|
|
75
138
|
self.name = url.split('/').last
|
|
76
139
|
self.url = url
|
|
77
140
|
end
|
|
78
141
|
|
|
142
|
+
# Converts an ARN to a queue URL
|
|
143
|
+
#
|
|
144
|
+
# @param arn_str [String] the ARN string
|
|
145
|
+
# @return [String] the queue URL
|
|
79
146
|
def arn_to_url(arn_str)
|
|
80
147
|
*, region, account_id, resource = arn_str.split(':')
|
|
81
148
|
|
|
82
149
|
required = [region, account_id, resource].map(&:to_s)
|
|
83
150
|
valid = required.none?(&:empty?)
|
|
84
151
|
|
|
85
|
-
|
|
152
|
+
raise Errors::InvalidArnError, "Invalid ARN: #{arn_str}. A valid ARN must include: region, account_id and resource." unless valid
|
|
86
153
|
|
|
87
154
|
"https://sqs.#{region}.amazonaws.com/#{account_id}/#{resource}"
|
|
88
155
|
end
|
|
89
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]
|
|
90
161
|
def set_name_and_url(name_or_url_or_arn) # rubocop:disable Naming/AccessorMethodName
|
|
91
162
|
if name_or_url_or_arn.include?('://')
|
|
92
163
|
set_by_url(name_or_url_or_arn)
|
|
@@ -106,16 +177,24 @@ module Shoryuken
|
|
|
106
177
|
end
|
|
107
178
|
|
|
108
179
|
set_by_name(name_or_url_or_arn)
|
|
109
|
-
rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
|
|
110
|
-
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."
|
|
111
182
|
end
|
|
112
183
|
|
|
184
|
+
# Returns the queue attributes from SQS
|
|
185
|
+
#
|
|
186
|
+
# @return [Aws::SQS::Types::GetQueueAttributesResult] the queue attributes
|
|
113
187
|
def queue_attributes
|
|
114
188
|
# Note: Retrieving all queue attributes as requesting `FifoQueue` on non-FIFO queue raises error.
|
|
115
189
|
# See issue: https://github.com/aws/aws-sdk-ruby/issues/1350
|
|
116
190
|
client.get_queue_attributes(queue_url: url, attribute_names: ['All'])
|
|
117
191
|
end
|
|
118
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
|
|
119
198
|
def sanitize_messages!(options)
|
|
120
199
|
if options.is_a?(Array)
|
|
121
200
|
entries = options.map.with_index do |m, index|
|
|
@@ -130,6 +209,11 @@ module Shoryuken
|
|
|
130
209
|
options
|
|
131
210
|
end
|
|
132
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
|
|
133
217
|
def add_fifo_attributes!(options)
|
|
134
218
|
return unless fifo?
|
|
135
219
|
|
|
@@ -139,6 +223,11 @@ module Shoryuken
|
|
|
139
223
|
options
|
|
140
224
|
end
|
|
141
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
|
|
142
231
|
def sanitize_message!(options)
|
|
143
232
|
options = { message_body: options } if options.is_a?(String)
|
|
144
233
|
|
data/lib/shoryuken/runner.rb
CHANGED
|
@@ -9,13 +9,23 @@ require 'erb'
|
|
|
9
9
|
require 'shoryuken'
|
|
10
10
|
|
|
11
11
|
module Shoryuken
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
# Runs the Shoryuken server process.
|
|
13
|
+
# Handles signal trapping, daemonization, and lifecycle management.
|
|
15
14
|
class Runner
|
|
16
15
|
include Util
|
|
17
16
|
include Singleton
|
|
18
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]
|
|
19
29
|
def run(options)
|
|
20
30
|
self_read, self_write = IO.pipe
|
|
21
31
|
|
|
@@ -51,12 +61,18 @@ module Shoryuken
|
|
|
51
61
|
end
|
|
52
62
|
end
|
|
53
63
|
|
|
64
|
+
# Checks if the server is healthy
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean] true if the launcher is running and healthy
|
|
54
67
|
def healthy?
|
|
55
68
|
(@launcher && @launcher.healthy?) || false
|
|
56
69
|
end
|
|
57
70
|
|
|
58
71
|
private
|
|
59
72
|
|
|
73
|
+
# Initializes the Concurrent Ruby logger
|
|
74
|
+
#
|
|
75
|
+
# @return [void]
|
|
60
76
|
def initialize_concurrent_logger
|
|
61
77
|
return unless Shoryuken.logger
|
|
62
78
|
|
|
@@ -65,6 +81,12 @@ module Shoryuken
|
|
|
65
81
|
end
|
|
66
82
|
end
|
|
67
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]
|
|
68
90
|
def daemonize(options)
|
|
69
91
|
return unless options[:daemon]
|
|
70
92
|
|
|
@@ -90,25 +112,39 @@ module Shoryuken
|
|
|
90
112
|
$stdin.reopen('/dev/null')
|
|
91
113
|
end
|
|
92
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]
|
|
93
120
|
def write_pid(options)
|
|
94
121
|
return unless (path = options[:pidfile])
|
|
95
122
|
|
|
96
123
|
File.open(path, 'w') { |f| f.puts(Process.pid) }
|
|
97
124
|
end
|
|
98
125
|
|
|
126
|
+
# Executes a soft shutdown on USR1 signal
|
|
127
|
+
#
|
|
128
|
+
# @return [void]
|
|
99
129
|
def execute_soft_shutdown
|
|
100
|
-
logger.info { 'Received USR1, will soft shutdown
|
|
130
|
+
logger.info { 'Received USR1, will soft shutdown' }
|
|
101
131
|
|
|
102
132
|
@launcher.stop
|
|
103
133
|
exit 0
|
|
104
134
|
end
|
|
105
135
|
|
|
136
|
+
# Executes a terminal stop on TSTP signal
|
|
137
|
+
#
|
|
138
|
+
# @return [void]
|
|
106
139
|
def execute_terminal_stop
|
|
107
140
|
logger.info { 'Received TSTP, will stop accepting new work' }
|
|
108
141
|
|
|
109
142
|
@launcher.stop
|
|
110
143
|
end
|
|
111
144
|
|
|
145
|
+
# Prints backtraces of all threads
|
|
146
|
+
#
|
|
147
|
+
# @return [void]
|
|
112
148
|
def print_threads_backtrace
|
|
113
149
|
Thread.list.each do |thread|
|
|
114
150
|
logger.info { "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}" }
|
|
@@ -120,6 +156,11 @@ module Shoryuken
|
|
|
120
156
|
end
|
|
121
157
|
end
|
|
122
158
|
|
|
159
|
+
# Handles incoming signals
|
|
160
|
+
#
|
|
161
|
+
# @param sig [String] the signal name
|
|
162
|
+
# @return [void]
|
|
163
|
+
# @raise [Interrupt] on TERM or INT signals
|
|
123
164
|
def handle_signal(sig)
|
|
124
165
|
logger.debug "Got #{sig} signal"
|
|
125
166
|
|
data/lib/shoryuken/util.rb
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Shoryuken
|
|
4
|
+
# Utility methods shared across Shoryuken classes.
|
|
5
|
+
# Provides logging, event firing, and helper methods.
|
|
4
6
|
module Util
|
|
7
|
+
# Returns the Shoryuken logger
|
|
8
|
+
#
|
|
9
|
+
# @return [Logger] the configured logger
|
|
5
10
|
def logger
|
|
6
11
|
Shoryuken.logger
|
|
7
12
|
end
|
|
8
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]
|
|
9
20
|
def fire_event(event, reverse = false, event_options = {})
|
|
10
21
|
logger.debug { "Firing '#{event}' lifecycle event" }
|
|
11
22
|
arr = Shoryuken.options[:lifecycle_events][event]
|
|
@@ -18,24 +29,38 @@ module Shoryuken
|
|
|
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
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module Shoryuken
|
|
4
4
|
module Worker
|
|
5
|
+
# Default executor that sends messages to SQS for asynchronous processing.
|
|
6
|
+
# This is the standard executor used in production environments.
|
|
5
7
|
class DefaultExecutor
|
|
6
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
|
|
7
17
|
def perform_async(worker_class, body, options = {})
|
|
8
18
|
options[:message_attributes] ||= {}
|
|
9
19
|
options[:message_attributes]['shoryuken_class'] = {
|
|
@@ -18,6 +28,16 @@ module Shoryuken
|
|
|
18
28
|
Shoryuken::Client.queues(queue).send_message(options)
|
|
19
29
|
end
|
|
20
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
|
|
21
41
|
def perform_in(worker_class, interval, body, options = {})
|
|
22
42
|
interval = interval.to_f
|
|
23
43
|
now = Time.now.to_f
|
|
@@ -25,7 +45,7 @@ module Shoryuken
|
|
|
25
45
|
|
|
26
46
|
delay = (ts - now).ceil
|
|
27
47
|
|
|
28
|
-
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
|
|
29
49
|
|
|
30
50
|
worker_class.perform_async(body, options.merge(delay_seconds: delay))
|
|
31
51
|
end
|
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module Shoryuken
|
|
4
4
|
module Worker
|
|
5
|
+
# Executor that processes jobs synchronously in the current thread.
|
|
6
|
+
# Useful for testing and development environments.
|
|
5
7
|
class InlineExecutor
|
|
6
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
|
|
7
17
|
def perform_async(worker_class, body, options = {})
|
|
8
18
|
body = JSON.dump(body) if body.is_a?(Hash)
|
|
9
19
|
queue_name = options.delete(:queue) || worker_class.get_shoryuken_options['queue']
|
|
@@ -28,12 +38,26 @@ module Shoryuken
|
|
|
28
38
|
call(worker_class, sqs_msg)
|
|
29
39
|
end
|
|
30
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
|
|
31
50
|
def perform_in(worker_class, _interval, body, options = {})
|
|
32
51
|
worker_class.perform_async(body, options)
|
|
33
52
|
end
|
|
34
53
|
|
|
35
54
|
private
|
|
36
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
|
|
37
61
|
def call(worker_class, sqs_msg)
|
|
38
62
|
parsed_body = BodyParser.parse(worker_class, sqs_msg)
|
|
39
63
|
batch = worker_class.shoryuken_options_hash['batch']
|