shoryuken 2.1.1 → 3.1.3
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/.codeclimate.yml +5 -8
- data/.rubocop.yml +8 -2
- data/.travis.yml +6 -0
- data/CHANGELOG.md +167 -0
- data/Gemfile +2 -0
- data/README.md +16 -155
- data/Rakefile +0 -1
- data/bin/cli/base.rb +40 -0
- data/bin/cli/sqs.rb +204 -0
- data/bin/shoryuken +55 -9
- data/examples/default_worker.rb +1 -1
- data/lib/shoryuken/client.rb +3 -15
- data/lib/shoryuken/default_worker_registry.rb +10 -6
- data/lib/shoryuken/environment_loader.rb +60 -57
- data/lib/shoryuken/fetcher.rb +21 -51
- data/lib/shoryuken/launcher.rb +77 -23
- data/lib/shoryuken/logging.rb +0 -5
- data/lib/shoryuken/manager.rb +54 -205
- data/lib/shoryuken/message.rb +4 -13
- data/lib/shoryuken/middleware/chain.rb +1 -18
- data/lib/shoryuken/middleware/server/auto_delete.rb +3 -8
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +16 -17
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +37 -22
- data/lib/shoryuken/middleware/server/timing.rb +2 -2
- data/lib/shoryuken/options.rb +196 -0
- data/lib/shoryuken/polling/base.rb +67 -0
- data/lib/shoryuken/polling/strict_priority.rb +77 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +66 -0
- data/lib/shoryuken/processor.rb +27 -25
- data/lib/shoryuken/queue.rb +28 -9
- data/lib/shoryuken/runner.rb +131 -0
- data/lib/shoryuken/util.rb +1 -9
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker.rb +9 -1
- data/lib/shoryuken.rb +47 -157
- data/shoryuken.gemspec +7 -7
- data/spec/integration/launcher_spec.rb +15 -8
- data/spec/shoryuken/client_spec.rb +2 -45
- data/spec/shoryuken/default_worker_registry_spec.rb +12 -10
- data/spec/shoryuken/environment_loader_spec.rb +54 -0
- data/spec/shoryuken/fetcher_spec.rb +27 -46
- data/spec/shoryuken/manager_spec.rb +80 -93
- data/spec/shoryuken/middleware/chain_spec.rb +0 -24
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +2 -2
- data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +7 -3
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +56 -33
- data/spec/shoryuken/middleware/server/timing_spec.rb +5 -3
- data/spec/shoryuken/options_spec.rb +100 -0
- data/spec/shoryuken/polling/strict_priority_spec.rb +140 -0
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +99 -0
- data/spec/shoryuken/processor_spec.rb +20 -37
- data/spec/shoryuken/queue_spec.rb +72 -26
- data/spec/shoryuken/{cli_spec.rb → runner_spec.rb} +11 -26
- data/spec/shoryuken_spec.rb +1 -48
- data/spec/spec_helper.rb +14 -20
- data/test_workers/endless_interruptive_worker.rb +41 -0
- data/test_workers/endless_uninterruptive_worker.rb +44 -0
- metadata +39 -34
- data/lib/shoryuken/aws_config.rb +0 -64
- data/lib/shoryuken/cli.rb +0 -215
- data/lib/shoryuken/sns_arn.rb +0 -27
- data/lib/shoryuken/topic.rb +0 -17
- data/spec/shoryuken/sns_arn_spec.rb +0 -42
- data/spec/shoryuken/topic_spec.rb +0 -32
- data/spec/shoryuken_endpoint.yml +0 -6
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Shoryuken::Polling::WeightedRoundRobin do
|
|
4
|
+
let(:queue1) { 'shoryuken' }
|
|
5
|
+
let(:queue2) { 'uppercut' }
|
|
6
|
+
let(:queues) { Array.new }
|
|
7
|
+
subject { Shoryuken::Polling::WeightedRoundRobin.new(queues) }
|
|
8
|
+
|
|
9
|
+
describe '#next_queue' do
|
|
10
|
+
it 'cycles' do
|
|
11
|
+
# [shoryuken, 2]
|
|
12
|
+
# [uppercut, 1]
|
|
13
|
+
queues << queue1
|
|
14
|
+
queues << queue1
|
|
15
|
+
queues << queue2
|
|
16
|
+
|
|
17
|
+
expect(subject.next_queue).to eq(queue1)
|
|
18
|
+
expect(subject.next_queue).to eq(queue2)
|
|
19
|
+
expect(subject.next_queue).to eq(queue1)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'returns nil if there are no active queues' do
|
|
23
|
+
expect(subject.next_queue).to eq(nil)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'unpauses queues whose pause is expired' do
|
|
27
|
+
# [shoryuken, 2]
|
|
28
|
+
# [uppercut, 1]
|
|
29
|
+
queues << queue1
|
|
30
|
+
queues << queue1
|
|
31
|
+
queues << queue2
|
|
32
|
+
|
|
33
|
+
allow(subject).to receive(:delay).and_return(10)
|
|
34
|
+
|
|
35
|
+
now = Time.now
|
|
36
|
+
allow(Time).to receive(:now).and_return(now)
|
|
37
|
+
|
|
38
|
+
# pause the first queue
|
|
39
|
+
subject.messages_found(queue1, 0)
|
|
40
|
+
expect(subject.next_queue).to eq(queue2)
|
|
41
|
+
|
|
42
|
+
now += 5
|
|
43
|
+
allow(Time).to receive(:now).and_return(now)
|
|
44
|
+
|
|
45
|
+
# pause the second queue
|
|
46
|
+
subject.messages_found(queue2, 0)
|
|
47
|
+
expect(subject.next_queue).to eq(nil)
|
|
48
|
+
|
|
49
|
+
# queue1 should be unpaused now
|
|
50
|
+
now += 6
|
|
51
|
+
allow(Time).to receive(:now).and_return(now)
|
|
52
|
+
expect(subject.next_queue).to eq(queue1)
|
|
53
|
+
|
|
54
|
+
# queue1 should be unpaused and added to the end of queues now
|
|
55
|
+
now += 6
|
|
56
|
+
allow(Time).to receive(:now).and_return(now)
|
|
57
|
+
expect(subject.next_queue).to eq(queue1)
|
|
58
|
+
expect(subject.next_queue).to eq(queue2)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe '#messages_found' do
|
|
63
|
+
it 'pauses a queue if there are no messages found' do
|
|
64
|
+
# [shoryuken, 2]
|
|
65
|
+
# [uppercut, 1]
|
|
66
|
+
queues << queue1
|
|
67
|
+
queues << queue1
|
|
68
|
+
queues << queue2
|
|
69
|
+
|
|
70
|
+
expect(subject).to receive(:pause).with(queue1).and_call_original
|
|
71
|
+
subject.messages_found(queue1, 0)
|
|
72
|
+
expect(subject.instance_variable_get(:@queues)).to eq([queue2])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'increased the weight if message is found' do
|
|
76
|
+
# [shoryuken, 2]
|
|
77
|
+
# [uppercut, 1]
|
|
78
|
+
queues << queue1
|
|
79
|
+
queues << queue1
|
|
80
|
+
queues << queue2
|
|
81
|
+
|
|
82
|
+
expect(subject.instance_variable_get(:@queues)).to eq([queue1, queue2])
|
|
83
|
+
subject.messages_found(queue1, 1)
|
|
84
|
+
expect(subject.instance_variable_get(:@queues)).to eq([queue1, queue2, queue1])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'respects the maximum queue weight' do
|
|
88
|
+
# [shoryuken, 2]
|
|
89
|
+
# [uppercut, 1]
|
|
90
|
+
queues << queue1
|
|
91
|
+
queues << queue1
|
|
92
|
+
queues << queue2
|
|
93
|
+
|
|
94
|
+
subject.messages_found(queue1, 1)
|
|
95
|
+
subject.messages_found(queue1, 1)
|
|
96
|
+
expect(subject.instance_variable_get(:@queues)).to eq([queue1, queue2, queue1])
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -3,7 +3,7 @@ require 'shoryuken/processor'
|
|
|
3
3
|
require 'shoryuken/manager'
|
|
4
4
|
|
|
5
5
|
RSpec.describe Shoryuken::Processor do
|
|
6
|
-
let(:manager) { double Shoryuken::Manager
|
|
6
|
+
let(:manager) { double Shoryuken::Manager }
|
|
7
7
|
let(:sqs_queue) { double Shoryuken::Queue, visibility_timeout: 30 }
|
|
8
8
|
let(:queue) { 'default' }
|
|
9
9
|
|
|
@@ -16,14 +16,14 @@ RSpec.describe Shoryuken::Processor do
|
|
|
16
16
|
receipt_handle: SecureRandom.uuid
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
subject { described_class.new(manager) }
|
|
20
|
-
|
|
21
19
|
before do
|
|
22
20
|
allow(manager).to receive(:async).and_return(manager)
|
|
23
21
|
allow(manager).to receive(:real_thread)
|
|
24
22
|
allow(Shoryuken::Client).to receive(:queues).with(queue).and_return(sqs_queue)
|
|
25
23
|
end
|
|
26
24
|
|
|
25
|
+
subject { described_class.new(queue, sqs_msg) }
|
|
26
|
+
|
|
27
27
|
describe '#process' do
|
|
28
28
|
it 'parses the body into JSON' do
|
|
29
29
|
TestWorker.get_shoryuken_options['body_parser'] = :json
|
|
@@ -34,7 +34,7 @@ RSpec.describe Shoryuken::Processor do
|
|
|
34
34
|
|
|
35
35
|
allow(sqs_msg).to receive(:body).and_return(JSON.dump(body))
|
|
36
36
|
|
|
37
|
-
subject.process
|
|
37
|
+
subject.process
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
it 'parses the body calling the proc' do
|
|
@@ -44,7 +44,7 @@ RSpec.describe Shoryuken::Processor do
|
|
|
44
44
|
|
|
45
45
|
allow(sqs_msg).to receive(:body).and_return('test')
|
|
46
46
|
|
|
47
|
-
subject.process
|
|
47
|
+
subject.process
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
it 'parses the body as text' do
|
|
@@ -56,7 +56,7 @@ RSpec.describe Shoryuken::Processor do
|
|
|
56
56
|
|
|
57
57
|
allow(sqs_msg).to receive(:body).and_return(body)
|
|
58
58
|
|
|
59
|
-
subject.process
|
|
59
|
+
subject.process
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
it 'parses calling `.load`' do
|
|
@@ -72,7 +72,7 @@ RSpec.describe Shoryuken::Processor do
|
|
|
72
72
|
|
|
73
73
|
allow(sqs_msg).to receive(:body).and_return(JSON.dump(body))
|
|
74
74
|
|
|
75
|
-
subject.process
|
|
75
|
+
subject.process
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
it 'parses calling `.parse`' do
|
|
@@ -88,28 +88,21 @@ RSpec.describe Shoryuken::Processor do
|
|
|
88
88
|
|
|
89
89
|
allow(sqs_msg).to receive(:body).and_return(JSON.dump(body))
|
|
90
90
|
|
|
91
|
-
subject.process
|
|
91
|
+
subject.process
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
context 'when parse errors' do
|
|
95
95
|
before do
|
|
96
96
|
TestWorker.get_shoryuken_options['body_parser'] = :json
|
|
97
97
|
|
|
98
|
-
allow(sqs_msg).to receive(:body).and_return('invalid
|
|
98
|
+
allow(sqs_msg).to receive(:body).and_return('invalid JSON')
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
expect(subject.logger).to receive(:error)
|
|
103
|
-
expect(block.call).
|
|
104
|
-
to include("unexpected token at 'invalid json'\nbody_parser: json\nsqs_msg.body: invalid json")
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
subject.process(queue, sqs_msg) rescue nil
|
|
108
|
-
end
|
|
101
|
+
specify do
|
|
102
|
+
expect(subject.logger).to receive(:error).twice
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
to raise_error(JSON::ParserError, /unexpected token at 'invalid json'/)
|
|
104
|
+
expect { subject.process }.
|
|
105
|
+
to raise_error(JSON::ParserError, /unexpected token at 'invalid JSON'/)
|
|
113
106
|
end
|
|
114
107
|
end
|
|
115
108
|
|
|
@@ -123,7 +116,7 @@ RSpec.describe Shoryuken::Processor do
|
|
|
123
116
|
|
|
124
117
|
allow(sqs_msg).to receive(:body).and_return(body)
|
|
125
118
|
|
|
126
|
-
subject.process
|
|
119
|
+
subject.process
|
|
127
120
|
end
|
|
128
121
|
end
|
|
129
122
|
|
|
@@ -150,7 +143,7 @@ RSpec.describe Shoryuken::Processor do
|
|
|
150
143
|
|
|
151
144
|
context 'server' do
|
|
152
145
|
before do
|
|
153
|
-
allow(Shoryuken).to receive(:server?).and_return(true)
|
|
146
|
+
allow(Shoryuken::Options).to receive(:server?).and_return(true)
|
|
154
147
|
WorkerCalledMiddlewareWorker.instance_variable_set(:@server_chain, nil) # un-memoize middleware
|
|
155
148
|
|
|
156
149
|
Shoryuken.configure_server do |config|
|
|
@@ -169,12 +162,10 @@ RSpec.describe Shoryuken::Processor do
|
|
|
169
162
|
end
|
|
170
163
|
|
|
171
164
|
it 'invokes middleware' do
|
|
172
|
-
expect(manager).to receive(:processor_done).with(queue, subject)
|
|
173
|
-
|
|
174
165
|
expect_any_instance_of(WorkerCalledMiddlewareWorker).to receive(:perform).with(sqs_msg, sqs_msg.body)
|
|
175
166
|
expect_any_instance_of(WorkerCalledMiddlewareWorker).to receive(:called).with(sqs_msg, queue)
|
|
176
167
|
|
|
177
|
-
subject.process
|
|
168
|
+
subject.process
|
|
178
169
|
end
|
|
179
170
|
end
|
|
180
171
|
|
|
@@ -199,12 +190,10 @@ RSpec.describe Shoryuken::Processor do
|
|
|
199
190
|
end
|
|
200
191
|
|
|
201
192
|
it "doesn't invoke middleware" do
|
|
202
|
-
expect(manager).to receive(:processor_done).with(queue, subject)
|
|
203
|
-
|
|
204
193
|
expect_any_instance_of(WorkerCalledMiddlewareWorker).to receive(:perform).with(sqs_msg, sqs_msg.body)
|
|
205
194
|
expect_any_instance_of(WorkerCalledMiddlewareWorker).to_not receive(:called).with(sqs_msg, queue)
|
|
206
195
|
|
|
207
|
-
subject.process
|
|
196
|
+
subject.process
|
|
208
197
|
end
|
|
209
198
|
end
|
|
210
199
|
end
|
|
@@ -212,25 +201,21 @@ RSpec.describe Shoryuken::Processor do
|
|
|
212
201
|
it 'performs with delete' do
|
|
213
202
|
TestWorker.get_shoryuken_options['auto_delete'] = true
|
|
214
203
|
|
|
215
|
-
expect(manager).to receive(:processor_done).with(queue, subject)
|
|
216
|
-
|
|
217
204
|
expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, sqs_msg.body)
|
|
218
205
|
|
|
219
206
|
expect(sqs_queue).to receive(:delete_messages).with(entries: [{ id: '0', receipt_handle: sqs_msg.receipt_handle }])
|
|
220
207
|
|
|
221
|
-
subject.process
|
|
208
|
+
subject.process
|
|
222
209
|
end
|
|
223
210
|
|
|
224
211
|
it 'performs without delete' do
|
|
225
212
|
TestWorker.get_shoryuken_options['auto_delete'] = false
|
|
226
213
|
|
|
227
|
-
expect(manager).to receive(:processor_done).with(queue, subject)
|
|
228
|
-
|
|
229
214
|
expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, sqs_msg.body)
|
|
230
215
|
|
|
231
216
|
expect(sqs_queue).to_not receive(:delete_messages)
|
|
232
217
|
|
|
233
|
-
subject.process
|
|
218
|
+
subject.process
|
|
234
219
|
end
|
|
235
220
|
|
|
236
221
|
context 'when shoryuken_class header' do
|
|
@@ -249,13 +234,11 @@ RSpec.describe Shoryuken::Processor do
|
|
|
249
234
|
it 'performs without delete' do
|
|
250
235
|
Shoryuken.worker_registry.clear # unregister TestWorker
|
|
251
236
|
|
|
252
|
-
expect(manager).to receive(:processor_done).with(queue, subject)
|
|
253
|
-
|
|
254
237
|
expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, sqs_msg.body)
|
|
255
238
|
|
|
256
239
|
expect(sqs_queue).to_not receive(:delete_messages)
|
|
257
240
|
|
|
258
|
-
subject.process
|
|
241
|
+
subject.process
|
|
259
242
|
end
|
|
260
243
|
end
|
|
261
244
|
end
|
|
@@ -1,22 +1,68 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
describe Shoryuken::Queue do
|
|
3
|
+
RSpec.describe Shoryuken::Queue do
|
|
4
4
|
let(:credentials) { Aws::Credentials.new('access_key_id', 'secret_access_key') }
|
|
5
5
|
let(:sqs) { Aws::SQS::Client.new(stub_responses: true, credentials: credentials) }
|
|
6
6
|
let(:queue_name) { 'shoryuken' }
|
|
7
|
-
let(:queue_url) {
|
|
7
|
+
let(:queue_url) { "https://sqs.eu-west-1.amazonaws.com:6059/0123456789/#{queue_name}" }
|
|
8
8
|
|
|
9
9
|
subject { described_class.new(sqs, queue_name) }
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
before do
|
|
11
12
|
# Required as Aws::SQS::Client.get_queue_url returns 'String' when responses are stubbed,
|
|
12
13
|
# which is not accepted by Aws::SQS::Client.get_queue_attributes for :queue_name parameter.
|
|
13
14
|
allow(subject).to receive(:url).and_return(queue_url)
|
|
14
|
-
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#new' do
|
|
18
|
+
context 'when queue url supplied' do
|
|
19
|
+
subject { described_class.new(sqs, queue_url) }
|
|
20
|
+
|
|
21
|
+
specify do
|
|
22
|
+
expect(subject.name).to eq(queue_name)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'when queue name supplied' do
|
|
27
|
+
subject { described_class.new(sqs, queue_name) }
|
|
28
|
+
|
|
29
|
+
specify do
|
|
30
|
+
expect(subject.name).to eq(queue_name)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe '#delete_messages' do
|
|
36
|
+
let(:entries) do
|
|
37
|
+
[
|
|
38
|
+
{ id: '1', receipt_handle: '1' },
|
|
39
|
+
{ id: '2', receipt_handle: '2' }
|
|
40
|
+
]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'deletes' do
|
|
44
|
+
expect(sqs).to receive(:delete_message_batch).with(entries: entries, queue_url: queue_url).and_return(double(failed: []))
|
|
45
|
+
|
|
46
|
+
subject.delete_messages(entries: entries)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context 'when it fails' do
|
|
50
|
+
it 'logs the reason' do
|
|
51
|
+
failure = double(id: 'id', code: 'code', message: '...', sender_fault: false)
|
|
52
|
+
logger = double 'Logger'
|
|
53
|
+
|
|
54
|
+
expect(sqs).to receive(:delete_message_batch).with(entries: entries, queue_url: queue_url).and_return(double(failed: [failure]))
|
|
55
|
+
expect(subject).to receive(:logger).and_return(logger)
|
|
56
|
+
expect(logger).to receive(:error)
|
|
57
|
+
|
|
58
|
+
subject.delete_messages(entries: entries)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
15
62
|
|
|
16
63
|
describe '#send_message' do
|
|
17
|
-
before {
|
|
18
|
-
|
|
19
|
-
}
|
|
64
|
+
before { allow(subject).to receive(:fifo?).and_return(false) }
|
|
65
|
+
|
|
20
66
|
it 'accepts SQS request parameters' do
|
|
21
67
|
# https://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#send_message-instance_method
|
|
22
68
|
expect(sqs).to receive(:send_message).with(hash_including(message_body: 'msg1'))
|
|
@@ -77,24 +123,24 @@ describe Shoryuken::Queue do
|
|
|
77
123
|
|
|
78
124
|
it 'accepts an array of messages' do
|
|
79
125
|
options = { entries: [{ id: '0',
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
126
|
+
message_body: 'msg1',
|
|
127
|
+
delay_seconds: 1,
|
|
128
|
+
message_attributes: { attr: 'attr1' } },
|
|
129
|
+
{ id: '1',
|
|
130
|
+
message_body: 'msg2',
|
|
131
|
+
delay_seconds: 1,
|
|
132
|
+
message_attributes: { attr: 'attr2' } }] }
|
|
87
133
|
|
|
88
134
|
expect(sqs).to receive(:send_message_batch).with(hash_including(options))
|
|
89
135
|
|
|
90
136
|
subject.send_messages([{ message_body: 'msg1',
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
137
|
+
delay_seconds: 1,
|
|
138
|
+
message_attributes: { attr: 'attr1' }
|
|
139
|
+
}, {
|
|
140
|
+
message_body: 'msg2',
|
|
141
|
+
delay_seconds: 1,
|
|
142
|
+
message_attributes: { attr: 'attr2' }
|
|
143
|
+
}])
|
|
98
144
|
end
|
|
99
145
|
|
|
100
146
|
context 'when FIFO' do
|
|
@@ -125,9 +171,9 @@ describe Shoryuken::Queue do
|
|
|
125
171
|
end
|
|
126
172
|
|
|
127
173
|
subject.send_messages([{ message_body: 'msg1',
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
174
|
+
message_attributes: { attr: 'attr1' },
|
|
175
|
+
message_group_id: 'my group',
|
|
176
|
+
message_deduplication_id: 'my id' }])
|
|
131
177
|
end
|
|
132
178
|
end
|
|
133
179
|
end
|
|
@@ -151,13 +197,13 @@ describe Shoryuken::Queue do
|
|
|
151
197
|
context 'when queue is FIFO' do
|
|
152
198
|
let(:fifo) { true }
|
|
153
199
|
|
|
154
|
-
|
|
200
|
+
specify { expect(subject.fifo?).to be }
|
|
155
201
|
end
|
|
156
202
|
|
|
157
203
|
context 'when queue is not FIFO' do
|
|
158
204
|
let(:fifo) { false }
|
|
159
205
|
|
|
160
|
-
|
|
206
|
+
specify { expect(subject.fifo?).to_not be }
|
|
161
207
|
end
|
|
162
208
|
end
|
|
163
209
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
-
require 'shoryuken/
|
|
3
|
-
require 'shoryuken/launcher'
|
|
2
|
+
require 'shoryuken/runner'
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
# rubocop:disable Metrics/BlockLength
|
|
5
|
+
RSpec.describe Shoryuken::Runner do
|
|
6
|
+
let(:cli) { Shoryuken::Runner.instance }
|
|
7
7
|
|
|
8
8
|
before do
|
|
9
9
|
# make sure we do not bail
|
|
@@ -16,50 +16,35 @@ RSpec.describe Shoryuken::CLI do
|
|
|
16
16
|
describe '#run' do
|
|
17
17
|
let(:launcher) { instance_double('Shoryuken::Launcher') }
|
|
18
18
|
|
|
19
|
-
before
|
|
19
|
+
before do
|
|
20
20
|
allow(Shoryuken::Launcher).to receive(:new).and_return(launcher)
|
|
21
|
-
allow(launcher).to receive(:
|
|
22
|
-
allow(launcher).to receive(:stop)
|
|
21
|
+
allow(launcher).to receive(:start).and_raise(Interrupt)
|
|
22
|
+
allow(launcher).to receive(:stop!)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
it 'does not raise' do
|
|
26
|
-
expect { cli.run(
|
|
26
|
+
expect { cli.run({}) }.to_not raise_error
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
it 'daemonizes with --daemon --logfile' do
|
|
30
|
-
expect(cli).to receive(:celluloid_loaded?).and_return(false)
|
|
31
30
|
expect(Process).to receive(:daemon)
|
|
32
|
-
cli.run(
|
|
31
|
+
cli.run(daemon: true, logfile: '/dev/null')
|
|
33
32
|
end
|
|
34
33
|
|
|
35
34
|
it 'does NOT daemonize with --logfile' do
|
|
36
35
|
expect(Process).to_not receive(:daemon)
|
|
37
|
-
cli.run(
|
|
36
|
+
cli.run(logfile: '/dev/null')
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
it 'writes PID file with --pidfile' do
|
|
41
40
|
pidfile = instance_double('File')
|
|
42
41
|
expect(File).to receive(:open).with('/dev/null', 'w').and_yield(pidfile)
|
|
43
42
|
expect(pidfile).to receive(:puts).with(Process.pid)
|
|
44
|
-
cli.run(
|
|
43
|
+
cli.run(pidfile: '/dev/null')
|
|
45
44
|
end
|
|
46
45
|
end
|
|
47
46
|
|
|
48
47
|
describe '#daemonize' do
|
|
49
|
-
before(:each) do
|
|
50
|
-
allow(cli).to receive(:celluloid_loaded?).and_return(false)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
it 'raises if logfile is not set' do
|
|
54
|
-
expect { cli.send(:daemonize, daemon: true) }.to raise_error(ArgumentError)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
it 'raises if Celluloid is already loaded' do
|
|
58
|
-
expect(cli).to receive(:celluloid_loaded?).and_return(true)
|
|
59
|
-
args = { daemon: true, logfile: '/dev/null' }
|
|
60
|
-
expect { cli.send(:daemonize, args) }.to raise_error(RuntimeError)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
48
|
it 'calls Process.daemon' do
|
|
64
49
|
args = { daemon: true, logfile: '/dev/null' }
|
|
65
50
|
expect(Process).to receive(:daemon).with(true, true)
|
data/spec/shoryuken_spec.rb
CHANGED
|
@@ -1,51 +1,4 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
describe Shoryuken do
|
|
4
|
-
describe '.register_worker' do
|
|
5
|
-
it 'registers a worker' do
|
|
6
|
-
described_class.worker_registry.clear
|
|
7
|
-
described_class.register_worker('default', TestWorker)
|
|
8
|
-
expect(described_class.worker_registry.workers('default')).to eq([TestWorker])
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
it 'registers a batchable worker' do
|
|
12
|
-
described_class.worker_registry.clear
|
|
13
|
-
TestWorker.get_shoryuken_options['batch'] = true
|
|
14
|
-
described_class.register_worker('default', TestWorker)
|
|
15
|
-
expect(described_class.worker_registry.workers('default')).to eq([TestWorker])
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it 'allows multiple workers' do
|
|
19
|
-
described_class.worker_registry.clear
|
|
20
|
-
described_class.register_worker('default', TestWorker)
|
|
21
|
-
expect(described_class.worker_registry.workers('default')).to eq([TestWorker])
|
|
22
|
-
|
|
23
|
-
class Test2Worker
|
|
24
|
-
include Shoryuken::Worker
|
|
25
|
-
|
|
26
|
-
shoryuken_options queue: 'default'
|
|
27
|
-
|
|
28
|
-
def perform(sqs_msg, body); end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
expect(described_class.worker_registry.workers('default')).to eq([Test2Worker])
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
it 'raises an exception when mixing batchable with non batchable' do
|
|
35
|
-
described_class.worker_registry.clear
|
|
36
|
-
TestWorker.get_shoryuken_options['batch'] = true
|
|
37
|
-
described_class.register_worker('default', TestWorker)
|
|
38
|
-
|
|
39
|
-
expect {
|
|
40
|
-
class BatchableWorker
|
|
41
|
-
include Shoryuken::Worker
|
|
42
|
-
|
|
43
|
-
shoryuken_options queue: 'default', batch: true
|
|
44
|
-
|
|
45
|
-
def perform(sqs_msg, body); end
|
|
46
|
-
end
|
|
47
|
-
}.to raise_error("Could not register BatchableWorker for 'default', because TestWorker is already registered for this queue, " \
|
|
48
|
-
"and Shoryuken doesn't support a batchable worker for a queue with multiple workers")
|
|
49
|
-
end
|
|
50
|
-
end
|
|
3
|
+
RSpec.describe Shoryuken do
|
|
51
4
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -2,11 +2,10 @@ require 'bundler/setup'
|
|
|
2
2
|
Bundler.setup
|
|
3
3
|
|
|
4
4
|
require 'pry-byebug'
|
|
5
|
-
require 'celluloid/current'
|
|
6
5
|
require 'shoryuken'
|
|
7
6
|
require 'json'
|
|
8
|
-
require 'multi_xml'
|
|
9
7
|
require 'dotenv'
|
|
8
|
+
require 'securerandom'
|
|
10
9
|
Dotenv.load
|
|
11
10
|
|
|
12
11
|
if ENV['CODECLIMATE_REPO_TOKEN']
|
|
@@ -19,16 +18,6 @@ config_file = File.join(File.expand_path('../..', __FILE__), 'spec', 'shoryuken.
|
|
|
19
18
|
Shoryuken::EnvironmentLoader.setup_options(config_file: config_file)
|
|
20
19
|
|
|
21
20
|
Shoryuken.logger.level = Logger::UNKNOWN
|
|
22
|
-
Celluloid.logger.level = Logger::UNKNOWN
|
|
23
|
-
|
|
24
|
-
# I'm not sure whether this is an issue specific to running Shoryuken against github.com/comcast/cmb
|
|
25
|
-
# as opposed to AWS itself, but sometimes the receive_messages call returns XML that looks like this:
|
|
26
|
-
#
|
|
27
|
-
# <ReceiveMessageResponse>\n\t<ReceiveMessageResult>\n\t</ReceiveMessageResult> ... </ReceiveMessageResponse>
|
|
28
|
-
#
|
|
29
|
-
# The default MultiXML parser is ReXML, which seems to mishandle \n\t chars. Nokogiri seems to be
|
|
30
|
-
# the only one that correctly ignore this whitespace.
|
|
31
|
-
MultiXml.parser = :nokogiri
|
|
32
21
|
|
|
33
22
|
class TestWorker
|
|
34
23
|
include Shoryuken::Worker
|
|
@@ -40,32 +29,37 @@ end
|
|
|
40
29
|
|
|
41
30
|
RSpec.configure do |config|
|
|
42
31
|
# Only run slow tests if SPEC_ALL=true and AWS_ACCESS_KEY_ID is present
|
|
43
|
-
# The AWS_ACCESS_KEY_ID checker is because Travis CI
|
|
32
|
+
# The AWS_ACCESS_KEY_ID checker is because Travis CI
|
|
33
|
+
# does not expose ENV variables to pull requests from forked repositories
|
|
44
34
|
# http://docs.travis-ci.com/user/pull-requests/
|
|
45
|
-
config.filter_run_excluding slow: true if ENV['SPEC_ALL'] != 'true' || ENV['AWS_ACCESS_KEY_ID'].nil?
|
|
35
|
+
# config.filter_run_excluding slow: true if ENV['SPEC_ALL'] != 'true' || ENV['AWS_ACCESS_KEY_ID'].nil?
|
|
36
|
+
config.filter_run_excluding slow: true
|
|
46
37
|
|
|
47
38
|
config.before do
|
|
48
39
|
Shoryuken::Client.class_variable_set :@@queues, {}
|
|
49
|
-
Shoryuken::Client.class_variable_set :@@visibility_timeouts, {}
|
|
50
40
|
|
|
51
41
|
Shoryuken::Client.sqs = nil
|
|
52
|
-
Shoryuken::Client.sqs_resource = nil
|
|
53
|
-
Shoryuken::Client.sns = nil
|
|
54
42
|
|
|
55
|
-
Shoryuken.
|
|
43
|
+
Shoryuken.groups.clear
|
|
56
44
|
|
|
57
45
|
Shoryuken.options[:concurrency] = 1
|
|
58
46
|
Shoryuken.options[:delay] = 1
|
|
59
47
|
Shoryuken.options[:timeout] = 1
|
|
60
48
|
Shoryuken.options[:daemon] = nil
|
|
61
49
|
Shoryuken.options[:logfile] = nil
|
|
62
|
-
|
|
63
|
-
Shoryuken.options[:aws].delete(:receive_message)
|
|
50
|
+
Shoryuken.options[:queues] = nil
|
|
64
51
|
|
|
65
52
|
TestWorker.get_shoryuken_options.clear
|
|
66
53
|
TestWorker.get_shoryuken_options['queue'] = 'default'
|
|
67
54
|
|
|
55
|
+
Shoryuken.active_job_queue_name_prefixing = false
|
|
56
|
+
|
|
68
57
|
Shoryuken.worker_registry.clear
|
|
69
58
|
Shoryuken.register_worker('default', TestWorker)
|
|
59
|
+
|
|
60
|
+
Aws.config[:stub_responses] = true
|
|
61
|
+
|
|
62
|
+
allow(Concurrent).to receive(:global_io_executor).and_return(Concurrent::ImmediateExecutor.new)
|
|
63
|
+
allow(Shoryuken).to receive(:active_job?).and_return(false)
|
|
70
64
|
end
|
|
71
65
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class EndlessInterruptiveWorker
|
|
2
|
+
include Shoryuken::Worker
|
|
3
|
+
|
|
4
|
+
# Usage:
|
|
5
|
+
# QUEUE="super-q"
|
|
6
|
+
# MAX_EXECUTION_TIME=2000 QUEUE=$QUEUE \
|
|
7
|
+
# bundle exec ./bin/shoryuken -r ./examples/endless_uninterruptive_worker.rb -q $QUEUE -c 8
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def queue
|
|
11
|
+
ENV['QUEUE'] || 'default'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def max_execution_time
|
|
15
|
+
ENV["MAX_EXECUTION_TIME"] ? ENV["MAX_EXECUTION_TIME"].to_i : 100
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def rng
|
|
19
|
+
@rng ||= Random.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# returns a random number between 0 and 100
|
|
23
|
+
def random_number(hi = 1000)
|
|
24
|
+
(rng.rand * hi).to_i
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def perform(sqs_msg, body)
|
|
29
|
+
Shoryuken.logger.info("Received message: '#{body}'")
|
|
30
|
+
|
|
31
|
+
execution_ms = self.class.random_number(self.class.max_execution_time)
|
|
32
|
+
Shoryuken.logger.info("Going to sleep for #{execution_ms}ms")
|
|
33
|
+
|
|
34
|
+
new_body = "#{execution_ms}-" + body.to_s
|
|
35
|
+
sleep(execution_ms.to_f / 1000)
|
|
36
|
+
|
|
37
|
+
self.class.perform_async(new_body.slice(0, 512))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
shoryuken_options queue: queue, auto_delete: true
|
|
41
|
+
end
|