shoryuken 4.0.1 → 5.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1f28614380213b13c17dea0f85aae332f8e335f6
4
- data.tar.gz: 34e084982c8aa8ec9b0306f04f548cc1321eec13
2
+ SHA256:
3
+ metadata.gz: ce36f4dac1c75e383185df15db196aec57c968dda1a5823b8d944ad4a9675906
4
+ data.tar.gz: a460a585e97a6c0a3432f6b28bec254d494900672a06bffbd7461e830a8de0cf
5
5
  SHA512:
6
- metadata.gz: 40a160284600447798e3e1ebc61ec1e8bf596b0aa9d5558301d7f647e1ab1af285b845a9ec052eb52e157714d4066d3888f6f2192762898d9fc52decf1503a3b
7
- data.tar.gz: 7f3d7584167804ad724dc3ca3a661cef8a4a60cadccebe333ceacb10333c0eb1bc7db9247439ddce875a1b7cb771769e9516afcda0ae5703991db780acd165d3
6
+ metadata.gz: 833bab295345b25456f617f36e4ec2f2cfd0d823a01ddb87d8f528597ab79b91ce0ae8c67b47ffc2a9d1400f0a5cf62df3125fbdfbb49abd3a5e4a5eb3b7b0ee
7
+ data.tar.gz: 91a4355cd9e180f006e8768bde9627c1df9cf0057d294bdfcb422f479de2ef1784aa5b904c7b97dac8ed7bb7b9879bdea56eb4cd00d51ff456d3b4555f88c991
data/.gitignore CHANGED
@@ -25,3 +25,4 @@ shoryuken.yml
25
25
  *.log
26
26
  .env
27
27
  rubocop.html
28
+ .byebug_history
data/.rubocop.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  AllCops:
2
2
  Exclude:
3
3
  - '**/Gemfile'
4
- TargetRubyVersion: 2.1
4
+ TargetRubyVersion: 2.3
5
5
 
6
6
  Metrics/PerceivedComplexity:
7
7
  Enabled: false
data/.travis.yml CHANGED
@@ -3,10 +3,11 @@ language: ruby
3
3
  rvm:
4
4
  - 2.0.0
5
5
  - 2.1.10
6
- - 2.2.9
7
- - 2.3.6
8
- - 2.4.3
9
- - 2.5.0
6
+ - 2.2.10
7
+ - 2.3.8
8
+ - 2.4.4
9
+ - 2.5.1
10
+ - 2.6.3
10
11
 
11
12
  notifications:
12
13
  email:
@@ -23,7 +24,7 @@ env:
23
24
  script: bundle exec rspec spec
24
25
 
25
26
  before_install:
26
- - gem update bundler
27
+ - gem install bundler -v '< 2'
27
28
 
28
29
  after_success:
