shoryuken 3.0.9 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/shoryuken.rb CHANGED
@@ -20,185 +20,51 @@ require 'shoryuken/middleware/server/auto_delete'
20
20
  Shoryuken::Middleware::Server.autoload :AutoExtendVisibility, 'shoryuken/middleware/server/auto_extend_visibility'
21
21
  require 'shoryuken/middleware/server/exponential_backoff_retry'
22
22
  require 'shoryuken/middleware/server/timing'
23
- require 'shoryuken/polling'
23
+ require 'shoryuken/polling/base'
24
+ require 'shoryuken/polling/weighted_round_robin'
25
+ require 'shoryuken/polling/strict_priority'
24
26
  require 'shoryuken/manager'
25
27
  require 'shoryuken/launcher'
26
28
  require 'shoryuken/processor'
27
29
  require 'shoryuken/fetcher'
30
+ require 'shoryuken/options'
28
31
 
29
32
  module Shoryuken
30
- DEFAULTS = {
31
- concurrency: 25,
32
- queues: [],
33
- aws: {},
34
- delay: 0,
35
- timeout: 8,
36
- lifecycle_events: {
37
- startup: [],
38
- dispatch: [],
39
- quiet: [],
40
- shutdown: []
41
- },
42
- polling_strategy: Polling::WeightedRoundRobin
43
- }.freeze
44
-
45
- @@queues = []
46
- @@worker_registry = DefaultWorkerRegistry.new
47
- @@active_job_queue_name_prefixing = false
48
- @@sqs_client = nil
49
- @@sqs_client_receive_message_opts = {}
50
- @@start_callback = nil
51
- @@stop_callback = nil
52
-
53
- class << self
54
- def queues
55
- @@queues
56
- end
57
-
58
- def add_queue(queue, priority = 1)
59
- priority.times { queues << queue }
60
- end
61
-
62
- def worker_registry
63
- @@worker_registry
64
- end
65
-
66
- def worker_registry=(worker_registry)
67
- @@worker_registry = worker_registry
68
- end
69
-
70
- def start_callback
71
- @@start_callback
72
- end
73
-
74
- def start_callback=(start_callback)
75
- @@start_callback = start_callback
76
- end
77
-
78
- def stop_callback
79
- @@stop_callback
80
- end
81
-
82
- def stop_callback=(stop_callback)
83
- @@stop_callback = stop_callback
84
- end
85
-
86
- def active_job_queue_name_prefixing
87
- @@active_job_queue_name_prefixing
88
- end
89
-
90
- def active_job_queue_name_prefixing=(active_job_queue_name_prefixing)
91
- @@active_job_queue_name_prefixing = active_job_queue_name_prefixing
92
- end
93
-
94
- def sqs_client
95
- @@sqs_client ||= Aws::SQS::Client.new
96
- end
97
-
98
- def sqs_client=(sqs_client)
99
- @@sqs_client = sqs_client
100
- end
101
-
102
- def sqs_client_receive_message_opts
103
- @@sqs_client_receive_message_opts
104
- end
105
-
106
- def sqs_client_receive_message_opts=(sqs_client_receive_message_opts)
107
- @@sqs_client_receive_message_opts = sqs_client_receive_message_opts
108
- end
109
-
110
- def options
111
- @@options ||= DEFAULTS.dup
112
- end
113
-
114
- def logger
115
- Shoryuken::Logging.logger
116
- end
117
-
118
- def register_worker(*args)
119
- @@worker_registry.register_worker(*args)
120
- end
121
-
122
- def configure_server
123
- yield self if server?
124
- end
125
-
126
- def server_middleware
127
- @@server_chain ||= default_server_middleware
128
- yield @@server_chain if block_given?
129
- @@server_chain
130
- end
131
-
132
- def configure_client
133
- yield self unless server?
134
- end
135
-
136
- def client_middleware
137
- @@client_chain ||= default_client_middleware
138
- yield @@client_chain if block_given?
139
- @@client_chain
140
- end
141
-
142
- def default_worker_options
143
- @@default_worker_options ||= {
144
- 'queue' => 'default',
145
- 'delete' => false,
146
- 'auto_delete' => false,
147
- 'auto_visibility_timeout' => false,
148
- 'retry_intervals' => nil,
149
- 'batch' => false
150
- }
151
- end
152
-
153
- def default_worker_options=(default_worker_options)
154
- @@default_worker_options = default_worker_options
155
- end
156
-
157
- def on_start(&block)
158
- @@start_callback = block
159
- end
160
-
161
- def on_stop(&block)
162
- @@stop_callback = block
163
- end
164
-
165
- # Register a block to run at a point in the Shoryuken lifecycle.
166
- # :startup, :quiet or :shutdown are valid events.
167
- #
168
- # Shoryuken.configure_server do |config|
169
- # config.on(:shutdown) do
170
- # puts "Goodbye cruel world!"
171
- # end
172
- # end
173
- def on(event, &block)
174
- fail ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
175
- fail ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
176
- options[:lifecycle_events][event] << block
177
- end
178
-
179
- private
180
-
181
- def default_server_middleware
182
- Middleware::Chain.new do |m|
183
- m.add Middleware::Server::Timing
184
- m.add Middleware::Server::ExponentialBackoffRetry
185
- m.add Middleware::Server::AutoDelete
186
- m.add Middleware::Server::AutoExtendVisibility
187
- if defined?(::ActiveRecord::Base)
188
- require 'shoryuken/middleware/server/active_record'
189
- m.add Middleware::Server::ActiveRecord
190
- end
191
- end
192
- end
193
-
194
- def default_client_middleware
195
- Middleware::Chain.new
196
- end
197
-
198
- def server?
199
- defined?(Shoryuken::CLI)
200
- end
201
- end
33
+ extend SingleForwardable
34
+
35
+ def_delegators(
36
+ :'Shoryuken::Options',
37
+ :add_group,
38
+ :groups,
39
+ :add_queue,
40
+ :ungrouped_queues,
41
+ :worker_registry,
42
+ :worker_registry=,
43
+ :polling_strategy,
44
+ :start_callback,
45
+ :start_callback=,
46
+ :stop_callback,
47
+ :stop_callback=,
48
+ :active_job_queue_name_prefixing,
49
+ :active_job_queue_name_prefixing=,
50
+ :sqs_client,
51
+ :sqs_client=,
52
+ :sqs_client_receive_message_opts,
53
+ :sqs_client_receive_message_opts=,
54
+ :options,
55
+ :logger,
56
+ :register_worker,
57
+ :configure_server,
58
+ :server?,
59
+ :server_middleware,
60
+ :configure_client,
61
+ :client_middleware,
62
+ :default_worker_options,
63
+ :default_worker_options=,
64
+ :on_start,
65
+ :on_stop,
66
+ :on
67
+ )
202
68
  end
