shoryuken 7.0.0.alpha1 → 7.0.0.rc1
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 +3 -3
- data/.github/workflows/specs.yml +27 -17
- data/.github/workflows/verify-action-pins.yml +1 -1
- data/.rspec +2 -1
- data/.ruby-version +1 -1
- data/Appraisals +6 -18
- data/CHANGELOG.md +200 -142
- data/Gemfile +1 -0
- data/README.md +12 -13
- data/bin/cli/base.rb +1 -2
- data/bin/cli/sqs.rb +6 -5
- data/bin/shoryuken +3 -2
- data/gemfiles/rails_7_2.gemfile +1 -0
- data/gemfiles/rails_8_0.gemfile +1 -0
- data/gemfiles/{rails_7_1.gemfile → rails_8_1.gemfile} +2 -1
- data/lib/shoryuken/body_parser.rb +3 -1
- data/lib/shoryuken/client.rb +2 -0
- data/lib/shoryuken/default_exception_handler.rb +2 -0
- data/lib/shoryuken/default_worker_registry.rb +11 -11
- data/lib/shoryuken/environment_loader.rb +6 -6
- data/lib/shoryuken/extensions/active_job_adapter.rb +21 -6
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +5 -5
- data/lib/shoryuken/extensions/active_job_extensions.rb +2 -0
- data/lib/shoryuken/fetcher.rb +4 -2
- data/lib/shoryuken/helpers/atomic_boolean.rb +44 -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 +66 -0
- data/lib/shoryuken/inline_message.rb +22 -0
- data/lib/shoryuken/launcher.rb +16 -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 +6 -12
- data/lib/shoryuken/manager.rb +6 -4
- data/lib/shoryuken/message.rb +116 -1
- data/lib/shoryuken/middleware/chain.rb +140 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +2 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +2 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +11 -11
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +5 -3
- data/lib/shoryuken/middleware/server/timing.rb +2 -0
- data/lib/shoryuken/options.rb +9 -5
- data/lib/shoryuken/polling/base_strategy.rb +126 -0
- data/lib/shoryuken/polling/queue_configuration.rb +103 -0
- data/lib/shoryuken/polling/strict_priority.rb +2 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +2 -0
- data/lib/shoryuken/processor.rb +5 -2
- data/lib/shoryuken/queue.rb +6 -4
- data/lib/shoryuken/runner.rb +12 -12
- data/lib/shoryuken/util.rb +6 -6
- data/lib/shoryuken/version.rb +3 -1
- data/lib/shoryuken/worker/default_executor.rb +2 -0
- data/lib/shoryuken/worker/inline_executor.rb +3 -1
- data/lib/shoryuken/worker.rb +173 -0
- data/lib/shoryuken/worker_registry.rb +2 -0
- data/lib/shoryuken.rb +8 -28
- data/shoryuken.gemspec +6 -6
- data/spec/integration/active_job_continuation_spec.rb +145 -0
- data/spec/integration/launcher_spec.rb +2 -3
- data/spec/shared_examples_for_active_job.rb +13 -8
- data/spec/shoryuken/body_parser_spec.rb +1 -2
- data/spec/shoryuken/client_spec.rb +1 -1
- data/spec/shoryuken/default_exception_handler_spec.rb +9 -10
- data/spec/shoryuken/default_worker_registry_spec.rb +1 -2
- data/spec/shoryuken/environment_loader_spec.rb +9 -8
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +2 -1
- data/spec/shoryuken/extensions/active_job_base_spec.rb +2 -1
- data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +2 -1
- data/spec/shoryuken/extensions/active_job_continuation_spec.rb +110 -0
- data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +2 -1
- data/spec/shoryuken/fetcher_spec.rb +23 -26
- data/spec/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
- data/spec/shoryuken/helpers/atomic_counter_spec.rb +177 -0
- data/spec/shoryuken/helpers/atomic_hash_spec.rb +307 -0
- data/spec/shoryuken/helpers/hash_utils_spec.rb +145 -0
- data/spec/shoryuken/helpers/string_utils_spec.rb +124 -0
- data/spec/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/shoryuken/helpers_integration_spec.rb +96 -0
- data/spec/shoryuken/inline_message_spec.rb +196 -0
- data/spec/shoryuken/launcher_spec.rb +23 -2
- data/spec/shoryuken/manager_spec.rb +1 -2
- data/spec/shoryuken/middleware/chain_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/timing_spec.rb +1 -1
- data/spec/shoryuken/options_spec.rb +4 -4
- data/spec/shoryuken/polling/base_strategy_spec.rb +280 -0
- data/spec/shoryuken/polling/queue_configuration_spec.rb +195 -0
- data/spec/shoryuken/polling/strict_priority_spec.rb +1 -1
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +1 -1
- data/spec/shoryuken/processor_spec.rb +1 -1
- data/spec/shoryuken/queue_spec.rb +2 -3
- data/spec/shoryuken/runner_spec.rb +1 -3
- data/spec/shoryuken/util_spec.rb +1 -1
- data/spec/shoryuken/worker/default_executor_spec.rb +1 -1
- data/spec/shoryuken/worker/inline_executor_spec.rb +1 -1
- data/spec/shoryuken/worker_spec.rb +15 -11
- data/spec/shoryuken_spec.rb +1 -1
- data/spec/spec_helper.rb +16 -0
- metadata +72 -29
- data/.github/FUNDING.yml +0 -12
- data/gemfiles/rails_6_1.gemfile +0 -18
- data/gemfiles/rails_7_0.gemfile +0 -19
- data/lib/shoryuken/core_ext.rb +0 -69
- data/lib/shoryuken/polling/base.rb +0 -67
- data/shoryuken.jpg +0 -0
- data/spec/shoryuken/core_ext_spec.rb +0 -40
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'active_job'
|
|
5
|
+
require 'shoryuken/extensions/active_job_adapter'
|
|
6
|
+
require 'shoryuken/extensions/active_job_extensions'
|
|
7
|
+
|
|
8
|
+
RSpec.describe 'ActiveJob Continuations Integration' do
|
|
9
|
+
# Skip all tests in this suite if ActiveJob::Continuable is not available (Rails < 8.0)
|
|
10
|
+
before(:all) do
|
|
11
|
+
skip 'ActiveJob::Continuable not available (Rails < 8.0)' unless defined?(ActiveJob::Continuable)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Test job that uses ActiveJob Continuations
|
|
15
|
+
class ContinuableTestJob < ActiveJob::Base
|
|
16
|
+
include ActiveJob::Continuable if defined?(ActiveJob::Continuable)
|
|
17
|
+
|
|
18
|
+
queue_as :default
|
|
19
|
+
|
|
20
|
+
class_attribute :executions_log, default: []
|
|
21
|
+
class_attribute :checkpoints_reached, default: []
|
|
22
|
+
|
|
23
|
+
def perform(max_iterations: 10)
|
|
24
|
+
self.class.executions_log << { execution: executions, started_at: Time.current }
|
|
25
|
+
|
|
26
|
+
step :initialize_work do
|
|
27
|
+
self.class.checkpoints_reached << "initialize_work_#{executions}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
step :process_items, start: cursor || 0 do
|
|
31
|
+
(cursor..max_iterations).each do |i|
|
|
32
|
+
self.class.checkpoints_reached << "processing_item_#{i}"
|
|
33
|
+
|
|
34
|
+
# Check if we should stop (checkpoint)
|
|
35
|
+
checkpoint
|
|
36
|
+
|
|
37
|
+
# Simulate some work
|
|
38
|
+
sleep 0.01
|
|
39
|
+
|
|
40
|
+
# Advance cursor
|
|
41
|
+
cursor.advance!
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
step :finalize_work do
|
|
46
|
+
self.class.checkpoints_reached << 'finalize_work'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
self.class.executions_log.last[:completed] = true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe 'stopping? method (unit tests)' do
|
|
54
|
+
it 'returns false when launcher is not initialized' do
|
|
55
|
+
adapter = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
56
|
+
expect(adapter.stopping?).to be false
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'returns true when launcher is stopping' do
|
|
60
|
+
launcher = Shoryuken::Launcher.new
|
|
61
|
+
runner = Shoryuken::Runner.instance
|
|
62
|
+
runner.instance_variable_set(:@launcher, launcher)
|
|
63
|
+
|
|
64
|
+
adapter = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
65
|
+
expect(adapter.stopping?).to be false
|
|
66
|
+
|
|
67
|
+
launcher.instance_variable_set(:@stopping, true)
|
|
68
|
+
expect(adapter.stopping?).to be true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe 'timestamp handling for continuation retries' do
|
|
73
|
+
it 'handles past timestamps for continuation retries' do
|
|
74
|
+
adapter = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
75
|
+
job = ContinuableTestJob.new
|
|
76
|
+
job.sqs_send_message_parameters = {}
|
|
77
|
+
|
|
78
|
+
# Mock the queue
|
|
79
|
+
queue = instance_double(Shoryuken::Queue, fifo?: false)
|
|
80
|
+
allow(Shoryuken::Client).to receive(:queues).and_return(queue)
|
|
81
|
+
allow(Shoryuken).to receive(:register_worker)
|
|
82
|
+
allow(queue).to receive(:send_message) do |params|
|
|
83
|
+
# Verify past timestamp results in immediate delivery (delay_seconds <= 0)
|
|
84
|
+
expect(params[:delay_seconds]).to be <= 0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Enqueue with past timestamp (simulating continuation retry)
|
|
88
|
+
past_timestamp = Time.current.to_f - 60
|
|
89
|
+
adapter.enqueue_at(job, past_timestamp)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe 'enqueue_at with continuation timestamps (unit tests)' do
|
|
94
|
+
let(:adapter) { ActiveJob::QueueAdapters::ShoryukenAdapter.new }
|
|
95
|
+
let(:job) do
|
|
96
|
+
job = ContinuableTestJob.new
|
|
97
|
+
job.sqs_send_message_parameters = {}
|
|
98
|
+
job
|
|
99
|
+
end
|
|
100
|
+
let(:queue) { instance_double(Shoryuken::Queue, fifo?: false) }
|
|
101
|
+
|
|
102
|
+
before do
|
|
103
|
+
allow(Shoryuken::Client).to receive(:queues).and_return(queue)
|
|
104
|
+
allow(Shoryuken).to receive(:register_worker)
|
|
105
|
+
@sent_messages = []
|
|
106
|
+
allow(queue).to receive(:send_message) do |params|
|
|
107
|
+
@sent_messages << params
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'accepts past timestamps without error' do
|
|
112
|
+
past_timestamp = Time.current.to_f - 30
|
|
113
|
+
|
|
114
|
+
expect {
|
|
115
|
+
adapter.enqueue_at(job, past_timestamp)
|
|
116
|
+
}.not_to raise_error
|
|
117
|
+
|
|
118
|
+
expect(@sent_messages.size).to eq(1)
|
|
119
|
+
expect(@sent_messages.first[:delay_seconds]).to be <= 0
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'accepts current timestamp' do
|
|
123
|
+
current_timestamp = Time.current.to_f
|
|
124
|
+
|
|
125
|
+
expect {
|
|
126
|
+
adapter.enqueue_at(job, current_timestamp)
|
|
127
|
+
}.not_to raise_error
|
|
128
|
+
|
|
129
|
+
expect(@sent_messages.size).to eq(1)
|
|
130
|
+
expect(@sent_messages.first[:delay_seconds]).to be_between(-1, 1)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'accepts future timestamp' do
|
|
134
|
+
future_timestamp = Time.current.to_f + 30
|
|
135
|
+
|
|
136
|
+
expect {
|
|
137
|
+
adapter.enqueue_at(job, future_timestamp)
|
|
138
|
+
}.not_to raise_error
|
|
139
|
+
|
|
140
|
+
expect(@sent_messages.size).to eq(1)
|
|
141
|
+
expect(@sent_messages.first[:delay_seconds]).to be > 0
|
|
142
|
+
expect(@sent_messages.first[:delay_seconds]).to be <= 30
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -24,7 +24,7 @@ RSpec.shared_examples 'active_job_adapters' do
|
|
|
24
24
|
expect(queue).to receive(:send_message) do |hash|
|
|
25
25
|
expect(hash[:message_deduplication_id]).to_not be
|
|
26
26
|
expect(hash[:message_attributes]['shoryuken_class'][:string_value]).to eq(described_class::JobWrapper.to_s)
|
|
27
|
-
expect(hash[:message_attributes]['shoryuken_class'][:data_type]).to eq(
|
|
27
|
+
expect(hash[:message_attributes]['shoryuken_class'][:data_type]).to eq('String')
|
|
28
28
|
expect(hash[:message_attributes].keys).to eq(['shoryuken_class'])
|
|
29
29
|
end
|
|
30
30
|
expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
|
|
@@ -133,9 +133,9 @@ RSpec.shared_examples 'active_job_adapters' do
|
|
|
133
133
|
|
|
134
134
|
expect(queue).to receive(:send_message) do |hash|
|
|
135
135
|
expect(hash[:message_attributes]['shoryuken_class'][:string_value]).to eq(described_class::JobWrapper.to_s)
|
|
136
|
-
expect(hash[:message_attributes]['shoryuken_class'][:data_type]).to eq(
|
|
136
|
+
expect(hash[:message_attributes]['shoryuken_class'][:data_type]).to eq('String')
|
|
137
137
|
expect(hash[:message_attributes]['tracer_id'][:string_value]).to eq(custom_message_attributes['tracer_id'][:string_value])
|
|
138
|
-
expect(hash[:message_attributes]['tracer_id'][:data_type]).to eq(
|
|
138
|
+
expect(hash[:message_attributes]['tracer_id'][:data_type]).to eq('String')
|
|
139
139
|
end
|
|
140
140
|
expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
|
|
141
141
|
|
|
@@ -157,7 +157,8 @@ RSpec.shared_examples 'active_job_adapters' do
|
|
|
157
157
|
it 'should enqueue a message with the message_attributes specified on the job' do
|
|
158
158
|
expect(queue).to receive(:send_message) do |hash|
|
|
159
159
|
expect(hash[:message_attributes]['tracer_id']).to eq({ data_type: 'String', string_value: 'job-value' })
|
|
160
|
-
expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String',
|
|
160
|
+
expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String',
|
|
161
|
+
string_value: described_class::JobWrapper.to_s })
|
|
161
162
|
end
|
|
162
163
|
subject.enqueue job
|
|
163
164
|
end
|
|
@@ -185,8 +186,10 @@ RSpec.shared_examples 'active_job_adapters' do
|
|
|
185
186
|
|
|
186
187
|
expect(queue).to receive(:send_message) do |hash|
|
|
187
188
|
expect(hash[:message_attributes]['tracer_id']).to be_nil
|
|
188
|
-
expect(hash[:message_attributes]['options_tracer_id']).to eq({ data_type: 'String',
|
|
189
|
-
|
|
189
|
+
expect(hash[:message_attributes]['options_tracer_id']).to eq({ data_type: 'String',
|
|
190
|
+
string_value: 'options-value' })
|
|
191
|
+
expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String',
|
|
192
|
+
string_value: described_class::JobWrapper.to_s })
|
|
190
193
|
end
|
|
191
194
|
subject.enqueue job, message_attributes: custom_message_attributes
|
|
192
195
|
end
|
|
@@ -225,7 +228,8 @@ RSpec.shared_examples 'active_job_adapters' do
|
|
|
225
228
|
|
|
226
229
|
it 'should enqueue a message with the message_system_attributes specified on the job' do
|
|
227
230
|
expect(queue).to receive(:send_message) do |hash|
|
|
228
|
-
expect(hash[:message_system_attributes]['AWSTraceHeader']).to eq({ data_type: 'String',
|
|
231
|
+
expect(hash[:message_system_attributes]['AWSTraceHeader']).to eq({ data_type: 'String',
|
|
232
|
+
string_value: 'job-value' })
|
|
229
233
|
end
|
|
230
234
|
subject.enqueue job
|
|
231
235
|
end
|
|
@@ -253,7 +257,8 @@ RSpec.shared_examples 'active_job_adapters' do
|
|
|
253
257
|
|
|
254
258
|
expect(queue).to receive(:send_message) do |hash|
|
|
255
259
|
expect(hash[:message_system_attributes]['job_trace_header']).to be_nil
|
|
256
|
-
expect(hash[:message_system_attributes]['options_trace_header']).to eq({ data_type: 'String',
|
|
260
|
+
expect(hash[:message_system_attributes]['options_trace_header']).to eq({ data_type: 'String',
|
|
261
|
+
string_value: 'options-value' })
|
|
257
262
|
end
|
|
258
263
|
subject.enqueue job, message_system_attributes: custom_message_attributes
|
|
259
264
|
end
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# rubocop:disable Metrics/BlockLength
|
|
4
3
|
RSpec.describe Shoryuken::DefaultExceptionHandler do
|
|
5
4
|
class CustomErrorHandler
|
|
6
5
|
extend Shoryuken::Util
|
|
7
6
|
|
|
8
7
|
def self.call(_ex, queue, _msg)
|
|
9
|
-
logger.error("#{queue
|
|
8
|
+
logger.error("#{queue} failed to process the message")
|
|
10
9
|
end
|
|
11
10
|
end
|
|
12
11
|
|
|
@@ -38,31 +37,31 @@ RSpec.describe Shoryuken::DefaultExceptionHandler do
|
|
|
38
37
|
|
|
39
38
|
subject { Shoryuken::Processor.new(queue, sqs_msg) }
|
|
40
39
|
|
|
41
|
-
context
|
|
40
|
+
context 'with default handler' do
|
|
42
41
|
before do
|
|
43
42
|
Shoryuken.exception_handlers = described_class
|
|
44
43
|
end
|
|
45
44
|
|
|
46
|
-
it
|
|
45
|
+
it 'logs an error message' do
|
|
47
46
|
expect(Shoryuken::Logging.logger).to receive(:error).twice
|
|
48
47
|
|
|
49
|
-
allow_any_instance_of(TestWorker).to receive(:perform).and_raise(StandardError,
|
|
48
|
+
allow_any_instance_of(TestWorker).to receive(:perform).and_raise(StandardError, 'error')
|
|
50
49
|
allow(sqs_msg).to receive(:body)
|
|
51
50
|
|
|
52
51
|
expect { subject.process }.to raise_error(StandardError)
|
|
53
52
|
end
|
|
54
53
|
end
|
|
55
54
|
|
|
56
|
-
context
|
|
55
|
+
context 'with custom handler' do
|
|
57
56
|
before do
|
|
58
57
|
Shoryuken.exception_handlers = [described_class, CustomErrorHandler]
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
it
|
|
60
|
+
it 'logs default and custom error messages' do
|
|
62
61
|
expect(Shoryuken::Logging.logger).to receive(:error).twice
|
|
63
|
-
expect(Shoryuken::Logging.logger).to receive(:error).with(
|
|
62
|
+
expect(Shoryuken::Logging.logger).to receive(:error).with('default failed to process the message').once
|
|
64
63
|
|
|
65
|
-
allow_any_instance_of(TestWorker).to receive(:perform).and_raise(StandardError,
|
|
64
|
+
allow_any_instance_of(TestWorker).to receive(:perform).and_raise(StandardError, 'error')
|
|
66
65
|
allow(sqs_msg).to receive(:body)
|
|
67
66
|
|
|
68
67
|
expect { subject.process }.to raise_error(StandardError)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'active_job'
|
|
3
4
|
|
|
4
5
|
RSpec.describe Shoryuken::EnvironmentLoader do
|
|
@@ -63,7 +64,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
|
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
specify do
|
|
66
|
-
Shoryuken.options[:queues] = [
|
|
67
|
+
Shoryuken.options[:queues] = %w[queue1 queue2] # default queues
|
|
67
68
|
Shoryuken.options[:groups] = [['custom', { queues: ['queue3'], delay: 25 }]]
|
|
68
69
|
subject.load
|
|
69
70
|
|
|
@@ -124,11 +125,11 @@ RSpec.describe Shoryuken::EnvironmentLoader do
|
|
|
124
125
|
end
|
|
125
126
|
end
|
|
126
127
|
|
|
127
|
-
describe
|
|
128
|
-
let(:cli_queues) { {
|
|
129
|
-
let(:config_queues) { [[
|
|
128
|
+
describe '#setup_options' do
|
|
129
|
+
let(:cli_queues) { { 'queue1' => 10, 'queue2' => 20 } }
|
|
130
|
+
let(:config_queues) { [['queue1', 8], ['queue2', 4]] }
|
|
130
131
|
|
|
131
|
-
context
|
|
132
|
+
context 'when given queues through config and CLI' do
|
|
132
133
|
specify do
|
|
133
134
|
allow_any_instance_of(Shoryuken::EnvironmentLoader).to receive(:config_file_options).and_return({ queues: config_queues })
|
|
134
135
|
Shoryuken::EnvironmentLoader.setup_options(queues: cli_queues)
|
|
@@ -136,7 +137,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
|
|
|
136
137
|
end
|
|
137
138
|
end
|
|
138
139
|
|
|
139
|
-
context
|
|
140
|
+
context 'when given queues through config only' do
|
|
140
141
|
specify do
|
|
141
142
|
allow_any_instance_of(Shoryuken::EnvironmentLoader).to receive(:config_file_options).and_return({ queues: config_queues })
|
|
142
143
|
Shoryuken::EnvironmentLoader.setup_options({})
|
|
@@ -144,7 +145,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
|
|
|
144
145
|
end
|
|
145
146
|
end
|
|
146
147
|
|
|
147
|
-
context
|
|
148
|
+
context 'when given queues through CLI only' do
|
|
148
149
|
specify do
|
|
149
150
|
Shoryuken::EnvironmentLoader.setup_options(queues: cli_queues)
|
|
150
151
|
expect(Shoryuken.options[:queues]).to eq(cli_queues)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_job'
|
|
4
|
+
require 'shared_examples_for_active_job'
|
|
5
|
+
require 'shoryuken/extensions/active_job_adapter'
|
|
6
|
+
require 'shoryuken/extensions/active_job_extensions'
|
|
7
|
+
|
|
8
|
+
RSpec.describe 'ActiveJob Continuation support' do
|
|
9
|
+
let(:adapter) { ActiveJob::QueueAdapters::ShoryukenAdapter.new }
|
|
10
|
+
let(:job) do
|
|
11
|
+
job = TestJob.new
|
|
12
|
+
job.sqs_send_message_parameters = {}
|
|
13
|
+
job
|
|
14
|
+
end
|
|
15
|
+
let(:queue) { double('Queue', fifo?: false) }
|
|
16
|
+
|
|
17
|
+
before do
|
|
18
|
+
allow(Shoryuken::Client).to receive(:queues).with(job.queue_name).and_return(queue)
|
|
19
|
+
allow(Shoryuken).to receive(:register_worker)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe '#stopping?' do
|
|
23
|
+
context 'when Launcher is not initialized' do
|
|
24
|
+
it 'returns false' do
|
|
25
|
+
runner = instance_double(Shoryuken::Runner, launcher: nil)
|
|
26
|
+
allow(Shoryuken::Runner).to receive(:instance).and_return(runner)
|
|
27
|
+
|
|
28
|
+
expect(adapter.stopping?).to be false
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'when Launcher is initialized' do
|
|
33
|
+
let(:runner) { instance_double(Shoryuken::Runner) }
|
|
34
|
+
let(:launcher) { instance_double(Shoryuken::Launcher) }
|
|
35
|
+
|
|
36
|
+
before do
|
|
37
|
+
allow(Shoryuken::Runner).to receive(:instance).and_return(runner)
|
|
38
|
+
allow(runner).to receive(:launcher).and_return(launcher)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'returns false when not stopping' do
|
|
42
|
+
allow(launcher).to receive(:stopping?).and_return(false)
|
|
43
|
+
expect(adapter.stopping?).to be false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns true when stopping' do
|
|
47
|
+
allow(launcher).to receive(:stopping?).and_return(true)
|
|
48
|
+
expect(adapter.stopping?).to be true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe '#enqueue_at with past timestamps' do
|
|
54
|
+
let(:past_timestamp) { Time.current.to_f - 60 } # 60 seconds ago
|
|
55
|
+
|
|
56
|
+
it 'enqueues with negative delay_seconds when timestamp is in the past' do
|
|
57
|
+
expect(queue).to receive(:send_message) do |hash|
|
|
58
|
+
expect(hash[:delay_seconds]).to be <= 0
|
|
59
|
+
expect(hash[:delay_seconds]).to be >= -61 # Allow for rounding and timing
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
adapter.enqueue_at(job, past_timestamp)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'does not raise an error for past timestamps' do
|
|
66
|
+
allow(queue).to receive(:send_message)
|
|
67
|
+
|
|
68
|
+
expect { adapter.enqueue_at(job, past_timestamp) }.not_to raise_error
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe '#enqueue_at with future timestamps' do
|
|
73
|
+
let(:future_timestamp) { Time.current.to_f + 60 } # 60 seconds from now
|
|
74
|
+
|
|
75
|
+
it 'enqueues with delay_seconds when timestamp is in the future' do
|
|
76
|
+
expect(queue).to receive(:send_message) do |hash|
|
|
77
|
+
expect(hash[:delay_seconds]).to be > 0
|
|
78
|
+
expect(hash[:delay_seconds]).to be <= 60
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
adapter.enqueue_at(job, future_timestamp)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe '#enqueue_at with current timestamp' do
|
|
86
|
+
let(:current_timestamp) { Time.current.to_f }
|
|
87
|
+
|
|
88
|
+
it 'enqueues with delay_seconds close to 0' do
|
|
89
|
+
expect(queue).to receive(:send_message) do |hash|
|
|
90
|
+
expect(hash[:delay_seconds]).to be_between(-1, 1) # Allow for timing/rounding
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
adapter.enqueue_at(job, current_timestamp)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe 'retry_on with zero wait' do
|
|
98
|
+
it 'allows immediate retries through continuation mechanism' do
|
|
99
|
+
# Simulate a job with retry_on configuration that uses zero wait
|
|
100
|
+
past_timestamp = Time.current.to_f - 1
|
|
101
|
+
|
|
102
|
+
expect(queue).to receive(:send_message) do |hash|
|
|
103
|
+
# Negative delay for past timestamp - SQS will handle immediate delivery
|
|
104
|
+
expect(hash[:delay_seconds]).to be <= 0
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
adapter.enqueue_at(job, past_timestamp)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
require 'shoryuken/manager'
|
|
3
|
-
require 'shoryuken/fetcher'
|
|
1
|
+
# frozen_string_literal: true
|
|
4
2
|
|
|
5
|
-
# rubocop:disable Metrics/BlockLength
|
|
6
3
|
RSpec.describe Shoryuken::Fetcher do
|
|
7
4
|
let(:queue) { instance_double('Shoryuken::Queue', fifo?: false) }
|
|
8
5
|
let(:queue_name) { 'default' }
|
|
@@ -29,17 +26,17 @@ RSpec.describe Shoryuken::Fetcher do
|
|
|
29
26
|
Shoryuken.sqs_client_receive_message_opts[group] = { wait_time_seconds: 10 }
|
|
30
27
|
|
|
31
28
|
expect(queue).to receive(:receive_messages).with({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
wait_time_seconds: 10,
|
|
30
|
+
max_number_of_messages: limit,
|
|
31
|
+
message_attribute_names: ['All'],
|
|
32
|
+
attribute_names: ['All']
|
|
33
|
+
}).and_return([])
|
|
37
34
|
|
|
38
35
|
subject.fetch(queue_config, limit)
|
|
39
36
|
end
|
|
40
37
|
|
|
41
38
|
it 'logs debug only' do
|
|
42
|
-
# See https://github.com/
|
|
39
|
+
# See https://github.com/ruby-shoryuken/shoryuken/issues/435
|
|
43
40
|
logger = double 'logger'
|
|
44
41
|
|
|
45
42
|
allow(subject).to receive(:logger).and_return(logger)
|
|
@@ -63,10 +60,10 @@ RSpec.describe Shoryuken::Fetcher do
|
|
|
63
60
|
Shoryuken.sqs_client_receive_message_opts[queue_name] = { max_number_of_messages: 1 }
|
|
64
61
|
|
|
65
62
|
expect(queue).to receive(:receive_messages).with({
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
max_number_of_messages: 1,
|
|
64
|
+
message_attribute_names: ['All'],
|
|
65
|
+
attribute_names: ['All']
|
|
66
|
+
}).and_return([])
|
|
70
67
|
|
|
71
68
|
subject.fetch(queue_config, limit)
|
|
72
69
|
end
|
|
@@ -79,10 +76,10 @@ RSpec.describe Shoryuken::Fetcher do
|
|
|
79
76
|
Shoryuken.sqs_client_receive_message_opts[queue_name] = { max_number_of_messages: 20 }
|
|
80
77
|
|
|
81
78
|
expect(queue).to receive(:receive_messages).with({
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
max_number_of_messages: limit,
|
|
80
|
+
message_attribute_names: ['All'],
|
|
81
|
+
attribute_names: ['All']
|
|
82
|
+
}).and_return([])
|
|
86
83
|
|
|
87
84
|
subject.fetch(queue_config, limit)
|
|
88
85
|
end
|
|
@@ -94,8 +91,8 @@ RSpec.describe Shoryuken::Fetcher do
|
|
|
94
91
|
specify do
|
|
95
92
|
allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
|
|
96
93
|
expect(queue).to receive(:receive_messages).with({
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
max_number_of_messages: described_class::FETCH_LIMIT, attribute_names: ['All'], message_attribute_names: ['All']
|
|
95
|
+
}).and_return([])
|
|
99
96
|
|
|
100
97
|
subject.fetch(queue_config, limit)
|
|
101
98
|
end
|
|
@@ -106,26 +103,26 @@ RSpec.describe Shoryuken::Fetcher do
|
|
|
106
103
|
let(:queue) { instance_double('Shoryuken::Queue', fifo?: true, name: queue_name) }
|
|
107
104
|
|
|
108
105
|
it 'polls one message at a time' do
|
|
109
|
-
# see https://github.com/
|
|
106
|
+
# see https://github.com/ruby-shoryuken/shoryuken/pull/530
|
|
110
107
|
|
|
111
108
|
allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
|
|
112
109
|
expect(queue).to receive(:receive_messages).with({
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
max_number_of_messages: 1, attribute_names: ['All'], message_attribute_names: ['All']
|
|
111
|
+
}).and_return([])
|
|
115
112
|
|
|
116
113
|
subject.fetch(queue_config, limit)
|
|
117
114
|
end
|
|
118
115
|
|
|
119
116
|
context 'with batch=true' do
|
|
120
117
|
it 'polls the provided limit' do
|
|
121
|
-
# see https://github.com/
|
|
118
|
+
# see https://github.com/ruby-shoryuken/shoryuken/pull/530
|
|
122
119
|
|
|
123
120
|
allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
|
|
124
121
|
allow(Shoryuken.worker_registry).to receive(:batch_receive_messages?).with(queue.name).and_return(true)
|
|
125
122
|
|
|
126
123
|
expect(queue).to receive(:receive_messages).with({
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
max_number_of_messages: limit, attribute_names: ['All'], message_attribute_names: ['All']
|
|
125
|
+
}).and_return([])
|
|
129
126
|
|
|
130
127
|
subject.fetch(queue_config, limit)
|
|
131
128
|
end
|