shoryuken 6.2.1 → 7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/push.yml +36 -0
- data/.github/workflows/specs.yml +49 -44
- data/.github/workflows/verify-action-pins.yml +16 -0
- data/.gitignore +4 -1
- data/.rspec +3 -1
- data/.rubocop.yml +6 -1
- data/.ruby-version +1 -0
- data/.yard-lint.yml +279 -0
- data/CHANGELOG.md +308 -139
- data/Gemfile +1 -8
- data/Gemfile.lint +9 -0
- data/Gemfile.lint.lock +69 -0
- data/README.md +16 -33
- data/Rakefile +6 -10
- data/bin/clean_sqs +52 -0
- data/bin/cli/base.rb +22 -2
- data/bin/cli/sqs.rb +74 -7
- data/bin/integrations +275 -0
- data/bin/scenario +154 -0
- data/bin/shoryuken +3 -2
- data/docker-compose.yml +6 -0
- data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +20 -6
- data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
- data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
- data/lib/shoryuken/active_job/current_attributes.rb +139 -0
- data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
- data/lib/shoryuken/body_parser.rb +11 -1
- data/lib/shoryuken/client.rb +16 -0
- data/lib/shoryuken/default_exception_handler.rb +11 -0
- data/lib/shoryuken/default_worker_registry.rb +39 -11
- data/lib/shoryuken/environment_loader.rb +85 -15
- data/lib/shoryuken/errors.rb +36 -0
- data/lib/shoryuken/fetcher.rb +41 -3
- data/lib/shoryuken/helpers/atomic_boolean.rb +58 -0
- data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
- data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
- data/lib/shoryuken/helpers/hash_utils.rb +56 -0
- data/lib/shoryuken/helpers/string_utils.rb +65 -0
- data/lib/shoryuken/helpers/timer_task.rb +80 -0
- data/lib/shoryuken/inline_message.rb +22 -0
- data/lib/shoryuken/launcher.rb +55 -0
- data/lib/shoryuken/logging/base.rb +26 -0
- data/lib/shoryuken/logging/pretty.rb +25 -0
- data/lib/shoryuken/logging/without_timestamp.rb +25 -0
- data/lib/shoryuken/logging.rb +43 -15
- data/lib/shoryuken/manager.rb +84 -5
- data/lib/shoryuken/message.rb +116 -1
- data/lib/shoryuken/middleware/chain.rb +141 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +10 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +12 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +37 -11
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +34 -3
- data/lib/shoryuken/middleware/server/non_retryable_exception.rb +95 -0
- data/lib/shoryuken/middleware/server/timing.rb +13 -0
- data/lib/shoryuken/options.rb +154 -13
- data/lib/shoryuken/polling/base_strategy.rb +127 -0
- data/lib/shoryuken/polling/queue_configuration.rb +103 -0
- data/lib/shoryuken/polling/strict_priority.rb +41 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +44 -0
- data/lib/shoryuken/processor.rb +37 -3
- data/lib/shoryuken/queue.rb +99 -8
- data/lib/shoryuken/runner.rb +54 -16
- data/lib/shoryuken/util.rb +32 -7
- data/lib/shoryuken/version.rb +4 -1
- data/lib/shoryuken/worker/default_executor.rb +23 -1
- data/lib/shoryuken/worker/inline_executor.rb +33 -2
- data/lib/shoryuken/worker.rb +224 -0
- data/lib/shoryuken/worker_registry.rb +35 -0
- data/lib/shoryuken.rb +27 -38
- data/renovate.json +62 -0
- data/shoryuken.gemspec +8 -4
- data/spec/integration/.rspec +1 -0
- data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
- data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
- data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
- data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
- data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
- data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
- data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
- data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
- data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
- data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
- data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
- data/spec/integration/active_job/keyword_arguments/keyword_arguments_spec.rb +63 -0
- data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
- data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
- data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
- data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
- data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
- data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
- data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
- data/spec/integration/aws_config/aws_config_spec.rb +59 -0
- data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
- data/spec/integration/body_parser/json_parser_spec.rb +45 -0
- data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
- data/spec/integration/body_parser/text_parser_spec.rb +43 -0
- data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
- data/spec/integration/custom_group_polling_strategy/custom_group_polling_strategy_spec.rb +87 -0
- data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
- data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
- data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
- data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
- data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
- data/spec/integration/launcher/launcher_spec.rb +40 -0
- data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
- data/spec/integration/message_operations/message_operations_spec.rb +59 -0
- data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
- data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
- data/spec/integration/middleware_chain/removal_spec.rb +31 -0
- data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
- data/spec/integration/non_retryable_exception/non_retryable_exception_spec.rb +149 -0
- data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
- data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
- data/spec/integration/rails/rails_72/Gemfile +6 -0
- data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/Gemfile +6 -0
- data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
- data/spec/integration/rails/rails_81/Gemfile +6 -0
- data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
- data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
- data/spec/integration/spec_helper.rb +7 -0
- data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
- data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
- data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
- data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
- data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
- data/spec/integrations_helper.rb +243 -0
- data/spec/lib/active_job/extensions_spec.rb +225 -0
- data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
- data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +5 -4
- data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +6 -5
- data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +2 -4
- data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +9 -10
- data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +1 -2
- data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +10 -9
- data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +23 -26
- data/spec/lib/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
- data/spec/lib/shoryuken/helpers/atomic_counter_spec.rb +177 -0
- data/spec/lib/shoryuken/helpers/atomic_hash_spec.rb +307 -0
- data/spec/lib/shoryuken/helpers/hash_utils_spec.rb +145 -0
- data/spec/lib/shoryuken/helpers/string_utils_spec.rb +124 -0
- data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/lib/shoryuken/helpers_integration_spec.rb +96 -0
- data/spec/lib/shoryuken/inline_message_spec.rb +196 -0
- data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +23 -2
- data/spec/lib/shoryuken/logging_spec.rb +242 -0
- data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +1 -2
- data/spec/lib/shoryuken/message_spec.rb +109 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +1 -1
- data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
- data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +51 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +1 -1
- data/spec/lib/shoryuken/middleware/server/non_retryable_exception_spec.rb +214 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +49 -6
- data/spec/lib/shoryuken/polling/base_strategy_spec.rb +280 -0
- data/spec/lib/shoryuken/polling/queue_configuration_spec.rb +195 -0
- data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +2 -3
- data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +1 -3
- data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +2 -2
- data/spec/lib/shoryuken/version_spec.rb +17 -0
- data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +1 -1
- data/spec/lib/shoryuken/worker/inline_executor_spec.rb +105 -0
- data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
- data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +15 -11
- data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +1 -1
- data/spec/shared_examples_for_active_job.rb +40 -15
- data/spec/spec_helper.rb +48 -2
- metadata +295 -101
- data/.codeclimate.yml +0 -20
- data/.devcontainer/Dockerfile +0 -17
- data/.devcontainer/base.Dockerfile +0 -43
- data/.devcontainer/devcontainer.json +0 -35
- data/.github/FUNDING.yml +0 -12
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/stale.yml +0 -20
- data/.reek.yml +0 -5
- data/Appraisals +0 -42
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/aws_sdk_core_2.gemfile +0 -21
- data/gemfiles/rails_4_2.gemfile +0 -20
- data/gemfiles/rails_5_2.gemfile +0 -21
- data/gemfiles/rails_6_0.gemfile +0 -21
- data/gemfiles/rails_6_1.gemfile +0 -21
- data/gemfiles/rails_7_0.gemfile +0 -22
- data/lib/shoryuken/core_ext.rb +0 -69
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -103
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
- data/lib/shoryuken/polling/base.rb +0 -67
- data/shoryuken.jpg +0 -0
- data/spec/integration/launcher_spec.rb +0 -128
- data/spec/shoryuken/core_ext_spec.rb +0 -40
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -7
- data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -84
- data/spec/shoryuken/worker/inline_executor_spec.rb +0 -49
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Shoryuken::Middleware::Server::ActiveRecord do
|
|
6
|
+
subject { described_class.new }
|
|
7
|
+
|
|
8
|
+
# Mock ActiveRecord to avoid requiring the actual gem in tests
|
|
9
|
+
before do
|
|
10
|
+
# Create mock ActiveRecord module
|
|
11
|
+
active_record_module = Module.new
|
|
12
|
+
|
|
13
|
+
# Create mock Base class with simplified methods
|
|
14
|
+
active_record_base = Class.new do
|
|
15
|
+
@connection_handler = nil
|
|
16
|
+
|
|
17
|
+
def self.clear_active_connections!
|
|
18
|
+
# Mock implementation for Rails < 7.1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.connection_handler
|
|
22
|
+
@connection_handler ||= Object.new.tap do |handler|
|
|
23
|
+
def handler.clear_active_connections!(_pool_key)
|
|
24
|
+
# Mock implementation for Rails 7.1+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
active_record_module.const_set('Base', active_record_base)
|
|
31
|
+
stub_const('ActiveRecord', active_record_module)
|
|
32
|
+
|
|
33
|
+
# Mock version checking - start with a simple approach
|
|
34
|
+
def active_record_module.version
|
|
35
|
+
@version ||= Object.new.tap do |v|
|
|
36
|
+
def v.>=(other)
|
|
37
|
+
# For our tests, we'll control this with instance variables
|
|
38
|
+
@is_rails_71_or_higher ||= false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def v.rails_71_or_higher!
|
|
42
|
+
@is_rails_71_or_higher = true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def v.rails_70!
|
|
46
|
+
@is_rails_71_or_higher = false
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Mock Gem::Version
|
|
52
|
+
unless defined?(Gem::Version)
|
|
53
|
+
gem_module = Module.new
|
|
54
|
+
gem_version_class = Class.new do
|
|
55
|
+
def initialize(_version)
|
|
56
|
+
# Simple mock
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
gem_module.const_set('Version', gem_version_class)
|
|
60
|
+
stub_const('Gem', gem_module)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe '#call' do
|
|
65
|
+
it 'yields to the block' do
|
|
66
|
+
block_called = false
|
|
67
|
+
subject.call do
|
|
68
|
+
block_called = true
|
|
69
|
+
end
|
|
70
|
+
expect(block_called).to be true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'returns the value from the block' do
|
|
74
|
+
result = subject.call { 'block_result' }
|
|
75
|
+
expect(result).to eq('block_result')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context 'when ActiveRecord version is 7.1 or higher' do
|
|
79
|
+
before do
|
|
80
|
+
# Mock Rails 7.1+ behavior
|
|
81
|
+
allow(ActiveRecord).to receive(:version).and_return(double('>=' => true))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'calls clear_active_connections! on connection_handler with :all parameter' do
|
|
85
|
+
connection_handler = ActiveRecord::Base.connection_handler
|
|
86
|
+
expect(connection_handler).to receive(:clear_active_connections!).with(:all)
|
|
87
|
+
|
|
88
|
+
subject.call { 'test' }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'clears connections even when an exception is raised' do
|
|
92
|
+
connection_handler = ActiveRecord::Base.connection_handler
|
|
93
|
+
expect(connection_handler).to receive(:clear_active_connections!).with(:all)
|
|
94
|
+
|
|
95
|
+
expect do
|
|
96
|
+
subject.call { raise StandardError, 'test error' }
|
|
97
|
+
end.to raise_error(StandardError, 'test error')
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
context 'when ActiveRecord version is lower than 7.1' do
|
|
102
|
+
before do
|
|
103
|
+
# Mock Rails < 7.1 behavior
|
|
104
|
+
allow(ActiveRecord).to receive(:version).and_return(double('>=' => false))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'calls clear_active_connections! directly on ActiveRecord::Base' do
|
|
108
|
+
expect(ActiveRecord::Base).to receive(:clear_active_connections!)
|
|
109
|
+
|
|
110
|
+
subject.call { 'test' }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'clears connections even when an exception is raised' do
|
|
114
|
+
expect(ActiveRecord::Base).to receive(:clear_active_connections!)
|
|
115
|
+
|
|
116
|
+
expect do
|
|
117
|
+
subject.call { raise StandardError, 'test error' }
|
|
118
|
+
end.to raise_error(StandardError, 'test error')
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'works with middleware arguments (ignores them)' do
|
|
123
|
+
allow(ActiveRecord).to receive(:version).and_return(double('>=' => false))
|
|
124
|
+
expect(ActiveRecord::Base).to receive(:clear_active_connections!)
|
|
125
|
+
|
|
126
|
+
worker = double('worker')
|
|
127
|
+
message = double('message')
|
|
128
|
+
|
|
129
|
+
result = subject.call(worker, message) { 'middleware_result' }
|
|
130
|
+
expect(result).to eq('middleware_result')
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
RSpec.describe Shoryuken::Middleware::Server::AutoExtendVisibility do
|
|
4
4
|
let(:queue) { 'default' }
|
|
@@ -64,4 +64,54 @@ RSpec.describe Shoryuken::Middleware::Server::AutoExtendVisibility do
|
|
|
64
64
|
|
|
65
65
|
Runner.new.run_and_sleep(TestWorker.new, queue, sqs_msg, visibility_timeout)
|
|
66
66
|
end
|
|
67
|
+
|
|
68
|
+
context 'when batch worker with auto_visibility_timeout' do
|
|
69
|
+
it 'warns and does not extend visibility for batch workers' do
|
|
70
|
+
TestWorker.get_shoryuken_options['auto_visibility_timeout'] = true
|
|
71
|
+
|
|
72
|
+
expect(Shoryuken.logger).to receive(:warn) do |&block|
|
|
73
|
+
expect(block.call).to include("Auto extend visibility isn't supported for batch workers")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
expect { |b| subject.call(TestWorker.new, queue, [sqs_msg], nil, &b) }.to yield_control
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context 'when visibility extension fails' do
|
|
81
|
+
it 'logs error when change_visibility raises an exception' do
|
|
82
|
+
TestWorker.get_shoryuken_options['auto_visibility_timeout'] = true
|
|
83
|
+
|
|
84
|
+
allow(sqs_msg).to receive(:queue) { sqs_queue }
|
|
85
|
+
allow(sqs_msg).to receive(:message_id).and_return('test-message-id')
|
|
86
|
+
allow(sqs_msg).to receive(:change_visibility).and_raise(StandardError, 'AWS error')
|
|
87
|
+
|
|
88
|
+
expect(Shoryuken.logger).to receive(:error) do |&block|
|
|
89
|
+
msg = block.call
|
|
90
|
+
expect(msg).to include('Could not auto extend the message')
|
|
91
|
+
expect(msg).to include('test-message-id')
|
|
92
|
+
expect(msg).to include('AWS error')
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
Runner.new.run_and_sleep(TestWorker.new, queue, sqs_msg, visibility_timeout)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context 'debug logging' do
|
|
100
|
+
it 'logs debug message when extending visibility' do
|
|
101
|
+
TestWorker.get_shoryuken_options['auto_visibility_timeout'] = true
|
|
102
|
+
|
|
103
|
+
allow(sqs_msg).to receive(:queue) { sqs_queue }
|
|
104
|
+
allow(sqs_msg).to receive(:message_id).and_return('test-message-id')
|
|
105
|
+
allow(sqs_msg).to receive(:change_visibility)
|
|
106
|
+
|
|
107
|
+
expect(Shoryuken.logger).to receive(:debug) do |&block|
|
|
108
|
+
msg = block.call
|
|
109
|
+
expect(msg).to include('Extending message')
|
|
110
|
+
expect(msg).to include('test-message-id')
|
|
111
|
+
expect(msg).to include("by #{visibility_timeout}s")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
Runner.new.run_and_sleep(TestWorker.new, queue, sqs_msg, visibility_timeout)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
67
117
|
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Shoryuken::Middleware::Server::NonRetryableException do
|
|
4
|
+
let(:queue) { 'default' }
|
|
5
|
+
let(:sqs_queue) { double Shoryuken::Queue }
|
|
6
|
+
|
|
7
|
+
def build_message
|
|
8
|
+
double Shoryuken::Message,
|
|
9
|
+
queue_url: queue,
|
|
10
|
+
body: 'test',
|
|
11
|
+
message_id: SecureRandom.uuid,
|
|
12
|
+
receipt_handle: SecureRandom.uuid
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:sqs_msg) { build_message }
|
|
16
|
+
|
|
17
|
+
before do
|
|
18
|
+
allow(Shoryuken::Client).to receive(:queues).with(queue).and_return(sqs_queue)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
context 'when non_retryable_exceptions is not configured' do
|
|
22
|
+
it 're-raises the exception' do
|
|
23
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = nil
|
|
24
|
+
|
|
25
|
+
expect(sqs_queue).not_to receive(:delete_messages)
|
|
26
|
+
|
|
27
|
+
expect {
|
|
28
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise StandardError, 'test error' }
|
|
29
|
+
}.to raise_error(StandardError, 'test error')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'when exception is not in non_retryable_exceptions list' do
|
|
34
|
+
it 're-raises the exception' do
|
|
35
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = [ArgumentError]
|
|
36
|
+
|
|
37
|
+
expect(sqs_queue).not_to receive(:delete_messages)
|
|
38
|
+
|
|
39
|
+
expect {
|
|
40
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise StandardError, 'test error' }
|
|
41
|
+
}.to raise_error(StandardError, 'test error')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'when exception is in non_retryable_exceptions list' do
|
|
46
|
+
it 'deletes the message and does not re-raise' do
|
|
47
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = [StandardError]
|
|
48
|
+
|
|
49
|
+
expect(sqs_queue).to receive(:delete_messages).with(entries: [
|
|
50
|
+
{ id: '0', receipt_handle: sqs_msg.receipt_handle }
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
expect(Shoryuken.logger).to receive(:warn) do |&block|
|
|
54
|
+
expect(block.call).to match(/Non-retryable exception StandardError/)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
expect {
|
|
58
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise StandardError, 'test error' }
|
|
59
|
+
}.not_to raise_error
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'logs the exception backtrace in debug mode' do
|
|
63
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = [StandardError]
|
|
64
|
+
|
|
65
|
+
error = StandardError.new('test error')
|
|
66
|
+
error.set_backtrace(['backtrace line 1', 'backtrace line 2'])
|
|
67
|
+
|
|
68
|
+
allow(sqs_queue).to receive(:delete_messages)
|
|
69
|
+
|
|
70
|
+
expect(Shoryuken.logger).to receive(:warn)
|
|
71
|
+
expect(Shoryuken.logger).to receive(:debug) do |&block|
|
|
72
|
+
expect(block.call).to eq("backtrace line 1\nbacktrace line 2")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise error }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'handles multiple exception classes' do
|
|
79
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = [ArgumentError, StandardError]
|
|
80
|
+
|
|
81
|
+
expect(sqs_queue).to receive(:delete_messages).with(entries: [
|
|
82
|
+
{ id: '0', receipt_handle: sqs_msg.receipt_handle }
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
expect {
|
|
86
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise ArgumentError, 'test error' }
|
|
87
|
+
}.not_to raise_error
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'handles custom exception classes' do
|
|
91
|
+
custom_error = Class.new(StandardError)
|
|
92
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = [custom_error]
|
|
93
|
+
|
|
94
|
+
expect(sqs_queue).to receive(:delete_messages).with(entries: [
|
|
95
|
+
{ id: '0', receipt_handle: sqs_msg.receipt_handle }
|
|
96
|
+
])
|
|
97
|
+
|
|
98
|
+
expect {
|
|
99
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise custom_error, 'test error' }
|
|
100
|
+
}.not_to raise_error
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
context 'with batch messages' do
|
|
105
|
+
it 'deletes all messages in the batch' do
|
|
106
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = [StandardError]
|
|
107
|
+
|
|
108
|
+
sqs_msg2 = build_message
|
|
109
|
+
sqs_msg3 = build_message
|
|
110
|
+
sqs_msgs = [sqs_msg, sqs_msg2, sqs_msg3]
|
|
111
|
+
|
|
112
|
+
expect(sqs_queue).to receive(:delete_messages).with(entries: [
|
|
113
|
+
{ id: '0', receipt_handle: sqs_msg.receipt_handle },
|
|
114
|
+
{ id: '1', receipt_handle: sqs_msg2.receipt_handle },
|
|
115
|
+
{ id: '2', receipt_handle: sqs_msg3.receipt_handle }
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
expect(Shoryuken.logger).to receive(:warn) do |&block|
|
|
119
|
+
expect(block.call).to match(/Non-retryable exception StandardError/)
|
|
120
|
+
expect(block.call).to match(/#{sqs_msg.message_id}/)
|
|
121
|
+
expect(block.call).to match(/#{sqs_msg2.message_id}/)
|
|
122
|
+
expect(block.call).to match(/#{sqs_msg3.message_id}/)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
expect {
|
|
126
|
+
subject.call(TestWorker.new, queue, sqs_msgs, [sqs_msg.body, sqs_msg2.body, sqs_msg3.body]) do
|
|
127
|
+
raise StandardError, 'test error'
|
|
128
|
+
end
|
|
129
|
+
}.not_to raise_error
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
context 'when no exception occurs' do
|
|
134
|
+
it 'does not delete the message' do
|
|
135
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = [StandardError]
|
|
136
|
+
|
|
137
|
+
expect(sqs_queue).not_to receive(:delete_messages)
|
|
138
|
+
|
|
139
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) {}
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context 'when using lambda for dynamic classification' do
|
|
144
|
+
it 'calls the lambda with the exception and deletes if lambda returns true' do
|
|
145
|
+
lambda_called = false
|
|
146
|
+
lambda_result = true
|
|
147
|
+
|
|
148
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = lambda do |error|
|
|
149
|
+
lambda_called = true
|
|
150
|
+
expect(error).to be_a(StandardError)
|
|
151
|
+
expect(error.message).to eq('test error')
|
|
152
|
+
lambda_result
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
expect(sqs_queue).to receive(:delete_messages).with(entries: [
|
|
156
|
+
{ id: '0', receipt_handle: sqs_msg.receipt_handle }
|
|
157
|
+
])
|
|
158
|
+
|
|
159
|
+
expect {
|
|
160
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise StandardError, 'test error' }
|
|
161
|
+
}.not_to raise_error
|
|
162
|
+
|
|
163
|
+
expect(lambda_called).to be true
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 're-raises if lambda returns false' do
|
|
167
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = ->(_error) { false }
|
|
168
|
+
|
|
169
|
+
expect(sqs_queue).not_to receive(:delete_messages)
|
|
170
|
+
|
|
171
|
+
expect {
|
|
172
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise StandardError, 'test error' }
|
|
173
|
+
}.to raise_error(StandardError, 'test error')
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'supports complex lambda logic based on exception properties' do
|
|
177
|
+
TestWorker.get_shoryuken_options['non_retryable_exceptions'] = lambda do |error|
|
|
178
|
+
error.is_a?(ArgumentError) || (error.is_a?(StandardError) && error.message.include?('permanent'))
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# ArgumentError should be deleted
|
|
182
|
+
expect(sqs_queue).to receive(:delete_messages).with(entries: [
|
|
183
|
+
{ id: '0', receipt_handle: sqs_msg.receipt_handle }
|
|
184
|
+
])
|
|
185
|
+
|
|
186
|
+
expect {
|
|
187
|
+
subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise ArgumentError, 'invalid argument' }
|
|
188
|
+
}.not_to raise_error
|
|
189
|
+
|
|
190
|
+
# StandardError with 'permanent' should be deleted
|
|
191
|
+
sqs_msg2 = build_message
|
|
192
|
+
allow(Shoryuken::Client).to receive(:queues).with(queue).and_return(sqs_queue)
|
|
193
|
+
|
|
194
|
+
expect(sqs_queue).to receive(:delete_messages).with(entries: [
|
|
195
|
+
{ id: '0', receipt_handle: sqs_msg2.receipt_handle }
|
|
196
|
+
])
|
|
197
|
+
|
|
198
|
+
expect {
|
|
199
|
+
subject.call(TestWorker.new, queue, sqs_msg2, sqs_msg2.body) { raise StandardError, 'permanent failure' }
|
|
200
|
+
}.not_to raise_error
|
|
201
|
+
|
|
202
|
+
# StandardError without 'permanent' should be retried
|
|
203
|
+
sqs_msg3 = build_message
|
|
204
|
+
allow(Shoryuken::Client).to receive(:queues).with(queue).and_return(sqs_queue)
|
|
205
|
+
|
|
206
|
+
expect(sqs_queue).not_to receive(:delete_messages)
|
|
207
|
+
|
|
208
|
+
expect {
|
|
209
|
+
subject.call(TestWorker.new, queue, sqs_msg3, sqs_msg3.body) { raise StandardError, 'temporary failure' }
|
|
210
|
+
}.to raise_error(StandardError, 'temporary failure')
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
RSpec.describe Shoryuken::Options do
|
|
4
4
|
subject { Shoryuken.shoryuken_options }
|
|
5
5
|
|
|
6
6
|
describe '.on_stop' do
|
|
7
7
|
specify do
|
|
8
|
-
on_stop =
|
|
8
|
+
on_stop = proc {}
|
|
9
9
|
Shoryuken.on_stop(&on_stop)
|
|
10
10
|
|
|
11
11
|
expect(Shoryuken.stop_callback).to eq(on_stop)
|
|
@@ -14,7 +14,7 @@ RSpec.describe Shoryuken::Options do
|
|
|
14
14
|
|
|
15
15
|
describe '.on_start' do
|
|
16
16
|
specify do
|
|
17
|
-
on_start =
|
|
17
|
+
on_start = proc {}
|
|
18
18
|
Shoryuken.on_start(&on_start)
|
|
19
19
|
|
|
20
20
|
expect(Shoryuken.start_callback).to eq(on_start)
|
|
@@ -41,6 +41,36 @@ RSpec.describe Shoryuken::Options do
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
describe '.add_group with polling_strategy' do
|
|
45
|
+
before do
|
|
46
|
+
Shoryuken.groups.clear
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'stores the polling_strategy in the group configuration' do
|
|
50
|
+
Shoryuken.add_group('group1', 25, polling_strategy: Shoryuken::Polling::StrictPriority)
|
|
51
|
+
|
|
52
|
+
expect(Shoryuken.groups['group1'][:polling_strategy]).to eq(Shoryuken::Polling::StrictPriority)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'defaults polling_strategy to nil when not provided' do
|
|
56
|
+
Shoryuken.add_group('group1', 25)
|
|
57
|
+
|
|
58
|
+
expect(Shoryuken.groups['group1'][:polling_strategy]).to be_nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'accepts a string polling_strategy' do
|
|
62
|
+
Shoryuken.add_group('group1', 25, polling_strategy: 'StrictPriority')
|
|
63
|
+
|
|
64
|
+
expect(Shoryuken.groups['group1'][:polling_strategy]).to eq('StrictPriority')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'raises InvalidPollingStrategyError for invalid polling_strategy type' do
|
|
68
|
+
expect {
|
|
69
|
+
Shoryuken.add_group('group1', 25, polling_strategy: 123)
|
|
70
|
+
}.to raise_error(Shoryuken::Errors::InvalidPollingStrategyError)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
44
74
|
describe '.delay works for each group' do
|
|
45
75
|
specify do
|
|
46
76
|
Shoryuken.add_group('group1', 25)
|
|
@@ -81,7 +111,7 @@ RSpec.describe Shoryuken::Options do
|
|
|
81
111
|
|
|
82
112
|
expect(Shoryuken.sqs_client_receive_message_opts).to eq(
|
|
83
113
|
'default' => { test: 1 },
|
|
84
|
-
'group1'
|
|
114
|
+
'group1' => { test: 2 }
|
|
85
115
|
)
|
|
86
116
|
end
|
|
87
117
|
end
|
|
@@ -188,8 +218,8 @@ RSpec.describe Shoryuken::Options do
|
|
|
188
218
|
end
|
|
189
219
|
|
|
190
220
|
specify do
|
|
191
|
-
expect { Shoryuken.polling_strategy('default') }.to raise_error(
|
|
192
|
-
expect { Shoryuken.polling_strategy('group1') }.to raise_error(
|
|
221
|
+
expect { Shoryuken.polling_strategy('default') }.to raise_error(Shoryuken::Errors::InvalidPollingStrategyError)
|
|
222
|
+
expect { Shoryuken.polling_strategy('group1') }.to raise_error(Shoryuken::Errors::InvalidPollingStrategyError)
|
|
193
223
|
end
|
|
194
224
|
end
|
|
195
225
|
|
|
@@ -212,5 +242,18 @@ RSpec.describe Shoryuken::Options do
|
|
|
212
242
|
expect(Shoryuken.polling_strategy('group1')).to eq Bar
|
|
213
243
|
end
|
|
214
244
|
end
|
|
245
|
+
|
|
246
|
+
context 'when set via add_group' do
|
|
247
|
+
before do
|
|
248
|
+
class Baz < Shoryuken::Polling::BaseStrategy; end
|
|
249
|
+
|
|
250
|
+
Shoryuken.groups.clear
|
|
251
|
+
Shoryuken.add_group('custom_group', 25, polling_strategy: Baz)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
specify do
|
|
255
|
+
expect(Shoryuken.polling_strategy('custom_group')).to eq Baz
|
|
256
|
+
end
|
|
257
|
+
end
|
|
215
258
|
end
|
|
216
259
|
end
|