203
69
 
204
70
  require 'shoryuken/extensions/active_job_adapter' if defined?(::ActiveJob)
data/shoryuken.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency 'pry-byebug'
24
24
  spec.add_development_dependency 'dotenv'
25
25
 
26
- spec.add_dependency 'aws-sdk-core', '~> 2'
26
+ spec.add_dependency 'aws-sdk-core', '> 2'
27
27
  spec.add_dependency 'concurrent-ruby'
28
28
  spec.add_dependency 'thor'
29
29
  end
@@ -6,25 +6,31 @@ require 'securerandom'
6
6
  RSpec.describe Shoryuken::Launcher do
7
7
  describe 'Consuming messages', slow: :true do
8
8
  before do
9
+ Aws.config[:stub_responses] = false
10
+ Aws.config[:region] = 'us-east-1'
11
+
9
12
  StandardWorker.received_messages = 0
10
13
 
11
- queue = "test_shoryuken#{StandardWorker}_#{SecureRandom.uuid}"
14
+ queue = "shoryuken-travis-#{StandardWorker}-#{SecureRandom.uuid}"
12
15
 
13
- Shoryuken::Client.sqs.create_queue queue_name: queue
16
+ Shoryuken::Client.sqs.create_queue(queue_name: queue)
14
17
 
15
- Shoryuken.queues << queue
18
+ Shoryuken.add_group('default', 1)
19
+ Shoryuken.add_queue(queue, 1, 'default')
16
20
 
17
21
  StandardWorker.get_shoryuken_options['queue'] = queue
18
22
 
19
- Shoryuken.register_worker queue, StandardWorker
23
+ Shoryuken.register_worker(queue, StandardWorker)
20
24
  end
21
25
 
22
26
  after do
27
+ Aws.config[:stub_responses] = true
28
+
23
29
  queue_url = Shoryuken::Client.sqs.get_queue_url(
24
30
  queue_name: StandardWorker.get_shoryuken_options['queue']
25
31
  ).queue_url
26
32
 
27
- Shoryuken::Client.sqs.delete_queue queue_url: queue_url
33
+ Shoryuken::Client.sqs.delete_queue(queue_url: queue_url)
28
34
  end
29
35
 
30
36
  it 'consumes as a command worker' do
@@ -61,7 +67,7 @@ RSpec.describe Shoryuken::Launcher do
61
67
  end
62
68
 
63
69
  def poll_queues_until
