shoryuken 5.0.6 → 5.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/specs.yml +62 -0
- data/.reek.yml +5 -0
- data/Appraisals +28 -0
- data/CHANGELOG.md +48 -0
- data/Gemfile +3 -1
- data/README.md +21 -1
- data/Rakefile +15 -1
- data/bin/cli/sqs.rb +50 -5
- data/gemfiles/.gitignore +1 -0
- data/gemfiles/rails_4_2.gemfile +20 -0
- data/gemfiles/rails_5_2.gemfile +21 -0
- data/gemfiles/rails_6_0.gemfile +21 -0
- data/gemfiles/rails_6_1.gemfile +21 -0
- data/lib/shoryuken.rb +1 -0
- data/lib/shoryuken/environment_loader.rb +3 -0
- data/lib/shoryuken/extensions/active_job_adapter.rb +25 -18
- data/lib/shoryuken/extensions/active_job_extensions.rb +38 -0
- data/lib/shoryuken/manager.rb +10 -4
- data/lib/shoryuken/polling/base.rb +2 -0
- data/lib/shoryuken/polling/strict_priority.rb +6 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +11 -0
- data/lib/shoryuken/version.rb +1 -1
- data/shoryuken.gemspec +0 -1
- data/spec/integration/launcher_spec.rb +29 -2
- data/spec/shared_examples_for_active_job.rb +226 -9
- data/spec/shoryuken/environment_loader_spec.rb +22 -2
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +1 -1
- data/spec/shoryuken/extensions/active_job_base_spec.rb +84 -0
- data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +4 -0
- data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +20 -0
- data/spec/shoryuken/manager_spec.rb +24 -0
- data/spec/shoryuken/polling/strict_priority_spec.rb +10 -0
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +10 -0
- data/spec/spec_helper.rb +5 -9
- metadata +16 -19
- data/.travis.yml +0 -30
@@ -0,0 +1,38 @@
|
|
1
|
+
module Shoryuken
|
2
|
+
module ActiveJobExtensions
|
3
|
+
# Adds an accessor for SQS SendMessage parameters on ActiveJob jobs
|
4
|
+
# (instances of ActiveJob::Base). Shoryuken ActiveJob queue adapters use
|
5
|
+
# these parameters when enqueueing jobs; other adapters can ignore them.
|
6
|
+
module SQSSendMessageParametersAccessor
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
attr_accessor :sqs_send_message_parameters
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Initializes SQS SendMessage parameters on instances of ActiveJobe::Base
|
15
|
+
# to the empty hash, and populates it whenever `#enqueue` is called, such
|
16
|
+
# as when using ActiveJob::Base.set.
|
17
|
+
module SQSSendMessageParametersSupport
|
18
|
+
def initialize(*arguments)
|
19
|
+
super(*arguments)
|
20
|
+
self.sqs_send_message_parameters = {}
|
21
|
+
end
|
22
|
+
ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
|
23
|
+
|
24
|
+
def enqueue(options = {})
|
25
|
+
sqs_options = options.extract! :message_attributes,
|
26
|
+
:message_system_attributes,
|
27
|
+
:message_deduplication_id,
|
28
|
+
:message_group_id
|
29
|
+
sqs_send_message_parameters.merge! sqs_options
|
30
|
+
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
ActiveJob::Base.include Shoryuken::ActiveJobExtensions::SQSSendMessageParametersAccessor
|
38
|
+
ActiveJob::Base.prepend Shoryuken::ActiveJobExtensions::SQSSendMessageParametersSupport
|
data/lib/shoryuken/manager.rb
CHANGED
@@ -57,8 +57,13 @@ module Shoryuken
|
|
57
57
|
@max_processors - busy
|
58
58
|
end
|
59
59
|
|
60
|
-
def processor_done
|
60
|
+
def processor_done(queue)
|
61
61
|
@busy_processors.decrement
|
62
|
+
client_queue = Shoryuken::Client.queues(queue)
|
63
|
+
return unless client_queue.fifo?
|
64
|
+
return unless @polling_strategy.respond_to?(:message_processed)
|
65
|
+
|
66
|
+
@polling_strategy.message_processed(queue)
|
62
67
|
end
|
63
68
|
|
64
69
|
def assign(queue_name, sqs_msg)
|
@@ -68,9 +73,10 @@ module Shoryuken
|
|
68
73
|
|
69
74
|
@busy_processors.increment
|
70
75
|
|
71
|
-
Concurrent::Promise
|
72
|
-
executor: @executor
|
73
|
-
|
76
|
+
Concurrent::Promise
|
77
|
+
.execute(executor: @executor) { Processor.process(queue_name, sqs_msg) }
|
78
|
+
.then { processor_done(queue_name) }
|
79
|
+
.rescue { processor_done(queue_name) }
|
74
80
|
end
|
75
81
|
|
76
82
|
def dispatch_batch(queue)
|
@@ -38,6 +38,11 @@ module Shoryuken
|
|
38
38
|
.reverse
|
39
39
|
end
|
40
40
|
|
41
|
+
def message_processed(queue)
|
42
|
+
logger.debug "Unpausing #{queue}"
|
43
|
+
@paused_until[queue] = Time.now
|
44
|
+
end
|
45
|
+
|
41
46
|
private
|
42
47
|
|
43
48
|
def next_active_queue
|
@@ -70,6 +75,7 @@ module Shoryuken
|
|
70
75
|
|
71
76
|
def pause(queue)
|
72
77
|
return unless delay > 0
|
78
|
+
|
73
79
|
@paused_until[queue] = Time.now + delay
|
74
80
|
logger.debug "Paused #{queue}"
|
75
81
|
end
|
@@ -35,10 +35,20 @@ module Shoryuken
|
|
35
35
|
unparse_queues(@queues)
|
36
36
|
end
|
37
37
|
|
38
|
+
def message_processed(queue)
|
39
|
+
return if @paused_queues.empty?
|
40
|
+
|
41
|
+
logger.debug "Unpausing #{queue}"
|
42
|
+
@paused_queues.reject! { |_time, name| name == queue }
|
43
|
+
@queues << queue
|
44
|
+
@queues.uniq!
|
45
|
+
end
|
46
|
+
|
38
47
|
private
|
39
48
|
|
40
49
|
def pause(queue)
|
41
50
|
return unless @queues.delete(queue)
|
51
|
+
|
42
52
|
@paused_queues << [Time.now + delay, queue]
|
43
53
|
logger.debug "Paused #{queue}"
|
44
54
|
end
|
@@ -46,6 +56,7 @@ module Shoryuken
|
|
46
56
|
def unpause_queues
|
47
57
|
return if @paused_queues.empty?
|
48
58
|
return if Time.now < @paused_queues.first[0]
|
59
|
+
|
49
60
|
pause = @paused_queues.shift
|
50
61
|
@queues << pause[1]
|
51
62
|
logger.debug "Unpaused #{pause[1]}"
|
data/lib/shoryuken/version.rb
CHANGED
data/shoryuken.gemspec
CHANGED
@@ -4,10 +4,37 @@ require 'shoryuken/launcher'
|
|
4
4
|
require 'securerandom'
|
5
5
|
|
6
6
|
RSpec.describe Shoryuken::Launcher do
|
7
|
-
|
7
|
+
let(:sqs_client) do
|
8
|
+
Aws::SQS::Client.new(
|
9
|
+
region: 'us-east-1',
|
10
|
+
endpoint: 'http://localhost:5000',
|
11
|
+
access_key_id: 'fake',
|
12
|
+
secret_access_key: 'fake'
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:executor) do
|
17
|
+
# We can't use Concurrent.global_io_executor in these tests since once you
|
18
|
+
# shut down a thread pool, you can't start it back up. Instead, we create
|
19
|
+
# one new thread pool executor for each spec. We use a new
|
20
|
+
# CachedThreadPool, since that most closely resembles
|
21
|
+
# Concurrent.global_io_executor
|
22
|
+
Concurrent::CachedThreadPool.new auto_terminate: true
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'Consuming messages' do
|
8
26
|
before do
|
9
27
|
Aws.config[:stub_responses] = false
|
10
|
-
|
28
|
+
|
29
|
+
allow(Shoryuken).to receive(:launcher_executor).and_return(executor)
|
30
|
+
|
31
|
+
Shoryuken.configure_client do |config|
|
32
|
+
config.sqs_client = sqs_client
|
33
|
+
end
|
34
|
+
|
35
|
+
Shoryuken.configure_server do |config|
|
36
|
+
config.sqs_client = sqs_client
|
37
|
+
end
|
11
38
|
|
12
39
|
StandardWorker.received_messages = 0
|
13
40
|
|
@@ -1,30 +1,44 @@
|
|
1
|
+
require 'active_job'
|
2
|
+
require 'shoryuken/extensions/active_job_extensions'
|
3
|
+
|
4
|
+
# Stand-in for a job class specified by the user
|
5
|
+
class TestJob < ActiveJob::Base; end
|
6
|
+
|
1
7
|
# rubocop:disable Metrics/BlockLength
|
2
8
|
RSpec.shared_examples 'active_job_adapters' do
|
3
|
-
let(:
|
9
|
+
let(:job_sqs_send_message_parameters) { {} }
|
10
|
+
let(:job) do
|
11
|
+
job = TestJob.new
|
12
|
+
job.sqs_send_message_parameters = job_sqs_send_message_parameters
|
13
|
+
job
|
14
|
+
end
|
4
15
|
let(:fifo) { false }
|
5
16
|
let(:queue) { double 'Queue', fifo?: fifo }
|
6
17
|
|
7
18
|
before do
|
8
19
|
allow(Shoryuken::Client).to receive(:queues).with(job.queue_name).and_return(queue)
|
9
|
-
allow(job).to receive(:serialize).and_return(
|
10
|
-
'job_class' => 'Worker',
|
11
|
-
'job_id' => job.id,
|
12
|
-
'queue_name' => job.queue_name,
|
13
|
-
'arguments' => nil,
|
14
|
-
'locale' => nil
|
15
|
-
)
|
16
20
|
end
|
17
21
|
|
18
22
|
describe '#enqueue' do
|
19
23
|
specify do
|
20
24
|
expect(queue).to receive(:send_message) do |hash|
|
21
25
|
expect(hash[:message_deduplication_id]).to_not be
|
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("String")
|
28
|
+
expect(hash[:message_attributes].keys).to eq(['shoryuken_class'])
|
22
29
|
end
|
23
30
|
expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
|
24
31
|
|
25
32
|
subject.enqueue(job)
|
26
33
|
end
|
27
34
|
|
35
|
+
it "should mutate the job's sqs_send_message_parameters reference to match those sent to the queue" do
|
36
|
+
expect(queue).to receive(:send_message) do |options|
|
37
|
+
expect(options).to be(job.sqs_send_message_parameters)
|
38
|
+
end
|
39
|
+
subject.enqueue(job)
|
40
|
+
end
|
41
|
+
|
28
42
|
context 'when fifo' do
|
29
43
|
let(:fifo) { true }
|
30
44
|
|
@@ -38,6 +52,209 @@ RSpec.shared_examples 'active_job_adapters' do
|
|
38
52
|
|
39
53
|
subject.enqueue(job)
|
40
54
|
end
|
55
|
+
|
56
|
+
context 'with message_deduplication_id' do
|
57
|
+
context 'when message_deduplication_id is specified in options' do
|
58
|
+
it 'should enqueue a message with the deduplication_id specified in options' do
|
59
|
+
expect(queue).to receive(:send_message) do |hash|
|
60
|
+
expect(hash[:message_deduplication_id]).to eq('options-dedupe-id')
|
61
|
+
end
|
62
|
+
subject.enqueue(job, message_deduplication_id: 'options-dedupe-id')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when message_deduplication_id is specified on the job' do
|
67
|
+
let(:job_sqs_send_message_parameters) { { message_deduplication_id: 'job-dedupe-id' } }
|
68
|
+
|
69
|
+
it 'should enqueue a message with the deduplication_id specified on the job' do
|
70
|
+
expect(queue).to receive(:send_message) do |hash|
|
71
|
+
expect(hash[:message_deduplication_id]).to eq('job-dedupe-id')
|
72
|
+
end
|
73
|
+
subject.enqueue job
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when message_deduplication_id is specified on the job and also in options' do
|
78
|
+
let(:job_sqs_send_message_parameters) { { message_deduplication_id: 'job-dedupe-id' } }
|
79
|
+
|
80
|
+
it 'should enqueue a message with the deduplication_id specified in options' do
|
81
|
+
expect(queue).to receive(:send_message) do |hash|
|
82
|
+
expect(hash[:message_deduplication_id]).to eq('options-dedupe-id')
|
83
|
+
end
|
84
|
+
subject.enqueue(job, message_deduplication_id: 'options-dedupe-id')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'with message_group_id' do
|
91
|
+
context 'when message_group_id is specified in options' do
|
92
|
+
it 'should enqueue a message with the group_id specified in options' do
|
93
|
+
expect(queue).to receive(:send_message) do |hash|
|
94
|
+
expect(hash[:message_group_id]).to eq('options-group-id')
|
95
|
+
end
|
96
|
+
subject.enqueue(job, message_group_id: 'options-group-id')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when message_group_id is specified on the job' do
|
101
|
+
let(:job_sqs_send_message_parameters) { { message_group_id: 'job-group-id' } }
|
102
|
+
|
103
|
+
it 'should enqueue a message with the group_id specified on the job' do
|
104
|
+
expect(queue).to receive(:send_message) do |hash|
|
105
|
+
expect(hash[:message_group_id]).to eq('job-group-id')
|
106
|
+
end
|
107
|
+
subject.enqueue job
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'when message_group_id is specified on the job and also in options' do
|
112
|
+
let(:job_sqs_send_message_parameters) { { message_group_id: 'job-group-id' } }
|
113
|
+
|
114
|
+
it 'should enqueue a message with the group_id specified in options' do
|
115
|
+
expect(queue).to receive(:send_message) do |hash|
|
116
|
+
expect(hash[:message_group_id]).to eq('options-group-id')
|
117
|
+
end
|
118
|
+
subject.enqueue(job, message_group_id: 'options-group-id')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'with additional message attributes' do
|
124
|
+
it 'should combine with activejob attributes' do
|
125
|
+
custom_message_attributes = {
|
126
|
+
'tracer_id' => {
|
127
|
+
string_value: SecureRandom.hex,
|
128
|
+
data_type: 'String'
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
expect(queue).to receive(:send_message) do |hash|
|
133
|
+
expect(hash[:message_attributes]['shoryuken_class'][:string_value]).to eq(described_class::JobWrapper.to_s)
|
134
|
+
expect(hash[:message_attributes]['shoryuken_class'][:data_type]).to eq("String")
|
135
|
+
expect(hash[:message_attributes]['tracer_id'][:string_value]).to eq(custom_message_attributes['tracer_id'][:string_value])
|
136
|
+
expect(hash[:message_attributes]['tracer_id'][:data_type]).to eq("String")
|
137
|
+
end
|
138
|
+
expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
|
139
|
+
|
140
|
+
subject.enqueue(job, message_attributes: custom_message_attributes)
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when message_attributes are specified on the job' do
|
144
|
+
let(:job_sqs_send_message_parameters) do
|
145
|
+
{
|
146
|
+
message_attributes: {
|
147
|
+
'tracer_id' => {
|
148
|
+
data_type: 'String',
|
149
|
+
string_value: 'job-value'
|
150
|
+
}
|
151
|
+
}
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should enqueue a message with the message_attributes specified on the job' do
|
156
|
+
expect(queue).to receive(:send_message) do |hash|
|
157
|
+
expect(hash[:message_attributes]['tracer_id']).to eq({ data_type: 'String', string_value: 'job-value' })
|
158
|
+
expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String', string_value: described_class::JobWrapper.to_s })
|
159
|
+
end
|
160
|
+
subject.enqueue job
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'when message_attributes are specified on the job and also in options' do
|
165
|
+
let(:job_sqs_send_message_parameters) do
|
166
|
+
{
|
167
|
+
message_attributes: {
|
168
|
+
'tracer_id' => {
|
169
|
+
data_type: 'String',
|
170
|
+
string_value: 'job-value'
|
171
|
+
}
|
172
|
+
}
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should enqueue a message with the message_attributes speficied in options' do
|
177
|
+
custom_message_attributes = {
|
178
|
+
'options_tracer_id' => {
|
179
|
+
string_value: 'options-value',
|
180
|
+
data_type: 'String'
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
expect(queue).to receive(:send_message) do |hash|
|
185
|
+
expect(hash[:message_attributes]['tracer_id']).to be_nil
|
186
|
+
expect(hash[:message_attributes]['options_tracer_id']).to eq({ data_type: 'String', string_value: 'options-value' })
|
187
|
+
expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String', string_value: described_class::JobWrapper.to_s })
|
188
|
+
end
|
189
|
+
subject.enqueue job, message_attributes: custom_message_attributes
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'with message_system_attributes' do
|
196
|
+
context 'when message_system_attributes are specified in options' do
|
197
|
+
it 'should enqueue a message with message_system_attributes specified in options' do
|
198
|
+
system_attributes = {
|
199
|
+
'AWSTraceHeader' => {
|
200
|
+
string_value: 'trace_id',
|
201
|
+
data_type: 'String'
|
202
|
+
}
|
203
|
+
}
|
204
|
+
expect(queue).to receive(:send_message) do |hash|
|
205
|
+
expect(hash[:message_system_attributes]['AWSTraceHeader'][:string_value]).to eq('trace_id')
|
206
|
+
expect(hash[:message_system_attributes]['AWSTraceHeader'][:data_type]).to eq('String')
|
207
|
+
end
|
208
|
+
subject.enqueue(job, message_system_attributes: system_attributes)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'when message_system_attributes are specified on the job' do
|
213
|
+
let(:job_sqs_send_message_parameters) do
|
214
|
+
{
|
215
|
+
message_system_attributes: {
|
216
|
+
'AWSTraceHeader' => {
|
217
|
+
string_value: 'job-value',
|
218
|
+
data_type: 'String'
|
219
|
+
}
|
220
|
+
}
|
221
|
+
}
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'should enqueue a message with the message_system_attributes specified on the job' do
|
225
|
+
expect(queue).to receive(:send_message) do |hash|
|
226
|
+
expect(hash[:message_system_attributes]['AWSTraceHeader']).to eq({ data_type: 'String', string_value: 'job-value' })
|
227
|
+
end
|
228
|
+
subject.enqueue job
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context 'when message_system_attributes are specified on the job and also in options' do
|
233
|
+
let(:job_sqs_send_message_parameters) do
|
234
|
+
{
|
235
|
+
message_system_attributes: {
|
236
|
+
'job_trace_header' => {
|
237
|
+
string_value: 'job-value',
|
238
|
+
data_type: 'String'
|
239
|
+
}
|
240
|
+
}
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'should enqueue a message with the message_system_attributes speficied in options' do
|
245
|
+
custom_message_attributes = {
|
246
|
+
'options_trace_header' => {
|
247
|
+
string_value: 'options-value',
|
248
|
+
data_type: 'String'
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
expect(queue).to receive(:send_message) do |hash|
|
253
|
+
expect(hash[:message_system_attributes]['job_trace_header']).to be_nil
|
254
|
+
expect(hash[:message_system_attributes]['options_trace_header']).to eq({ data_type: 'String', string_value: 'options-value' })
|
255
|
+
end
|
256
|
+
subject.enqueue job, message_system_attributes: custom_message_attributes
|
257
|
+
end
|
41
258
|
end
|
42
259
|
end
|
43
260
|
|
@@ -59,4 +276,4 @@ RSpec.shared_examples 'active_job_adapters' do
|
|
59
276
|
end
|
60
277
|
end
|
61
278
|
end
|
62
|
-
# rubocop:enable Metrics/BlockLength
|
279
|
+
# rubocop:enable Metrics/BlockLength
|