29
30
  - bundle exec codeclimate-test-reporter
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ ## [v5.0.0] - 2019-06-18
2
+
3
+ - Fix bug where empty queues were not paused in batch processing mode
4
+ - [#569](https://github.com/phstc/shoryuken/pull/569)
5
+
6
+ - Preserve batch limit when receiving messages from a FIFO queue
7
+ - [#563](https://github.com/phstc/shoryuken/pull/563)
8
+
9
+ - Replace static options with instance options
10
+ - [#534](https://github.com/phstc/shoryuken/pull/534)
11
+
12
+ ## [v4.0.3] - 2019-01-06
13
+
14
+ - Support delay per processing group
15
+ - [#543](https://github.com/phstc/shoryuken/pull/543)
16
+
17
+ ## [v4.0.2] - 2018-11-26
18
+
19
+ - Fix the delegated methods to public warning
20
+ - [#536](https://github.com/phstc/shoryuken/pull/536)
21
+
22
+ - Specify exception class to `raise_error` matcher warning
23
+ - [#537](https://github.com/phstc/shoryuken/pull/537)
24
+
25
+ - Fix spelling of "visibility"
26
+ - [#538](https://github.com/phstc/shoryuken/pull/538)
27
+
1
28
  ## [v4.0.1] - 2018-11-21
2
29
 
3
30
  - Allow caching visibility_timeout lookups
@@ -99,7 +99,7 @@ module Shoryuken
99
99
 
100
100
  def prefix_active_job_queue_names
101
101
  return unless Shoryuken.active_job?
102
- return unless Shoryuken.active_job_queue_name_prefixing
102
+ return unless Shoryuken.active_job_queue_name_prefixing?
103
103
 
104
104
  Shoryuken.options[:queues].to_a.map! do |queue_name, weight|
105
105
  prefix_active_job_queue_name(queue_name, weight)
@@ -122,7 +122,7 @@ module Shoryuken
122
122
 
123
123
  def parse_queues
124
124
  if Shoryuken.options[:queues].to_a.any?
125
- Shoryuken.add_group('default', Shoryuken.options.fetch(:concurrency, 25))
125
+ Shoryuken.add_group('default', Shoryuken.options[:concurrency])
126
126
 
127
127
  Shoryuken.options[:queues].to_a.each do |queue, weight|
128
128
  parse_queue(queue, weight, 'default')
@@ -130,7 +130,8 @@ module Shoryuken
130
130
  end
131
131
 
132
132
  Shoryuken.options[:groups].to_a.each do |group, options|
133
- Shoryuken.add_group(group, options.fetch(:concurrency, 25))
133
+ Shoryuken.add_group(group, options[:concurrency], delay: options[:delay])
134
+
134
135
  options[:queues].to_a.each do |queue, weight|
135
136
  parse_queue(queue, weight, group)
136
137
  end
@@ -14,7 +14,7 @@ module Shoryuken
14
14
 
15
15
  logger.debug { "Looking for new messages in #{queue}" }
16
16
 
17
- sqs_msgs = Array(receive_messages(queue, [FETCH_LIMIT, limit].min))
17
+ sqs_msgs = Array(receive_messages(queue, limit))
18
18
 
19
19
  logger.debug { "Found #{sqs_msgs.size} messages for #{queue.name}" } unless sqs_msgs.empty?
20
20
  logger.debug { "Fetcher for #{queue} completed in #{elapsed(started_at)} ms" }
@@ -49,16 +49,7 @@ module Shoryuken
49
49
 
50
50
  shoryuken_queue = Shoryuken::Client.queues(queue.name)
51
51
 
52
- # For FIFO queues we want to make sure we process one message per group at the time
53
- # if we set max_number_of_messages greater than 1,
54
- # SQS may return more than one message for the same message group
55
- # since Shoryuken uses threads, it will try to process more than one at once
56
- # > The message group ID is the tag that specifies that a message belongs to a specific message group.
57
- # > Messages that belong to the same message group are always processed one by one,
58
- # > in a strict order relative to the message group
59
- # > (however, messages that belong to different message groups might be processed out of order).
60
- # > https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagegroupid-property.html
61
- options[:max_number_of_messages] = shoryuken_queue.fifo? ? 1 : max_number_of_messages(limit, options)
52
+ options[:max_number_of_messages] = max_number_of_messages(shoryuken_queue, limit, options)
62
53
  options[:message_attribute_names] = %w[All]
63
54
  options[:attribute_names] = %w[All]
64
55
 
@@ -67,7 +58,18 @@ module Shoryuken
67
58
  shoryuken_queue.receive_messages(options)
68
59
  end
69
60
 
70
- def max_number_of_messages(limit, options)
61
+ def max_number_of_messages(shoryuken_queue, limit, options)
62
+ # For FIFO queues we want to make sure we process one message per group at a time
63
+ # if we set max_number_of_messages greater than 1,
64
+ # SQS may return more than one message for the same message group
65
+ # since Shoryuken uses threads, it will try to process more than one message at once
66
+ # > The message group ID is the tag that specifies that a message belongs to a specific message group.
67
+ # > Messages that belong to the same message group are always processed one by one,
68
+ # > in a strict order relative to the message group
69
+ # > (however, messages that belong to different message groups might be processed out of order).
70
+ # > https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagegroupid-property.html
71
+ limit = 1 if shoryuken_queue.fifo? && !batched_queue?(shoryuken_queue)
72
+
71
73
  [limit, FETCH_LIMIT, options[:max_number_of_messages]].compact.min
72
74
  end
73
75
 
@@ -77,5 +79,9 @@ module Shoryuken
77
79
 
78
80
  options.to_h.dup
79
81
  end
82
+
83
+ def batched_queue?(queue)
84
+ Shoryuken.worker_registry.batch_receive_messages?(queue.name)
85
+ end
80
86
  end
81
- end
87
+ end
@@ -72,7 +72,7 @@ module Shoryuken
72
72
  Shoryuken.groups.map do |group, options|
73
73
  Shoryuken::Manager.new(
74
74
  Shoryuken::Fetcher.new(group),
75
- Shoryuken.polling_strategy(group).new(options[:queues]),
75
+ Shoryuken.polling_strategy(group).new(options[:queues], Shoryuken.delay(group)),
76
76
  options[:concurrency],
77
77
  executor
78
78
  )
@@ -43,8 +43,8 @@ module Shoryuken
43
43
  logger.debug { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{@polling_strategy.active_queues}" }
44
44
 
45
45
  batched_queue?(queue) ? dispatch_batch(queue) : dispatch_single_messages(queue)
46
- rescue => ex
47
- handle_dispatch_error(ex)
46
+ rescue => e
47
+ handle_dispatch_error(e)
48
48
  ensure
49
49
  dispatch_loop
50
50
  end
@@ -74,9 +74,9 @@ module Shoryuken
74
74
  end
75
75
 
76
76
  def dispatch_batch(queue)
77
- return if (batch = @fetcher.fetch(queue, BATCH_LIMIT)).none?
77
+ batch = @fetcher.fetch(queue, BATCH_LIMIT)
78
78
  @polling_strategy.messages_found(queue.name, batch.size)
79
- assign(queue.name, patch_batch!(batch))
79
+ assign(queue.name, patch_batch!(batch)) if batch.any?
80
80
  end
81
81
 
82
82
  def dispatch_single_messages(queue)
@@ -4,7 +4,7 @@ module Shoryuken
4
4
  concurrency: 25,
5
5
  queues: [],
6
6
  aws: {},
7
- delay: 0,
7
+ delay: 0.0,
8
8
  timeout: 8,
9
9
  lifecycle_events: {
10
10
  startup: [],
@@ -14,221 +14,166 @@ module Shoryuken
14
14
  }
15
15
  }.freeze
16
16
 
17
- @@groups = {}
18
- @@worker_registry = DefaultWorkerRegistry.new
19
- @@active_job_queue_name_prefixing = false
20
- @@sqs_client = nil
21
- @@sqs_client_receive_message_opts = {}
22
- @@start_callback = nil
23
- @@stop_callback = nil
24
- @@worker_executor = Worker::DefaultExecutor
25
- @@launcher_executor = nil
26
- @@cache_visiblity_timeout = false
27
-
28
- class << self
29
- def active_job?
30
- defined?(::ActiveJob)
31
- end
32
-
33
- def add_group(group, concurrency)
34
- groups[group] ||= {
35
- concurrency: concurrency,
36
- queues: []
37
- }
38
- end
39
-
40
- def groups
41
- @@groups
42
- end
43
-
44
- def add_queue(queue, weight, group)
45
- weight.times do
46
- groups[group][:queues] << queue
47
- end
48
- end
49
-
50
- def ungrouped_queues
51
- groups.values.flat_map { |options| options[:queues] }
52
- end
53
-
54
- def worker_registry
55
- @@worker_registry
56
- end
57
-
58
- def worker_registry=(worker_registry)
59
- @@worker_registry = worker_registry
60
- end
61
-
62
- def worker_executor
63
- @@worker_executor
64
- end
65
-
66
- def worker_executor=(worker_executor)
67
- @@worker_executor = worker_executor
68
- end
69
-
70
- def launcher_executor
71
- @@launcher_executor
72
- end
73
-
74
- def launcher_executor=(launcher_executor)
75
- @@launcher_executor = launcher_executor
76
- end
17
+ attr_accessor :active_job_queue_name_prefixing, :cache_visibility_timeout, :default_worker_options, :groups,
18
+ :launcher_executor, :sqs_client, :sqs_client_receive_message_opts,
19
+ :start_callback, :worker_executor, :worker_registry
20
+
21
+ def initialize
22
+ self.groups = {}
23
+ self.worker_registry = DefaultWorkerRegistry.new
24
+ self.active_job_queue_name_prefixing = false
25
+ self.worker_executor = Worker::DefaultExecutor
26
+ self.cache_visibility_timeout = false
27
+ # this is needed for keeping backward compatibility
28
+ @sqs_client_receive_message_opts ||= {}
29
+ end
77
30
 
78
- def polling_strategy(group)
79
- strategy = (group == 'default' ? options : options[:groups].to_h[group]).to_h[:polling_strategy]
80
-
81
- case strategy
82
- when 'WeightedRoundRobin', nil # Default case
83
- Polling::WeightedRoundRobin
84
- when 'StrictPriority'
85
- Polling::StrictPriority
86
- when Class
87
- strategy
88
- else
89
- raise ArgumentError, "#{strategy} is not a valid polling_strategy"
90
- end
91
- end
31
+ def active_job?
32
+ defined?(::ActiveJob)
33
+ end
92
34
 
93
- def start_callback
94
- @@start_callback
95
- end
35
+ def add_group(group, concurrency = nil, delay: nil)
36
+ concurrency ||= options[:concurrency]
37
+ delay ||= options[:delay]
96
38
 
97
- def start_callback=(start_callback)
98
- @@start_callback = start_callback
99
- end
39
+ groups[group] ||= {
40
+ concurrency: concurrency,
41
+ delay: delay,
42
+ queues: []
43
+ }
44
+ end
100
45
 
101
- def stop_callback
102
- @@stop_callback
46
+ def add_queue(queue, weight, group)
47
+ weight.times do
48
+ groups[group][:queues] << queue
103
49
  end
50
+ end
104
51
 
105
- def stop_callback=(stop_callback)
106
- @@stop_callback = stop_callback
107
- end
52
+ def ungrouped_queues
53
+ groups.values.flat_map { |options| options[:queues] }
54
+ end
108
55
 
109
- def active_job_queue_name_prefixing
110
- @@active_job_queue_name_prefixing
56
+ def polling_strategy(group)
57
+ strategy = (group == 'default' ? options : options[:groups].to_h[group]).to_h[:polling_strategy]
58
+ case strategy
59
+ when 'WeightedRoundRobin', nil # Default case
60
+ Polling::WeightedRoundRobin
61
+ when 'StrictPriority'
62
+ Polling::StrictPriority
63
+ when Class
64
+ strategy
65
+ else
66
+ raise ArgumentError, "#{strategy} is not a valid polling_strategy"
111
67
  end
68
+ end
112
69
 
113
- def active_job_queue_name_prefixing=(active_job_queue_name_prefixing)
114
- @@active_job_queue_name_prefixing = active_job_queue_name_prefixing
115
- end
70
+ def delay(group)
71
+ groups[group].to_h.fetch(:delay, options[:delay]).to_f
72
+ end
116
73
 
117
- def sqs_client
118
- @@sqs_client ||= Aws::SQS::Client.new
119
- end
74
+ def sqs_client
75
+ @sqs_client ||= Aws::SQS::Client.new
76
+ end
120
77
 
121
- def sqs_client=(sqs_client)
122
- @@sqs_client = sqs_client
123
- end
78
+ def sqs_client_receive_message_opts=(sqs_client_receive_message_opts)
79
+ @sqs_client_receive_message_opts['default'] = sqs_client_receive_message_opts
80
+ end
124
81
 
125
- def sqs_client_receive_message_opts
126
- @@sqs_client_receive_message_opts
127
- end
82
+ def options
83
+ @options ||= DEFAULTS.dup
84
+ end
128
85
 
129
- def sqs_client_receive_message_opts=(sqs_client_receive_message_opts)
130
- @@sqs_client_receive_message_opts['default'] = sqs_client_receive_message_opts
131
- end
86
+ def logger
87
+ Shoryuken::Logging.logger
88
+ end
132
89
 
133
- def options
134
- @@options ||= DEFAULTS.dup
135
- end
90
+ def register_worker(*args)
91
+ worker_registry.register_worker(*args)
92
+ end
136
93
 
137
- def logger
138
- Shoryuken::Logging.logger
139
- end
94
+ def configure_server
95
+ yield self if server?
96
+ end
140
97
 
141
- def register_worker(*args)
142
- @@worker_registry.register_worker(*args)
143
- end
98
+ def server_middleware
99
+ @_server_chain ||= default_server_middleware
100
+ yield @_server_chain if block_given?
101
+ @_server_chain
102
+ end
144
103
 
145
- def configure_server
146
- yield self if server?
147
- end
104
+ def configure_client
105
+ yield self unless server?
106
+ end
148
107
 
149
- def server_middleware
150
- @@server_chain ||= default_server_middleware
151
- yield @@server_chain if block_given?
152
- @@server_chain
153
- end
108
+ def client_middleware
109
+ @_client_chain ||= default_client_middleware
110
+ yield @_client_chain if block_given?
111
+ @_client_chain
112
+ end
154
113
 
155
- def configure_client
156
- yield self unless server?
157
- end
114
+ def default_worker_options
115
+ @default_worker_options ||= {
116
+ 'queue' => 'default',
117
+ 'delete' => false,
118
+ 'auto_delete' => false,
119
+ 'auto_visibility_timeout' => false,
120
+ 'retry_intervals' => nil,
121
+ 'batch' => false
122
+ }
123
+ end
158
124
 
159
- def client_middleware
160
- @@client_chain ||= default_client_middleware
161
- yield @@client_chain if block_given?
162
- @@client_chain
163
- end
125
+ def on_start(&block)
126
+ self.start_callback = block
127
+ end
164
128
 
165
- def default_worker_options
166
- @@default_worker_options ||= {
167
- 'queue' => 'default',
168
- 'delete' => false,
169
- 'auto_delete' => false,
170
- 'auto_visibility_timeout' => false,
171
- 'retry_intervals' => nil,
172
- 'batch' => false
173
- }
174
- end
129
+ def on_stop(&block)
130
+ self.stop_callback = block
131
+ end
175
132
 
176
- def default_worker_options=(default_worker_options)
177
- @@default_worker_options = default_worker_options
178
- end
133
+ # Register a block to run at a point in the Shoryuken lifecycle.
134
+ # :startup, :quiet or :shutdown are valid events.
135
+ #
136
+ # Shoryuken.configure_server do |config|
137
+ # config.on(:shutdown) do
138
+ # puts "Goodbye cruel world!"
139
+ # end
140
+ # end
141
+ def on(event, &block)
142
+ fail ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
143
+ fail ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
144
+
145
+ options[:lifecycle_events][event] << block
146
+ end
179
147
 
180
- def on_start(&block)
181
- @@start_callback = block
182
- end
148
+ def server?
149
+ defined?(Shoryuken::CLI)
150
+ end
183
151
 
184
- def on_stop(&block)
185
- @@stop_callback = block
186
- end
152
+ def cache_visibility_timeout?
153
+ @cache_visibility_timeout
154
+ end
187
155
 
188
- # Register a block to run at a point in the Shoryuken lifecycle.
189
- # :startup, :quiet or :shutdown are valid events.
190
- #
191
- # Shoryuken.configure_server do |config|
192
- # config.on(:shutdown) do
193
- # puts "Goodbye cruel world!"
194
- # end
195
- # end
196
- def on(event, &block)
197
- fail ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
198
- fail ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
199
- options[:lifecycle_events][event] << block
200
- end
156
+ def active_job_queue_name_prefixing?
157
+ @active_job_queue_name_prefixing
158
+ end
201
159
 
202
- private
203
-
204
- def default_server_middleware
205
- Middleware::Chain.new do |m|
206
- m.add Middleware::Server::Timing
207
- m.add Middleware::Server::ExponentialBackoffRetry
208
- m.add Middleware::Server::AutoDelete
209
- m.add Middleware::Server::AutoExtendVisibility
210
- if defined?(::ActiveRecord::Base)
211
- require 'shoryuken/middleware/server/active_record'
212
- m.add Middleware::Server::ActiveRecord
213
- end
160
+ private
161
+
162
+ def default_server_middleware
163
+ Middleware::Chain.new do |m|
164
+ m.add Middleware::Server::Timing
165
+ m.add Middleware::Server::ExponentialBackoffRetry
166
+ m.add Middleware::Server::AutoDelete
167
+ m.add Middleware::Server::AutoExtendVisibility
168
+ if defined?(::ActiveRecord::Base)
169
+ require 'shoryuken/middleware/server/active_record'
170
+ m.add Middleware::Server::ActiveRecord
214
171
  end
215
172
  end
173
+ end
216
174
 
217
- def default_client_middleware
218
- Middleware::Chain.new
219
- end
220
-
221
- def server?
222
- defined?(Shoryuken::CLI)
223
- end
224
-
225
- def cache_visiblity_timeout?
226
- @@cache_visiblity_timeout
227
- end
228
-
229
- def cache_visiblity_timeout=(cache_visiblity_timeout)
230
- @@cache_visiblity_timeout = cache_visiblity_timeout
231
- end
175
+ def default_client_middleware
176
+ Middleware::Chain.new
232
177
  end
233
178
  end
234
179
  end
@@ -57,10 +57,8 @@ module Shoryuken
57
57
  end
58
58
  end
59
59
 
60
- private
61
-
62
60
  def delay
63
- Shoryuken.options[:delay].to_f
61
+ @delay || Shoryuken.options[:delay].to_f
64
62
  end
65
63
  end
66
64
  end
@@ -1,7 +1,7 @@
1
1
  module Shoryuken
2
2
  module Polling
3
3
  class StrictPriority < BaseStrategy
4
- def initialize(queues)
4
+ def initialize(queues, delay = nil)
5
5
  # Priority ordering of the queues, highest priority first
6
6
  @queues = queues
7
7
  .group_by { |q| q }
@@ -12,6 +12,7 @@ module Shoryuken
12
12
  @paused_until = queues
13
13
  .each_with_object({}) { |queue, h| h[queue] = Time.at(0) }
14
14
 
15
+ @delay = delay
15
16
  # Start queues at 0
16
17
  reset_next_queue
17
18
  end
@@ -1,10 +1,11 @@
1
1
  module Shoryuken
2
2
  module Polling
3
3
  class WeightedRoundRobin < BaseStrategy
4
- def initialize(queues)
4
+ def initialize(queues, delay = nil)
5
5
  @initial_queues = queues
6
6
  @queues = queues.dup.uniq
7
7
  @paused_queues = []
8
+ @delay = delay
8
9
  end
9
10
 
10
11
  def next_queue
@@ -14,9 +14,9 @@ module Shoryuken
14
14
  end
15
15
 
16
16
  def visibility_timeout
17
- # Always lookup for the latest visiblity when cache is disabled
17
+ # Always lookup for the latest visibility when cache is disabled
18
18
  # setting it to nil, forces re-lookup
19
- @_visibility_timeout = nil unless Shoryuken.cache_visiblity_timeout?
19
+ @_visibility_timeout = nil unless Shoryuken.cache_visibility_timeout?
20
20
  @_visibility_timeout ||= queue_attributes.attributes[VISIBILITY_TIMEOUT_ATTR].to_i
21
21
  end
22
22
 
@@ -66,7 +66,7 @@ module Shoryuken
66
66
  end
67
67
 
68
68
  def set_name_and_url(name_or_url)
69
- if name_or_url.start_with?('https://sqs.')
69
+ if name_or_url.include?('://')
70
70
  set_by_url(name_or_url)
71
71
 
72
72
  # anticipate the fifo? checker for validating the queue URL
@@ -1,3 +1,3 @@
1
1
  module Shoryuken
2
- VERSION = '4.0.1'.freeze
2
+ VERSION = '5.0.0'.freeze
3
3
  end
@@ -17,9 +17,9 @@ module Shoryuken
17
17
  alias_method :perform_at, :perform_in
18
18
 
19
19
  def server_middleware
20
- @server_chain ||= Shoryuken.server_middleware.dup
21
- yield @server_chain if block_given?
22
- @server_chain
20
+ @_server_chain ||= Shoryuken.server_middleware.dup
21
+ yield @_server_chain if block_given?
22
+ @_server_chain
23
23
  end
24
24
 
25
25
  def shoryuken_options(opts = {})
data/lib/shoryuken.rb CHANGED
@@ -41,8 +41,12 @@ require 'shoryuken/options'
41
41
  module Shoryuken
42
42
  extend SingleForwardable
43
43
 
44
+ def self.shoryuken_options
45
+ @_shoryuken_options ||= Shoryuken::Options.new
46
+ end
47
+
44
48
  def_delegators(
45
- :'Shoryuken::Options',
49
+ :shoryuken_options,
46
50
  :active_job?,
47
51
  :add_group,
48
52
  :groups,
@@ -59,7 +63,7 @@ module Shoryuken
59
63
  :start_callback=,
60
64
  :stop_callback,
61
65
  :stop_callback=,
62
- :active_job_queue_name_prefixing,
66
+ :active_job_queue_name_prefixing?,
63
67
  :active_job_queue_name_prefixing=,
64
68
  :sqs_client,
65
69
  :sqs_client=,
@@ -78,8 +82,9 @@ module Shoryuken
78
82
  :on_start,
79
83
  :on_stop,
80
84
  :on,
81
- :cache_visiblity_timeout?,
82
- :cache_visiblity_timeout=
85
+ :cache_visibility_timeout?,
86
+ :cache_visibility_timeout=,
87
+ :delay
83
88
  )
84
89
  end
85
90
 
data/shoryuken.gemspec CHANGED
@@ -17,7 +17,6 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.add_development_dependency 'bundler', '~> 1.6'
21
20
  spec.add_development_dependency 'dotenv'
22
21
  spec.add_development_dependency 'pry-byebug'
23
22
  spec.add_development_dependency 'rake'
@@ -4,7 +4,7 @@ require 'active_job'
4
4
  RSpec.describe Shoryuken::EnvironmentLoader do
5
5
  subject { described_class.new({}) }
6
6
 
7
- describe '#parse_queues' do
7
+ describe '#parse_queues loads default queues' do
8
8
  before do
9
9
  allow(subject).to receive(:load_rails)
10
10
  allow(subject).to receive(:prefix_active_job_queue_names)
@@ -22,6 +22,29 @@ RSpec.describe Shoryuken::EnvironmentLoader do
22
22
  end
23
23
  end
24
24
 
25
+ describe '#parse_queues includes delay per groups' do
26
+ before do
27
+ allow(subject).to receive(:load_rails)
28
+ allow(subject).to receive(:prefix_active_job_queue_names)
29
+ allow(subject).to receive(:require_workers)
30
+ allow(subject).to receive(:validate_queues)
31
+ allow(subject).to receive(:validate_workers)
32
+ allow(subject).to receive(:patch_deprecated_workers)
33
+ end
34
+
35
+ specify do
36
+ Shoryuken.options[:queues] = ['queue1', 'queue2'] # default queues
37
+ Shoryuken.options[:groups] = [[ 'custom', { queues: ['queue3'], delay: 25 }]]
38
+ subject.load
39
+
40
+ expect(Shoryuken.groups['default'][:queues]).to eq(%w[queue1 queue2])
41
+ expect(Shoryuken.groups['default'][:delay]).to eq(Shoryuken.options[:delay])
42
+ expect(Shoryuken.groups['custom'][:queues]).to eq(%w[queue3])
43
+ expect(Shoryuken.groups['custom'][:delay]).to eq(25)
44
+ end
45
+ end
46
+
47
+
25
48
  describe '#prefix_active_job_queue_names' do
26
49
  before do
27
50
  allow(subject).to receive(:load_rails)
@@ -103,9 +103,9 @@ RSpec.describe Shoryuken::Fetcher do
103
103
 
104
104
  context 'when FIFO' do
105
105
  let(:limit) { 10 }
106
- let(:queue) { instance_double('Shoryuken::Queue', fifo?: true) }
106
+ let(:queue) { instance_double('Shoryuken::Queue', fifo?: true, name: queue_name) }
107
107
 
108
- it 'polls one message at the time' do
108
+ it 'polls one message at a time' do
109
109
  # see https://github.com/phstc/shoryuken/pull/530
110
110
 
111
111
  allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
@@ -115,6 +115,21 @@ RSpec.describe Shoryuken::Fetcher do
115
115
 
116
116
  subject.fetch(queue_config, limit)
117
117
  end
118
+
119
+ context 'with batch=true' do
120
+ it 'polls the provided limit' do
121
+ # see https://github.com/phstc/shoryuken/pull/530
122
+
123
+ allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
124
+ allow(Shoryuken.worker_registry).to receive(:batch_receive_messages?).with(queue.name).and_return(true)
125
+
126
+ expect(queue).to receive(:receive_messages).with(
127
+ max_number_of_messages: limit, attribute_names: ['All'], message_attribute_names: ['All']
128
+ ).and_return([])
129
+
130
+ subject.fetch(queue_config, limit)
131
+ end
132
+ end
118
133
  end
119
134
  end
120
135
  end
@@ -72,11 +72,27 @@ RSpec.describe Shoryuken::Manager do
72
72
  expect(fetcher).to receive(:fetch).with(q, concurrency).and_return(messages)
73
73
  expect(subject).to receive(:fire_event).with(:dispatch, false, queue_name: q.name)
74
74
  expect(Shoryuken::Processor).to receive(:process).with(q, message)
75
- expect(Shoryuken.logger).to_not receive(:info)
75
+ expect(Shoryuken.logger).to receive(:info).never
76
76
 
77
77
  subject.send(:dispatch)
78
78
  end
79
79
 
80
+ context 'and there are no messages in the queue' do
81
+ specify do
82
+ messages = %w[]
83
+ q = Shoryuken::Polling::QueueConfiguration.new(queue, {})
84
+
85
+ expect(fetcher).to receive(:fetch).with(q, concurrency).and_return(messages)
86
+ expect(subject).to receive(:fire_event).with(:dispatch, false, queue_name: q.name)
87
+ expect(polling_strategy).to receive(:messages_found).with(q.name, 0)
88
+ expect(Shoryuken.logger).to receive(:info).never
89
+ expect(Shoryuken::Processor).to receive(:process).never
90
+ expect_any_instance_of(described_class).to receive(:assign).never
91
+
92
+ subject.send(:dispatch)
93
+ end
94
+ end
95
+
80
96
  context 'when batch' do
81
97
  specify do
82
98
  messages = %w[test1 test2 test3]
@@ -86,10 +102,27 @@ RSpec.describe Shoryuken::Manager do
86
102
  expect(subject).to receive(:fire_event).with(:dispatch, false, queue_name: q.name)
87
103
  allow(subject).to receive(:batched_queue?).with(q).and_return(true)
88
104
  expect(Shoryuken::Processor).to receive(:process).with(q, messages)
89
- expect(Shoryuken.logger).to_not receive(:info)
105
+ expect(Shoryuken.logger).to receive(:info).never
90
106
 
91
107
  subject.send(:dispatch)
92
108
  end
109
+
110
+ context 'and there are no messages in the queue' do
111
+ specify do
112
+ messages = %w[]
113
+ q = Shoryuken::Polling::QueueConfiguration.new(queue, {})
114
+
115
+ expect(fetcher).to receive(:fetch).with(q, described_class::BATCH_LIMIT).and_return(messages)
116
+ expect(subject).to receive(:fire_event).with(:dispatch, false, queue_name: q.name)
117
+ allow(subject).to receive(:batched_queue?).with(q).and_return(true)
118
+ expect(polling_strategy).to receive(:messages_found).with(q.name, 0)
119
+ expect(Shoryuken.logger).to receive(:info).never
120
+ expect(Shoryuken::Processor).to receive(:process).never
121
+ expect_any_instance_of(described_class).to receive(:assign).never
122
+
123
+ subject.send(:dispatch)
124
+ end
125
+ end
93
126
  end
94
127
  end
95
128
 
@@ -53,7 +53,7 @@ RSpec.describe Shoryuken::Middleware::Server::AutoExtendVisibility do
53
53
  allow(sqs_msg).to receive(:queue) { sqs_queue }
54
54
  expect(sqs_msg).to_not receive(:change_visibility)
55
55
 
56
- expect { Runner.new.run_and_raise(TestWorker.new, queue, sqs_msg) }.to raise_error
56
+ expect { Runner.new.run_and_raise(TestWorker.new, queue, sqs_msg) }.to raise_error(RuntimeError)
57
57
  end
58
58
 
59
59
  it 'does not extend message visibility if auto_visibility_timeout is not true' do
@@ -1,19 +1,37 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Shoryuken::Options do
4
- describe '.add_group' do
4
+ subject { Shoryuken.shoryuken_options }
5
+
6
+ describe '.add_group adds queues and optional delay' do
5
7
  before do
6
8
  Shoryuken.groups.clear
7
9
  Shoryuken.add_group('group1', 25)
8
10
  Shoryuken.add_group('group2', 25)
11
+ Shoryuken.add_group('group3', 25, delay: 5)
12
+ end
13
+
14
+ specify do
15
+ subject.add_queue('queue1', 1, 'group1')
16
+ subject.add_queue('queue2', 2, 'group2')
17
+ subject.add_queue('queue3', 1, 'group3')
18
+
19
+ expect(subject.groups['group1'][:queues]).to eq(%w[queue1])
20
+ expect(subject.groups['group2'][:queues]).to eq(%w[queue2 queue2])
21
+ expect(subject.groups['group3'][:queues]).to eq(%w[queue3])
22
+ expect(subject.groups['group3'][:delay]).to eq(5)
9
23
  end
24
+ end
10
25
 
26
+ describe '.delay works for each group' do
11
27
  specify do
12
- described_class.add_queue('queue1', 1, 'group1')
13
- described_class.add_queue('queue2', 2, 'group2')
28
+ Shoryuken.add_group('group1', 25)
29
+ Shoryuken.add_group('group2', 25, delay: 5)
30
+ subject.add_queue('queue1', 1, 'group1')
31
+ subject.add_queue('queue2', 2, 'group2')
14
32
 
15
- expect(described_class.groups['group1'][:queues]).to eq(%w[queue1])
16
- expect(described_class.groups['group2'][:queues]).to eq(%w[queue2 queue2])
33
+ expect(subject.delay('group1')).to eq(Shoryuken.options[:delay])
34
+ expect(subject.delay('group2')).to eq(5.0)
17
35
  end
18
36
  end
19
37
 
@@ -25,10 +43,10 @@ RSpec.describe Shoryuken::Options do
25
43
  end
26
44
 
27
45
  specify do
28
- described_class.add_queue('queue1', 1, 'group1')
29
- described_class.add_queue('queue2', 2, 'group2')
46
+ subject.add_queue('queue1', 1, 'group1')
47
+ subject.add_queue('queue2', 2, 'group2')
30
48
 
31
- expect(described_class.ungrouped_queues).to eq(%w[queue1 queue2 queue2])
49
+ expect(subject.ungrouped_queues).to eq(%w[queue1 queue2 queue2])
32
50
  end
33
51
  end
34
52
 
@@ -52,22 +70,22 @@ RSpec.describe Shoryuken::Options do
52
70
 
53
71
  describe '.register_worker' do
54
72
  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])
73
+ subject.worker_registry.clear
74
+ subject.register_worker('default', TestWorker)
75
+ expect(subject.worker_registry.workers('default')).to eq([TestWorker])
58
76
  end
59
77
 
60
78
  it 'registers a batchable worker' do
61
- described_class.worker_registry.clear
79
+ subject.worker_registry.clear
62
80
  TestWorker.get_shoryuken_options['batch'] = true
63
- described_class.register_worker('default', TestWorker)
64
- expect(described_class.worker_registry.workers('default')).to eq([TestWorker])
81
+ subject.register_worker('default', TestWorker)
82
+ expect(subject.worker_registry.workers('default')).to eq([TestWorker])
65
83
  end
66
84
 
67
85
  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])
86
+ subject.worker_registry.clear
87
+ subject.register_worker('default', TestWorker)
88
+ expect(subject.worker_registry.workers('default')).to eq([TestWorker])
71
89
 
72
90
  class Test2Worker
73
91
  include Shoryuken::Worker
@@ -77,13 +95,13 @@ RSpec.describe Shoryuken::Options do
77
95
  def perform(sqs_msg, body); end
78
96
  end
79
97
 
80
- expect(described_class.worker_registry.workers('default')).to eq([Test2Worker])
98
+ expect(subject.worker_registry.workers('default')).to eq([Test2Worker])
81
99
  end
82
100
 
83
101
  it 'raises an exception when mixing batchable with non batchable' do
84
- described_class.worker_registry.clear
102
+ subject.worker_registry.clear
85
103
  TestWorker.get_shoryuken_options['batch'] = true
86
- described_class.register_worker('default', TestWorker)
104
+ subject.register_worker('default', TestWorker)
87
105
 
88
106
  expect {
89
107
  class BatchableWorker
@@ -86,6 +86,14 @@ RSpec.describe Shoryuken::Polling::StrictPriority do
86
86
  end
87
87
  end
88
88
 
89
+ describe '#delay' do
90
+ it 'sets delay based on group' do
91
+ delay_polling = Shoryuken::Polling::StrictPriority.new(queues, 25)
92
+ expect(delay_polling.delay).to eq(25.0)
93
+ expect(subject.delay).to eq(1.0)
94
+ end
95
+ end
96
+
89
97
  describe '#messages_found' do
90
98
  it 'pauses a queue if there are no messages found' do
91
99
  # [shoryuken, 2]
@@ -96,4 +96,12 @@ RSpec.describe Shoryuken::Polling::WeightedRoundRobin do
96
96
  expect(subject.instance_variable_get(:@queues)).to eq([queue1, queue2, queue1])
97
97
  end
98
98
  end
99
+
100
+ describe '#delay' do
101
+ it 'sets delay based on group' do
102
+ delay_polling = Shoryuken::Polling::WeightedRoundRobin.new(queues, 25)
103
+ expect(delay_polling.delay).to eq(25.0)
104
+ expect(subject.delay).to eq(1.0)
105
+ end
106
+ end
99
107
  end
@@ -57,8 +57,8 @@ RSpec.describe Shoryuken::Processor do
57
57
 
58
58
  context 'server' do
59
59
  before do
60
- allow(Shoryuken::Options).to receive(:server?).and_return(true)
61
- WorkerCalledMiddlewareWorker.instance_variable_set(:@server_chain, nil) # un-memoize middleware
60
+ allow_any_instance_of(Shoryuken::Options).to receive(:server?).and_return(true)
61
+ WorkerCalledMiddlewareWorker.instance_variable_set(:@_server_chain, nil) # un-memoize middleware
62
62
 
63
63
  Shoryuken.configure_server do |config|
64
64
  config.server_middleware do |chain|
@@ -85,8 +85,8 @@ RSpec.describe Shoryuken::Processor do
85
85
 
86
86
  context 'client' do
87
87
  before do
88
- allow(Shoryuken).to receive(:server?).and_return(false)
89
- WorkerCalledMiddlewareWorker.instance_variable_set(:@server_chain, nil) # un-memoize middleware
88
+ allow_any_instance_of(Shoryuken::Options).to receive(:server?).and_return(false)
89
+ WorkerCalledMiddlewareWorker.instance_variable_set(:@_server_chain, nil) # un-memoize middleware
90
90
 
91
91
  Shoryuken.configure_server do |config|
92
92
  config.server_middleware do |chain|
@@ -26,6 +26,19 @@ RSpec.describe Shoryuken::Queue do
26
26
  end
27
27
  end
28
28
 
29
+ context 'when a non SQS queue URL' do
30
+ let(:queue_url) { "http://localhost:4576/queue/#{queue_name}" }
31
+
32
+ it 'instantiates by URL and validate the URL' do
33
+ # See https://github.com/phstc/shoryuken/pull/551
34
+ expect_any_instance_of(described_class).to receive(:fifo?).and_return(false)
35
+
36
+ subject = described_class.new(sqs, queue_url)
37
+
38
+ expect(subject.name).to eq(queue_name)
39
+ end
40
+ end
41
+
29
42
  context 'when queue name supplied' do
30
43
  subject { described_class.new(sqs, queue_name) }
31
44
 
@@ -235,7 +248,7 @@ RSpec.describe Shoryuken::Queue do
235
248
  end
236
249
  end
237
250
 
238
- describe '#visiblity_timeout' do
251
+ describe '#visibility_timeout' do
239
252
  let(:attribute_response) { double 'Aws::SQS::Types::GetQueueAttributesResponse' }
240
253
 
241
254
  before do
@@ -247,7 +260,7 @@ RSpec.describe Shoryuken::Queue do
247
260
 
248
261
  context 'when cache is disabled' do
249
262
  specify do
250
- Shoryuken.cache_visiblity_timeout = false
263
+ Shoryuken.cache_visibility_timeout = false
251
264
 
252
265
  expect(sqs).to(
253
266
  receive(:get_queue_attributes).with(queue_url: queue_url, attribute_names: ['All']).and_return(attribute_response).exactly(3).times
@@ -260,7 +273,7 @@ RSpec.describe Shoryuken::Queue do
260
273
 
261
274
  context 'when cache is enabled' do
262
275
  it 'memoizes response' do
263
- Shoryuken.cache_visiblity_timeout = true
276
+ Shoryuken.cache_visibility_timeout = true
264
277
 
265
278
  expect(sqs).to(
266
279
  receive(:get_queue_attributes).with(queue_url: queue_url, attribute_names: ['All']).and_return(attribute_response).once
data/spec/spec_helper.rb CHANGED
@@ -43,7 +43,7 @@ RSpec.configure do |config|
43
43
  Shoryuken.groups.clear
44
44
 
45
45
  Shoryuken.options[:concurrency] = 1
46
- Shoryuken.options[:delay] = 1
46
+ Shoryuken.options[:delay] = 1.0
47
47
  Shoryuken.options[:timeout] = 1
48
48
  Shoryuken.options[:daemon] = nil
49
49
  Shoryuken.options[:logfile] = nil
@@ -61,7 +61,7 @@ RSpec.configure do |config|
61
61
 
62
62
  Shoryuken.sqs_client_receive_message_opts.clear
63
63
 
64
- Shoryuken.cache_visiblity_timeout = false
64
+ Shoryuken.cache_visibility_timeout = false
65
65
 
66
66
  allow(Concurrent).to receive(:global_io_executor).and_return(Concurrent::ImmediateExecutor.new)
67
67
  allow(Shoryuken).to receive(:active_job?).and_return(false)
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoryuken
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Cantero
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-21 00:00:00.000000000 Z
11
+ date: 2019-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.6'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.6'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: dotenv
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -228,7 +214,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
228
214
  version: '0'
229
215
  requirements: []
230
216
  rubyforge_project:
231
- rubygems_version: 2.5.2
217
+ rubygems_version: 2.7.6.2
232
218
  signing_key:
233
219
  specification_version: 4
234
220
  summary: Shoryuken is a super efficient AWS SQS thread based message processor