64
- subject.run
70
+ subject.start
65
71
 
66
72
  Timeout::timeout(10) do
67
73
  begin
@@ -15,20 +15,11 @@ RSpec.describe Shoryuken::EnvironmentLoader do
15
15
  allow(subject).to receive(:patch_deprecated_workers)
16
16
  end
17
17
 
18
- it 'parses' do
19
- Shoryuken.options[:queues] = ['queue_1']
18
+ specify do
19
+ Shoryuken.options[:queues] = ['queue1', ['queue2', 2]]
20
20
  subject.load
21
21
 
22
- expect(Shoryuken.queues).to eq(%w(queue_1))
23
- end
24
-
25
- context 'with priority' do
26
- it 'parses' do
27
- Shoryuken.options[:queues] = ['queue_1', ['queue_2', 2]]
28
- subject.load
29
-
30
- expect(Shoryuken.queues).to eq(%w(queue_1 queue_2 queue_2))
31
- end
22
+ expect(Shoryuken.groups['default'][:queues]).to eq(%w(queue1 queue2 queue2))
32
23
  end
33
24
  end
34
25
  end
@@ -2,36 +2,51 @@ require 'spec_helper'
2
2
  require 'shoryuken/manager'
3
3
  require 'shoryuken/fetcher'
4
4
 
5
- describe Shoryuken::Fetcher do
6
- let(:queue) { instance_double('Shoryuken::Queue') }
7
- let(:queue_name) { 'default' }
5
+ RSpec.describe Shoryuken::Fetcher do
6
+ let(:queue) { instance_double('Shoryuken::Queue') }
7
+ let(:queue_name) { 'default' }
8
8
  let(:queue_config) { Shoryuken::Polling::QueueConfiguration.new(queue_name, {}) }
9
+ let(:group) { 'default' }
9
10
 
10
11
  let(:sqs_msg) do
11
- double(Shoryuken::Message,
12
+ double(
13
+ Shoryuken::Message,
12
14
  queue_url: queue_name,
13
15
  body: 'test',
14
16
  message_id: 'fc754df79cc24c4196ca5996a44b771e',
15
- )
17
+ )
16
18
  end
17
19
 
18
- subject { described_class.new }
20
+ subject { described_class.new(group) }
19
21
 
20
22
  describe '#fetch' do
