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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob adapter integration tests for Rails 8.1
|
|
4
|
+
|
|
5
|
+
setup_active_job
|
|
6
|
+
|
|
7
|
+
class EmailJob < ActiveJob::Base
|
|
8
|
+
queue_as :default
|
|
9
|
+
|
|
10
|
+
def perform(user_id, message)
|
|
11
|
+
{ user_id: user_id, message: message, sent_at: Time.current }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class DataProcessingJob < ActiveJob::Base
|
|
16
|
+
queue_as :high_priority
|
|
17
|
+
|
|
18
|
+
def perform(data_file)
|
|
19
|
+
"Processed: #{data_file}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class SerializationJob < ActiveJob::Base
|
|
24
|
+
queue_as :default
|
|
25
|
+
|
|
26
|
+
def perform(complex_data)
|
|
27
|
+
complex_data.transform_values(&:upcase)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Test adapter setup
|
|
32
|
+
adapter = ActiveJob::Base.queue_adapter
|
|
33
|
+
assert_equal("ActiveJob::QueueAdapters::ShoryukenAdapter", adapter.class.name)
|
|
34
|
+
|
|
35
|
+
# Test singleton pattern
|
|
36
|
+
instance1 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
|
|
37
|
+
instance2 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
|
|
38
|
+
assert_equal(instance1.object_id, instance2.object_id)
|
|
39
|
+
|
|
40
|
+
# Test transaction commit hook
|
|
41
|
+
adapter_instance = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
42
|
+
assert(adapter_instance.respond_to?(:enqueue_after_transaction_commit?))
|
|
43
|
+
assert_equal(true, adapter_instance.enqueue_after_transaction_commit?)
|
|
44
|
+
|
|
45
|
+
# Test simple job enqueue
|
|
46
|
+
job_capture = JobCapture.new
|
|
47
|
+
job_capture.start_capturing
|
|
48
|
+
|
|
49
|
+
EmailJob.perform_later(1, 'Hello World')
|
|
50
|
+
|
|
51
|
+
assert_equal(1, job_capture.job_count)
|
|
52
|
+
job = job_capture.last_job
|
|
53
|
+
message_body = job[:message_body]
|
|
54
|
+
assert_equal('EmailJob', message_body['job_class'])
|
|
55
|
+
assert_equal([1, 'Hello World'], message_body['arguments'])
|
|
56
|
+
assert_equal('default', message_body['queue_name'])
|
|
57
|
+
|
|
58
|
+
# Test different queue
|
|
59
|
+
job_capture2 = JobCapture.new
|
|
60
|
+
job_capture2.start_capturing
|
|
61
|
+
|
|
62
|
+
DataProcessingJob.perform_later('large_dataset.csv')
|
|
63
|
+
|
|
64
|
+
job2 = job_capture2.last_job
|
|
65
|
+
message_body2 = job2[:message_body]
|
|
66
|
+
assert_equal('DataProcessingJob', message_body2['job_class'])
|
|
67
|
+
assert_equal('high_priority', message_body2['queue_name'])
|
|
68
|
+
|
|
69
|
+
# Test complex data serialization
|
|
70
|
+
complex_data = {
|
|
71
|
+
'user' => { 'name' => 'John', 'age' => 30 },
|
|
72
|
+
'preferences' => ['email', 'sms']
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
job_capture3 = JobCapture.new
|
|
76
|
+
job_capture3.start_capturing
|
|
77
|
+
|
|
78
|
+
SerializationJob.perform_later(complex_data)
|
|
79
|
+
|
|
80
|
+
job3 = job_capture3.last_job
|
|
81
|
+
message_body3 = job3[:message_body]
|
|
82
|
+
args_data = message_body3['arguments'].first
|
|
83
|
+
assert_equal('John', args_data['user']['name'])
|
|
84
|
+
assert_equal(30, args_data['user']['age'])
|
|
85
|
+
|
|
86
|
+
# Test shoryuken_class message attribute
|
|
87
|
+
job_capture4 = JobCapture.new
|
|
88
|
+
job_capture4.start_capturing
|
|
89
|
+
|
|
90
|
+
EmailJob.perform_later(1, 'Attributes test')
|
|
91
|
+
|
|
92
|
+
job4 = job_capture4.last_job
|
|
93
|
+
attributes = job4[:message_attributes]
|
|
94
|
+
expected_shoryuken_class = {
|
|
95
|
+
string_value: "Shoryuken::ActiveJob::JobWrapper",
|
|
96
|
+
data_type: 'String'
|
|
97
|
+
}
|
|
98
|
+
assert_equal(expected_shoryuken_class, attributes['shoryuken_class'])
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob Continuations integration tests for Rails 8.1+
|
|
4
|
+
# Tests the stopping? method and continuation timestamp handling
|
|
5
|
+
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
# Skip if ActiveJob::Continuable is not available (Rails < 8.1)
|
|
9
|
+
unless defined?(ActiveJob::Continuable)
|
|
10
|
+
puts "Skipping continuation tests - ActiveJob::Continuable not available (requires Rails 8.1+)"
|
|
11
|
+
exit 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Test stopping? returns false when launcher is not initialized
|
|
15
|
+
adapter = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
16
|
+
assert_equal(false, adapter.stopping?)
|
|
17
|
+
|
|
18
|
+
# Test stopping? returns true when launcher is stopping
|
|
19
|
+
launcher = Shoryuken::Launcher.new
|
|
20
|
+
runner = Shoryuken::Runner.instance
|
|
21
|
+
runner.instance_variable_set(:@launcher, launcher)
|
|
22
|
+
|
|
23
|
+
adapter2 = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
24
|
+
assert_equal(false, adapter2.stopping?)
|
|
25
|
+
|
|
26
|
+
launcher.instance_variable_set(:@stopping, true)
|
|
27
|
+
assert_equal(true, adapter2.stopping?)
|
|
28
|
+
|
|
29
|
+
# Reset launcher state
|
|
30
|
+
launcher.instance_variable_set(:@stopping, false)
|
|
31
|
+
|
|
32
|
+
# Test past timestamps for continuation retries
|
|
33
|
+
job_capture = JobCapture.new
|
|
34
|
+
job_capture.start_capturing
|
|
35
|
+
|
|
36
|
+
class ContinuableTestJob < ActiveJob::Base
|
|
37
|
+
include ActiveJob::Continuable if defined?(ActiveJob::Continuable)
|
|
38
|
+
queue_as :default
|
|
39
|
+
def perform; end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
adapter3 = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
43
|
+
job = ContinuableTestJob.new
|
|
44
|
+
job.sqs_send_message_parameters = {}
|
|
45
|
+
|
|
46
|
+
past_timestamp = Time.current.to_f - 60
|
|
47
|
+
adapter3.enqueue_at(job, past_timestamp)
|
|
48
|
+
|
|
49
|
+
captured_job = job_capture.last_job
|
|
50
|
+
assert(captured_job[:delay_seconds] <= 0, "Past timestamp should result in immediate delivery")
|
|
51
|
+
|
|
52
|
+
# Test current timestamp
|
|
53
|
+
job_capture2 = JobCapture.new
|
|
54
|
+
job_capture2.start_capturing
|
|
55
|
+
|
|
56
|
+
job2 = ContinuableTestJob.new
|
|
57
|
+
job2.sqs_send_message_parameters = {}
|
|
58
|
+
|
|
59
|
+
current_timestamp = Time.current.to_f
|
|
60
|
+
adapter3.enqueue_at(job2, current_timestamp)
|
|
61
|
+
|
|
62
|
+
captured_job2 = job_capture2.last_job
|
|
63
|
+
delay = captured_job2[:delay_seconds]
|
|
64
|
+
assert(delay >= -1 && delay <= 1, "Current timestamp should have minimal delay")
|
|
65
|
+
|
|
66
|
+
# Test future timestamp
|
|
67
|
+
job_capture3 = JobCapture.new
|
|
68
|
+
job_capture3.start_capturing
|
|
69
|
+
|
|
70
|
+
job3 = ContinuableTestJob.new
|
|
71
|
+
job3.sqs_send_message_parameters = {}
|
|
72
|
+
|
|
73
|
+
future_timestamp = Time.current.to_f + 30
|
|
74
|
+
adapter3.enqueue_at(job3, future_timestamp)
|
|
75
|
+
|
|
76
|
+
captured_job3 = job_capture3.last_job
|
|
77
|
+
delay3 = captured_job3[:delay_seconds]
|
|
78
|
+
assert(delay3 > 0, "Future timestamp should have positive delay")
|
|
79
|
+
assert(delay3 <= 30, "Delay should not exceed scheduled time")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests retry behavior including ApproximateReceiveCount tracking
|
|
4
|
+
# across message redeliveries.
|
|
5
|
+
|
|
6
|
+
require 'concurrent'
|
|
7
|
+
|
|
8
|
+
setup_localstack
|
|
9
|
+
|
|
10
|
+
queue_name = DT.queue
|
|
11
|
+
create_test_queue(queue_name, attributes: { 'VisibilityTimeout' => '2' })
|
|
12
|
+
Shoryuken.add_group('default', 1)
|
|
13
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
14
|
+
|
|
15
|
+
# Atomic counter for fail tracking
|
|
16
|
+
fail_counter = Concurrent::AtomicFixnum.new(2)
|
|
17
|
+
|
|
18
|
+
worker_class = Class.new do
|
|
19
|
+
include Shoryuken::Worker
|
|
20
|
+
|
|
21
|
+
shoryuken_options auto_delete: false, batch: false
|
|
22
|
+
|
|
23
|
+
define_method(:perform) do |sqs_msg, body|
|
|
24
|
+
receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
|
|
25
|
+
DT[:receive_counts] << receive_count
|
|
26
|
+
|
|
27
|
+
if fail_counter.value > 0
|
|
28
|
+
fail_counter.decrement
|
|
29
|
+
raise "Simulated failure"
|
|
30
|
+
else
|
|
31
|
+
sqs_msg.delete
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
worker_class.get_shoryuken_options['queue'] = queue_name
|
|
37
|
+
Shoryuken.register_worker(queue_name, worker_class)
|
|
38
|
+
|
|
39
|
+
Shoryuken::Client.queues(queue_name).send_message(message_body: 'retry-count-test')
|
|
40
|
+
|
|
41
|
+
poll_queues_until(timeout: 20) { DT[:receive_counts].size >= 3 }
|
|
42
|
+
|
|
43
|
+
assert(DT[:receive_counts].size >= 3)
|
|
44
|
+
assert_equal(DT[:receive_counts], DT[:receive_counts].sort, "Receive counts should be increasing")
|
|
45
|
+
assert_equal(1, DT[:receive_counts].first)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests the StrictPriority polling strategy.
|
|
4
|
+
# Higher priority queues are always processed before lower priority queues.
|
|
5
|
+
|
|
6
|
+
setup_localstack
|
|
7
|
+
|
|
8
|
+
queue_high = DT.queues[0]
|
|
9
|
+
queue_low = DT.queues[1]
|
|
10
|
+
|
|
11
|
+
[queue_high, queue_low].each { |q| create_test_queue(q) }
|
|
12
|
+
|
|
13
|
+
# Configure StrictPriority polling strategy
|
|
14
|
+
Shoryuken.options[:polling_strategy] = 'StrictPriority'
|
|
15
|
+
|
|
16
|
+
Shoryuken.add_group('default', 1)
|
|
17
|
+
# Higher weight = higher priority (queue_high appears 3 times, queue_low appears 1 time)
|
|
18
|
+
Shoryuken.add_queue(queue_high, 3, 'default')
|
|
19
|
+
Shoryuken.add_queue(queue_low, 1, 'default')
|
|
20
|
+
|
|
21
|
+
worker_class = Class.new do
|
|
22
|
+
include Shoryuken::Worker
|
|
23
|
+
|
|
24
|
+
shoryuken_options auto_delete: true, batch: false
|
|
25
|
+
|
|
26
|
+
def perform(sqs_msg, body)
|
|
27
|
+
queue = sqs_msg.queue_url.split('/').last
|
|
28
|
+
DT[:processed_order] << { queue: queue, body: body, time: Time.now }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
[queue_high, queue_low].each do |queue|
|
|
33
|
+
worker_class.get_shoryuken_options['queue'] = queue
|
|
34
|
+
Shoryuken.register_worker(queue, worker_class)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Send messages to low priority queue first
|
|
38
|
+
3.times { |i| Shoryuken::Client.queues(queue_low).send_message(message_body: "low-#{i}") }
|
|
39
|
+
|
|
40
|
+
# Then send messages to high priority queue
|
|
41
|
+
3.times { |i| Shoryuken::Client.queues(queue_high).send_message(message_body: "high-#{i}") }
|
|
42
|
+
|
|
43
|
+
sleep 1
|
|
44
|
+
|
|
45
|
+
poll_queues_until(timeout: 20) { DT[:processed_order].size >= 6 }
|
|
46
|
+
|
|
47
|
+
assert_equal(6, DT[:processed_order].size)
|
|
48
|
+
|
|
49
|
+
# With StrictPriority, high priority messages should generally be processed first
|
|
50
|
+
high_messages = DT[:processed_order].select { |m| m[:queue] == queue_high }
|
|
51
|
+
low_messages = DT[:processed_order].select { |m| m[:queue] == queue_low }
|
|
52
|
+
|
|
53
|
+
assert_equal(3, high_messages.size, "All high priority messages should be processed")
|
|
54
|
+
assert_equal(3, low_messages.size, "All low priority messages should be processed")
|
|
55
|
+
|
|
56
|
+
# Verify both queues were processed
|
|
57
|
+
queues_processed = DT[:processed_order].map { |m| m[:queue] }.uniq
|
|
58
|
+
assert_equal(2, queues_processed.size, "Both queues should have messages processed")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests visibility timeout management including manual visibility
|
|
4
|
+
# extension during long processing.
|
|
5
|
+
|
|
6
|
+
setup_localstack
|
|
7
|
+
|
|
8
|
+
queue_name = DT.queue
|
|
9
|
+
create_test_queue(queue_name, attributes: { 'VisibilityTimeout' => '5' })
|
|
10
|
+
Shoryuken.add_group('default', 1)
|
|
11
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
12
|
+
|
|
13
|
+
worker_class = Class.new do
|
|
14
|
+
include Shoryuken::Worker
|
|
15
|
+
|
|
16
|
+
def perform(sqs_msg, body)
|
|
17
|
+
# Extend visibility before long processing
|
|
18
|
+
sqs_msg.change_visibility(visibility_timeout: 30)
|
|
19
|
+
DT[:visibility_extended] << true
|
|
20
|
+
|
|
21
|
+
sleep 2 # Simulate slow processing
|
|
22
|
+
|
|
23
|
+
DT[:messages] << body
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
worker_class.get_shoryuken_options['queue'] = queue_name
|
|
28
|
+
worker_class.get_shoryuken_options['auto_delete'] = true
|
|
29
|
+
worker_class.get_shoryuken_options['batch'] = false
|
|
30
|
+
Shoryuken.register_worker(queue_name, worker_class)
|
|
31
|
+
|
|
32
|
+
Shoryuken::Client.queues(queue_name).send_message(message_body: 'extend-test')
|
|
33
|
+
|
|
34
|
+
poll_queues_until { DT[:messages].size >= 1 }
|
|
35
|
+
|
|
36
|
+
assert_equal(1, DT[:messages].size)
|
|
37
|
+
assert(DT[:visibility_extended].any?, "Expected visibility to be extended")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests the worker enqueueing methods:
|
|
4
|
+
# - perform_async - enqueue a job for immediate processing
|
|
5
|
+
# - perform_in - enqueue a job with a delay
|
|
6
|
+
|
|
7
|
+
setup_localstack
|
|
8
|
+
|
|
9
|
+
queue_name = DT.queue
|
|
10
|
+
create_test_queue(queue_name)
|
|
11
|
+
Shoryuken.add_group('default', 1)
|
|
12
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
13
|
+
|
|
14
|
+
# Worker for testing enqueueing methods
|
|
15
|
+
enqueueing_worker = Class.new do
|
|
16
|
+
include Shoryuken::Worker
|
|
17
|
+
|
|
18
|
+
shoryuken_options auto_delete: true
|
|
19
|
+
|
|
20
|
+
def perform(sqs_msg, body)
|
|
21
|
+
DT[:processed_messages] << {
|
|
22
|
+
message_id: sqs_msg.message_id,
|
|
23
|
+
body: body,
|
|
24
|
+
processed_at: Time.now
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
enqueueing_worker.get_shoryuken_options['queue'] = queue_name
|
|
30
|
+
Shoryuken.register_worker(queue_name, enqueueing_worker)
|
|
31
|
+
|
|
32
|
+
# Test 1: perform_async - immediate enqueueing with string body
|
|
33
|
+
enqueueing_worker.perform_async('async string message')
|
|
34
|
+
|
|
35
|
+
# Test 2: perform_async with hash body
|
|
36
|
+
enqueueing_worker.perform_async('action' => 'test', 'data' => [1, 2, 3])
|
|
37
|
+
|
|
38
|
+
# Test 3: perform_in - delayed enqueueing (use short 1 second delay)
|
|
39
|
+
enqueueing_worker.perform_in(1, 'delayed message')
|
|
40
|
+
|
|
41
|
+
sleep 1
|
|
42
|
+
|
|
43
|
+
# Poll for all 3 messages
|
|
44
|
+
poll_queues_until(timeout: 15) { DT[:processed_messages].size >= 3 }
|
|
45
|
+
|
|
46
|
+
assert_equal(3, DT[:processed_messages].size)
|
|
47
|
+
|
|
48
|
+
# Verify string message was processed
|
|
49
|
+
string_msg = DT[:processed_messages].find { |m| m[:body] == 'async string message' }
|
|
50
|
+
assert(string_msg, 'String message should have been processed')
|
|
51
|
+
|
|
52
|
+
# Verify hash message was processed (bodies might be stringified depending on serialization)
|
|
53
|
+
hash_msg = DT[:processed_messages].find do |m|
|
|
54
|
+
m[:body].is_a?(Hash) || (m[:body].is_a?(String) && m[:body].include?('action'))
|
|
55
|
+
end
|
|
56
|
+
assert(hash_msg, 'Hash message should have been processed')
|
|
57
|
+
|
|
58
|
+
# Verify delayed message was processed
|
|
59
|
+
delayed_msg = DT[:processed_messages].find { |m| m[:body] == 'delayed message' }
|
|
60
|
+
assert(delayed_msg, 'Delayed message should have been processed')
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests multiple worker groups with different concurrency settings.
|
|
4
|
+
# Each group can have its own queues and concurrency level.
|
|
5
|
+
|
|
6
|
+
require 'concurrent'
|
|
7
|
+
|
|
8
|
+
setup_localstack
|
|
9
|
+
|
|
10
|
+
queue_group1 = DT.queues[0]
|
|
11
|
+
queue_group2 = DT.queues[1]
|
|
12
|
+
|
|
13
|
+
%w[queue_group1 queue_group2].each_with_index { |_, i| create_test_queue(DT.queues[i]) }
|
|
14
|
+
|
|
15
|
+
# Configure two separate groups with different concurrency
|
|
16
|
+
Shoryuken.add_group('group1', 3) # 3 concurrent processors
|
|
17
|
+
Shoryuken.add_group('group2', 1) # 1 concurrent processor
|
|
18
|
+
|
|
19
|
+
Shoryuken.add_queue(queue_group1, 1, 'group1')
|
|
20
|
+
Shoryuken.add_queue(queue_group2, 1, 'group2')
|
|
21
|
+
|
|
22
|
+
# Track concurrent processing per group
|
|
23
|
+
group1_concurrent = Concurrent::AtomicFixnum.new(0)
|
|
24
|
+
group1_max = Concurrent::AtomicFixnum.new(0)
|
|
25
|
+
group2_concurrent = Concurrent::AtomicFixnum.new(0)
|
|
26
|
+
group2_max = Concurrent::AtomicFixnum.new(0)
|
|
27
|
+
|
|
28
|
+
worker_class = Class.new do
|
|
29
|
+
include Shoryuken::Worker
|
|
30
|
+
|
|
31
|
+
shoryuken_options auto_delete: true, batch: false
|
|
32
|
+
|
|
33
|
+
define_method(:perform) do |sqs_msg, body|
|
|
34
|
+
queue = sqs_msg.queue_url.split('/').last
|
|
35
|
+
|
|
36
|
+
if queue == queue_group1
|
|
37
|
+
group1_concurrent.increment
|
|
38
|
+
current = group1_concurrent.value
|
|
39
|
+
group1_max.update { |max| [max, current].max }
|
|
40
|
+
sleep 0.5 # Longer sleep to increase chance of concurrent execution
|
|
41
|
+
group1_concurrent.decrement
|
|
42
|
+
else
|
|
43
|
+
group2_concurrent.increment
|
|
44
|
+
current = group2_concurrent.value
|
|
45
|
+
group2_max.update { |max| [max, current].max }
|
|
46
|
+
sleep 0.3
|
|
47
|
+
group2_concurrent.decrement
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
DT[:processed] << { queue: queue, body: body }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
[queue_group1, queue_group2].each do |queue|
|
|
55
|
+
worker_class.get_shoryuken_options['queue'] = queue
|
|
56
|
+
Shoryuken.register_worker(queue, worker_class)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Send messages to both groups
|
|
60
|
+
5.times { |i| Shoryuken::Client.queues(queue_group1).send_message(message_body: "group1-#{i}") }
|
|
61
|
+
5.times { |i| Shoryuken::Client.queues(queue_group2).send_message(message_body: "group2-#{i}") }
|
|
62
|
+
|
|
63
|
+
sleep 1
|
|
64
|
+
|
|
65
|
+
poll_queues_until(timeout: 20) { DT[:processed].size >= 10 }
|
|
66
|
+
|
|
67
|
+
assert_equal(10, DT[:processed].size)
|
|
68
|
+
|
|
69
|
+
# Verify messages from both groups were processed
|
|
70
|
+
group1_messages = DT[:processed].select { |m| m[:queue] == queue_group1 }
|
|
71
|
+
group2_messages = DT[:processed].select { |m| m[:queue] == queue_group2 }
|
|
72
|
+
|
|
73
|
+
assert_equal(5, group1_messages.size, 'All group1 messages should be processed')
|
|
74
|
+
assert_equal(5, group2_messages.size, 'All group2 messages should be processed')
|
|
75
|
+
|
|
76
|
+
# Verify concurrency was used - group1 with concurrency 3 should process concurrently
|
|
77
|
+
# group2 with concurrency 1 should process sequentially (max = 1)
|
|
78
|
+
assert(group1_max.value >= 1, "Group1 should have processed messages (max concurrent: #{group1_max.value})")
|
|
79
|
+
assert_equal(1, group2_max.value, 'Group2 with concurrency 1 should process sequentially')
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests worker lifecycle including worker registration and discovery.
|
|
4
|
+
|
|
5
|
+
setup_localstack
|
|
6
|
+
|
|
7
|
+
queue_name = DT.queue
|
|
8
|
+
create_test_queue(queue_name)
|
|
9
|
+
Shoryuken.add_group('default', 1)
|
|
10
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
11
|
+
|
|
12
|
+
worker_class = Class.new do
|
|
13
|
+
include Shoryuken::Worker
|
|
14
|
+
|
|
15
|
+
def perform(sqs_msg, body)
|
|
16
|
+
DT[:messages] << body
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
worker_class.get_shoryuken_options['queue'] = queue_name
|
|
21
|
+
worker_class.get_shoryuken_options['auto_delete'] = true
|
|
22
|
+
worker_class.get_shoryuken_options['batch'] = false
|
|
23
|
+
Shoryuken.register_worker(queue_name, worker_class)
|
|
24
|
+
|
|
25
|
+
registered = Shoryuken.worker_registry.workers(queue_name)
|
|
26
|
+
assert_includes(registered, worker_class)
|
|
27
|
+
|
|
28
|
+
Shoryuken::Client.queues(queue_name).send_message(message_body: 'lifecycle-test')
|
|
29
|
+
|
|
30
|
+
poll_queues_until { DT[:messages].size >= 1 }
|
|
31
|
+
|
|
32
|
+
assert_equal(1, DT[:messages].size)
|
|
33
|
+
assert_equal('lifecycle-test', DT[:messages].first)
|