21
- it 'calls Shoryuken::Client to receive messages' do
23
+ let(:limit) { 1 }
24
+
25
+ specify do
22
26
  expect(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
27
+
28
+ Shoryuken.sqs_client_receive_message_opts[group] = { wait_time_seconds: 10 }
29
+
23
30
  expect(queue).to receive(:receive_messages).
24
- with(max_number_of_messages: 1, attribute_names: ['All'], message_attribute_names: ['All']).
31
+ with(wait_time_seconds: 10, max_number_of_messages: limit, message_attribute_names: ['All'], attribute_names: ['All']).
25
32
  and_return([])
26
- subject.fetch(queue_config, 1)
33
+
34
+ subject.fetch(queue_config, limit)
27
35
  end
28
36
 
29
- it 'maxes messages to receive to 10 (SQS limit)' do
30
- allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
31
- expect(queue).to receive(:receive_messages).
32
- with(max_number_of_messages: 10, attribute_names: ['All'], message_attribute_names: ['All']).
33
- and_return([])
34
- subject.fetch(queue_config, 20)
37
+ context 'when limit is greater than FETCH_LIMIT' do
38
+ let(:limit) { 20 }
39
+
40
+ specify do
41
+ Shoryuken.sqs_client_receive_message_opts[group] = {}
42
+
43
+ allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
44
+ expect(queue).to receive(:receive_messages).
45
+ with(max_number_of_messages: described_class::FETCH_LIMIT, attribute_names: ['All'], message_attribute_names: ['All']).
46
+ and_return([])
47
+
48
+ subject.fetch(queue_config, limit)
49
+ end
35
50
  end
36
51
  end
37
52
  end
@@ -11,28 +11,20 @@ RSpec.describe Shoryuken::Manager do
11
11
  let(:queue) { 'default' }
12
12
  let(:queues) { [queue] }
13
13
  let(:polling_strategy) { Shoryuken::Polling::WeightedRoundRobin.new(queues) }
14
- let(:fetcher) { Shoryuken::Fetcher.new }
14
+ let(:fetcher) { double Shoryuken::Fetcher }
15
15
  let(:concurrency) { 1 }
16
16
 
17
- subject { Shoryuken::Manager.new(fetcher, polling_strategy) }
17
+ subject { Shoryuken::Manager.new(fetcher, polling_strategy, concurrency) }
18
18
 
19
- before(:each) do
20
- Shoryuken.options[:concurrency] = concurrency
19
+ before do
20
+ allow(fetcher).to receive(:fetch).and_return([])
21
21
  end
22
22
 
23
- after(:each) do
23
+ after do
24
24
  Shoryuken.options[:concurrency] = 1
25
25
  TestWorker.get_shoryuken_options['batch'] = false
26
26
  end
27
27
 
28
- describe 'Invalid concurrency setting' do
29
- it 'raises ArgumentError if concurrency is not positive number' do
30
- Shoryuken.options[:concurrency] = -1
31
- expect { Shoryuken::Manager.new(nil, nil) }
32
- .to raise_error(ArgumentError, 'Concurrency value -1 is invalid, it needs to be a positive number')
33
- end
34
- end
35
-
36
28
  describe '#start' do
37
29
  xit 'pauses when there are no active queues' do
38
30
  expect(polling_strategy).to receive(:next_queue).and_return(nil)
@@ -55,10 +47,10 @@ RSpec.describe Shoryuken::Manager do
55
47
  end
56
48
  end
57
49
 
58
- describe '#dispatch_now' do
59
- it 'fires a dispatch event' do
60
- expect(subject).to receive(:fire_event).with(:dispatch).once
61
- subject.send(:dispatch_now)
50
+ describe '#dispatch' do
51
+ xit 'fires a dispatch event' do
52
+ expect(subject).to receive(:fire_event).with(:dispatch)
53
+ subject.send(:dispatch)
62
54
  end
63
55
  end
64
56
 
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Shoryuken::Options do
4
+ describe '.add_group' do
5
+ before do
6
+ Shoryuken.groups.clear
7
+ Shoryuken.add_group('group1', 25)
8
+ Shoryuken.add_group('group2', 25)
9
+ end
10
+
11
+ specify do
12
+ described_class.add_queue('queue1', 1, 'group1')
13
+ described_class.add_queue('queue2', 2, 'group2')
14
+
15
+ expect(described_class.groups['group1'][:queues]).to eq(%w(queue1))
16
+ expect(described_class.groups['group2'][:queues]).to eq(%w(queue2 queue2))
17
+ end
18
+ end
19
+
20
+ describe '.ungrouped_queues' do
21
+ before do
22
+ Shoryuken.groups.clear
23
+ Shoryuken.add_group('group1', 25)
24
+ Shoryuken.add_group('group2', 25)
25
+ end
26
+
27
+ specify do
28
+ described_class.add_queue('queue1', 1, 'group1')
29
+ described_class.add_queue('queue2', 2, 'group2')
30
+
31
+ expect(described_class.ungrouped_queues).to eq(%w(queue1 queue2 queue2))
32
+ end
33
+ end
34
+
35
+ describe '.sqs_client_receive_message_opts' do
36
+ before do
37
+ Shoryuken.sqs_client_receive_message_opts
38
+ end
39
+
40
+ specify do
41
+ Shoryuken.sqs_client_receive_message_opts = { test: 1 }
42
+ expect(Shoryuken.sqs_client_receive_message_opts).to eq('default' => { test: 1 })
43
+
44
+ Shoryuken.sqs_client_receive_message_opts['group1'] = { test: 2 }
45
+
46
+ expect(Shoryuken.sqs_client_receive_message_opts).to eq(
47
+ 'default' => { test: 1 },
48
+ 'group1' => { test: 2 },
49
+ )
50
+ end
51
+ end
52
+
53
+ describe '.register_worker' do
54
+ it 'registers a worker' do
55
+ described_class.worker_registry.clear
56
+ described_class.register_worker('default', TestWorker)
57
+ expect(described_class.worker_registry.workers('default')).to eq([TestWorker])
58
+ end
59
+
60
+ it 'registers a batchable worker' do
61
+ described_class.worker_registry.clear
62
+ TestWorker.get_shoryuken_options['batch'] = true
63
+ described_class.register_worker('default', TestWorker)
64
+ expect(described_class.worker_registry.workers('default')).to eq([TestWorker])
65
+ end
66
+
67
+ it 'allows multiple workers' do
68
+ described_class.worker_registry.clear
69
+ described_class.register_worker('default', TestWorker)
70
+ expect(described_class.worker_registry.workers('default')).to eq([TestWorker])
71
+
72
+ class Test2Worker
73
+ include Shoryuken::Worker
74
+
75
+ shoryuken_options queue: 'default'
76
+
77
+ def perform(sqs_msg, body); end
78
+ end
79
+
80
+ expect(described_class.worker_registry.workers('default')).to eq([Test2Worker])
81
+ end
82
+
83
+ it 'raises an exception when mixing batchable with non batchable' do
84
+ described_class.worker_registry.clear
85
+ TestWorker.get_shoryuken_options['batch'] = true
86
+ described_class.register_worker('default', TestWorker)
87
+
88
+ expect {
89
+ class BatchableWorker
90
+ include Shoryuken::Worker
91
+
92
+ shoryuken_options queue: 'default', batch: true
93
+
94
+ def perform(sqs_msg, body); end
95
+ end
96
+ }.to raise_error("Could not register BatchableWorker for default, because TestWorker is already registered for this queue, " \
97
+ "and Shoryuken doesn't support a batchable worker for a queue with multiple workers")
98
+ end
99
+ end
100
+ end
@@ -1,105 +1,6 @@
1
1
  require 'spec_helper'
2
- require 'shoryuken/polling'
3
2
 
4
- describe Shoryuken::Polling::WeightedRoundRobin do
5
- let(:queue1) { 'shoryuken' }
6
- let(:queue2) { 'uppercut' }
7
- let(:queues) { Array.new }
8
- subject { Shoryuken::Polling::WeightedRoundRobin.new(queues) }
9
-
10
- describe '#next_queue' do
11
- it 'cycles' do
12
- # [shoryuken, 2]
13
- # [uppercut, 1]
14
- queues << queue1
15
- queues << queue1
16
- queues << queue2
17
-
18
- expect(subject.next_queue).to eq(queue1)
19
- expect(subject.next_queue).to eq(queue2)
20
- expect(subject.next_queue).to eq(queue1)
21
- end
22
-
23
- it 'returns nil if there are no active queues' do
24
- expect(subject.next_queue).to eq(nil)
25
- end
26
-
27
- it 'unpauses queues whose pause is expired' do
28
- # [shoryuken, 2]
29
- # [uppercut, 1]
30
- queues << queue1
31
- queues << queue1
32
- queues << queue2
33
-
34
- allow(subject).to receive(:delay).and_return(10)
35
-
36
- now = Time.now
37
- allow(Time).to receive(:now).and_return(now)
38
-
39
- # pause the first queue
40
- subject.messages_found(queue1, 0)
41
- expect(subject.next_queue).to eq(queue2)
42
-
43
- now += 5
44
- allow(Time).to receive(:now).and_return(now)
45
-
46
- # pause the second queue
47
- subject.messages_found(queue2, 0)
48
- expect(subject.next_queue).to eq(nil)
49
-
50
- # queue1 should be unpaused now
51
- now += 6
52
- allow(Time).to receive(:now).and_return(now)
53
- expect(subject.next_queue).to eq(queue1)
54
-
55
- # queue1 should be unpaused and added to the end of queues now
56
- now += 6
57
- allow(Time).to receive(:now).and_return(now)
58
- expect(subject.next_queue).to eq(queue1)
59
- expect(subject.next_queue).to eq(queue2)
60
- end
61
- end
62
-
63
- describe '#messages_found' do
64
- it 'pauses a queue if there are no messages found' do
65
- # [shoryuken, 2]
66
- # [uppercut, 1]
67
- queues << queue1
68
- queues << queue1
69
- queues << queue2
70
-
71
- expect(subject).to receive(:pause).with(queue1).and_call_original
72
- subject.messages_found(queue1, 0)
73
- expect(subject.instance_variable_get(:@queues)).to eq([queue2])
74
- end
75
-
76
- it 'increased the weight if message is found' do
77
- # [shoryuken, 2]
78
- # [uppercut, 1]
79
- queues << queue1
80
- queues << queue1
81
- queues << queue2
82
-
83
- expect(subject.instance_variable_get(:@queues)).to eq([queue1, queue2])
84
- subject.messages_found(queue1, 1)
85
- expect(subject.instance_variable_get(:@queues)).to eq([queue1, queue2, queue1])
86
- end
87
-
88
- it 'respects the maximum queue weight' do
89
- # [shoryuken, 2]
90
- # [uppercut, 1]
91
- queues << queue1
92
- queues << queue1
93
- queues << queue2
94
-
95
- subject.messages_found(queue1, 1)
96
- subject.messages_found(queue1, 1)
97
- expect(subject.instance_variable_get(:@queues)).to eq([queue1, queue2, queue1])
98
- end
99
- end
100
- end
101
-
102
- describe Shoryuken::Polling::StrictPriority do
3
+ RSpec.describe Shoryuken::Polling::StrictPriority do
103
4
  let(:queue1) { 'shoryuken' }
104
5
  let(:queue2) { 'uppercut' }
105
6
  let(:queue3) { 'other' }