shoryuken 2.1.1 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 767009617d3e6954d4b1bec86f33811fdfd6c4e0
4
- data.tar.gz: 578e87e222d8f980a72981f75ae57a7ad6a5275f
3
+ metadata.gz: fd50ddb123d1c14d3ff0a0ac562c3e156afd2dfd
4
+ data.tar.gz: 9bcd1a88456f1d8454609d7440d209bc78b5a89b
5
5
  SHA512:
6
- metadata.gz: 6c8493dc99b8b11ee02e7c5ae1d51acf499e60b0ba1b94c2202b40d7e36f4ea3d1ea03ab9f5d2570e7943d87f4cd2b0c54940c62b12c08903f83584967897203
7
- data.tar.gz: b04c12cd3ef6efe7ac4ca4a8ab3f6011bee687aa5053db2ed0fd03205d32e76a3cd45809a4aa8e75e8b7f50d311334dd43aa698cc25e366019ddc9d932bd8a94
6
+ metadata.gz: 64680cf91fbf51372720f872f0ac7cced4d42e2b86c61b66137fd5993f1e58ca2ab86c907ff49eb8c464817c5c4efe53ea75e234a4ee01a4d558164535086c46
7
+ data.tar.gz: 75fbaab18cd22e600ccd6e705eef80532faf0236a434803cf0678dc14cf6b7fac30e40d0350e8be2b47eb8dd54289667eeccb12a49af0febbcafc0d5594e05f5
data/.codeclimate.yml CHANGED
@@ -1,8 +1,6 @@
1
1
  ---
2
2
  engines:
3
- brakeman:
4
- enabled: true
5
- bundler-audit:
3
+ reek:
6
4
  enabled: true
7
5
  duplication:
8
6
  enabled: true
@@ -11,13 +9,10 @@ engines:
11
9
  - ruby
12
10
  fixme:
13
11
  enabled: true
14
- reek:
15
- enabled: true
16
12
  rubocop:
17
13
  enabled: true
18
14
  ratings:
19
15
  paths:
20
16
  - "**.rb"
21
- # exclude_paths:
22
- # - "**/vendor/**/*"
23
- # - "*/spec/**/*"
17
+ exclude_paths:
18
+ - spec/
data/.travis.yml CHANGED
@@ -14,3 +14,7 @@ before_install:
14
14
  - gem update bundler
15
15
  after_success:
16
16
  - bundle exec codeclimate-test-reporter
17
+
18
+ addons:
19
+ code_climate:
20
+ repo_token: 7709fd21981bb9d2658647a66d959415a1029a83f1c199573828797944f26c52
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [v2.1.2] - 2016-12-22
2
+ - Fix loading `logfile` from shoryuken.yml
3
+ - [#296](https://github.com/phstc/shoryuken/pull/296)
4
+
5
+ - Add support for Strict priority polling (pending documentation)
6
+ - [#288](https://github.com/phstc/shoryuken/pull/288)
7
+
8
+ - Add `test_workers` for end-to-end testing supporting
9
+ - [#286](https://github.com/phstc/shoryuken/pull/286)
10
+
11
+ - Update README documenting `configure_client` and `configure_server`
12
+ - [#283](https://github.com/phstc/shoryuken/pull/283)
13
+
14
+ - Fix memory leak caused by async tracking busy threads
15
+ - [#289](https://github.com/phstc/shoryuken/pull/289)
16
+
17
+ - Refactor fetcher, polling strategy and manager
18
+ - [#284](https://github.com/phstc/shoryuken/pull/284)
19
+
1
20
  ## [v2.1.1] - 2016-12-05
2
21
  - Fix aws deprecation warning message
3
22
  - [#279](https://github.com/phstc/shoryuken/pull/279)
data/README.md CHANGED
@@ -110,15 +110,6 @@ end
110
110
  Sample configuration file `shoryuken.yml`.
111
111
 
112
112
  ```yaml
113
- aws:
114
- access_key_id: ... # or <%= ENV['AWS_ACCESS_KEY_ID'] %>
115
- secret_access_key: ... # or <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
116
- region: us-east-1 # or <%= ENV['AWS_REGION'] %>
117
- receive_message: # See http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#receive_message-instance_method
118
- # wait_time_seconds: N # The number of seconds to wait for new messages when polling. Defaults to the #wait_time_seconds defined on the queue
119
- attribute_names:
120
- - ApproximateReceiveCount
121
- - SentTimestamp
122
113
  concurrency: 25 # The number of allocated threads to process messages. Default 25
123
114
  delay: 25 # The delay in seconds to pause a queue when it's empty. Default 0
124
115
  queues:
@@ -127,24 +118,80 @@ queues:
127
118
  - [low_priority, 1]
128
119
  ```
129
120
 
130
- The ```aws``` section is used to configure both the Aws objects used by Shoryuken internally, and also to set up some Shoryuken-specific config. The Shoryuken-specific keys are listed below, and you can expect any other key defined in that block to be passed on untouched to ```Aws::SQS::Client#initialize```:
121
+ And setup ```aws``` options to use ```configure_client``` in `config/initializers/shoryuken.rb`:
131
122
 
132
- - ```account_id``` is used when generating SNS ARNs
133
- - ```sns_endpoint``` can be used to explicitly override the SNS endpoint
134
- - ```sqs_endpoint``` can be used to explicitly override the SQS endpoint
135
- - ```receive_message``` can be used to define the options passed to the http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#receive_message-instance_method
123
+ ```ruby
124
+ Shoryuken.configure_client do |config|
125
+ config.aws = {
126
+ secret_access_key: ..., # or ENV["AWS_SECRET_ACCESS_KEY"]
127
+ access_key_id: ..., # or ENV["AWS_ACCESS_KEY_ID"]
128
+ region: "us-east-1", # or ENV["AWS_REGION"]
129
+ receive_message: { # See http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#receive_message-instance_method
130
+ # wait_time_seconds: N, # The number of seconds to wait for new messages when polling. Defaults to the #wait_time_seconds defined on the queue
131
+ attribute_names: [
132
+ "ApproximateReceiveCount",
133
+ "SentTimestamp"
134
+ ]
135
+ }
136
+ }
137
+ end
138
+ ```
139
+
140
+ If you use Shoryuken with plain ruby worker class (not Rails), please call `configure_client` at the beginning of the worker file:
141
+
142
+ ```ruby
143
+ Shoryuken.configure_client do |config|
144
+ config.aws = {
145
+ secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
146
+ access_key_id: ENV["AWS_ACCESS_KEY_ID"],
147
+ region: ENV["AWS_REGION"]
148
+ }
149
+ end
150
+
151
+ class MyWorker
152
+ end
153
+ ```
136
154
 
137
- The ```sns_endpoint``` and ```sqs_endpoint``` Shoryuken-specific options will also fallback to the environment variables ```AWS_SNS_ENDPOINT``` and ```AWS_SQS_ENDPOINT``` respectively, if they are set.
155
+ The `aws` section is used to configure both the Aws objects used by Shoryuken internally, and also to set up some Shoryuken-specific config. The Shoryuken-specific keys are listed below, and you can expect any other key defined in that block to be passed on untouched to `Aws::SQS::Client#initialize`:
156
+
157
+ - `account_id` is used when generating SNS ARNs
158
+ - `sns_endpoint` can be used to explicitly override the SNS endpoint
159
+ - `sqs_endpoint` can be used to explicitly override the SQS endpoint
160
+ - `receive_message` can be used to define the options passed to the http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#receive_message-instance_method
161
+
162
+ The `sns_endpoint` and `sqs_endpoint` Shoryuken-specific options will also fallback to the environment variables `AWS_SNS_ENDPOINT` and `AWS_SQS_ENDPOINT` respectively, if they are set.
138
163
 
139
164
  ### Configuration (producer side)
140
165
 
141
166
  'Producer' processes need permissions to put messages into SQS. There are a few ways:
142
167
 
168
+ * Use the `configure_server` in Rails initializer
143
169
  * Ensure the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` env vars are set.
144
170
  * Create a `~/.aws/credentials` file.
145
171
  * Set `Aws.config[:credentials]` from Ruby code (e.g. in a Rails initializer)
146
172
  * Use the Instance Profiles feature. The IAM role of the targeted machine must have an adequate SQS Policy.
147
173
 
174
+ For example, use the `configure_server` in `config/initializers/shoryuken.rb`:
175
+
176
+ ```ruby
177
+ Shoryuken.configure_client do |config|
178
+ config.aws = {
179
+ secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
180
+ access_key_id: ENV["AWS_ACCESS_KEY_ID"],
181
+ region: ENV["AWS_REGION"]
182
+ }
183
+ end
184
+
185
+ Shoryuken.configure_server do |config|
186
+ config.aws = {
187
+ secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
188
+ access_key_id: ENV["AWS_ACCESS_KEY_ID"],
189
+ region: ENV["AWS_REGION"]
190
+ }
191
+ end
192
+ ```
193
+
194
+
148
195
  Note that storing your credentials into Amazon instances represents a security risk. Instance Profiles tends to be the best choice.
149
196
 
150
197
  You can read about these in more detail [here](http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html).
data/lib/shoryuken.rb CHANGED
@@ -21,6 +21,7 @@ require 'shoryuken/middleware/server/exponential_backoff_retry'
21
21
  require 'shoryuken/middleware/server/timing'
22
22
  require 'shoryuken/sns_arn'
23
23
  require 'shoryuken/topic'
24
+ require 'shoryuken/polling'
24
25
 
25
26
  module Shoryuken
26
27
  DEFAULTS = {
@@ -33,7 +34,8 @@ module Shoryuken
33
34
  startup: [],
34
35
  quiet: [],
35
36
  shutdown: [],
36
- }
37
+ },
38
+ polling_strategy: Polling::WeightedRoundRobin,
37
39
  }
38
40
 
39
41
  @@queues = []
@@ -60,8 +60,8 @@ module Shoryuken
60
60
  end
61
61
 
62
62
  def initialize_logger
63
- Shoryuken::Logging.initialize_logger(options[:logfile]) if options[:logfile]
64
- Shoryuken.logger.level = Logger::DEBUG if options[:verbose]
63
+ Shoryuken::Logging.initialize_logger(Shoryuken.options[:logfile]) if Shoryuken.options[:logfile]
64
+ Shoryuken.logger.level = Logger::DEBUG if Shoryuken.options[:verbose]
65
65
  end
66
66
 
67
67
  def load_rails
@@ -1,26 +1,9 @@
1
1
  module Shoryuken
2
2
  class Fetcher
3
- include Celluloid
4
3
  include Util
5
4
 
6
5
  FETCH_LIMIT = 10
7
6
 
8
- def initialize(manager)
9
- @manager = manager
10
- end
11
-
12
- def receive_messages(queue, limit)
13
- # AWS limits the batch size by 10
14
- limit = limit > FETCH_LIMIT ? FETCH_LIMIT : limit
15
-
16
- options = (Shoryuken::AwsConfig.options[:receive_message] || {}).dup
17
- options[:max_number_of_messages] = limit
18
- options[:message_attribute_names] = %w(All)
19
- options[:attribute_names] = %w(All)
20
-
21
- Shoryuken::Client.queues(queue).receive_messages options
22
- end
23
-
24
7
  def fetch(queue, available_processors)
25
8
  watchdog('Fetcher#fetch died') do
26
9
  started_at = Time.now
@@ -28,46 +11,34 @@ module Shoryuken
28
11
  logger.debug { "Looking for new messages in '#{queue}'" }
29
12
 
30
13
  begin
31
- batch = Shoryuken.worker_registry.batch_receive_messages?(queue)
32
- limit = batch ? FETCH_LIMIT : available_processors
33
-
34
- if (sqs_msgs = Array(receive_messages(queue, limit))).any?
35
- logger.debug { "Found #{sqs_msgs.size} messages for '#{queue}'" }
36
-
37
- if batch
38
- @manager.async.assign(queue, patch_sqs_msgs!(sqs_msgs))
39
- else
40
- sqs_msgs.each { |sqs_msg| @manager.async.assign(queue, sqs_msg) }
41
- end
42
-
43
- @manager.async.rebalance_queue_weight!(queue)
44
- else
45
- logger.debug { "No message found for '#{queue}'" }
46
-
47
- @manager.async.pause_queue!(queue)
48
- end
14
+ limit = available_processors > FETCH_LIMIT ? FETCH_LIMIT : available_processors
49
15
 
16
+ sqs_msgs = Array(receive_messages(queue, limit))
17
+ logger.info { "Found #{sqs_msgs.size} messages for '#{queue.name}'" }
50
18
  logger.debug { "Fetcher for '#{queue}' completed in #{elapsed(started_at)} ms" }
19
+ sqs_msgs
51
20
  rescue => ex
52
21
  logger.error { "Error fetching message: #{ex}" }
53
22
  logger.error { ex.backtrace.first }
23
+ []
54
24
  end
55
-
56
- @manager.async.dispatch
57
25
  end
58
-
59
26
  end
60
27
 
61
28
  private
62
29
 
63
- def patch_sqs_msgs!(sqs_msgs)
64
- sqs_msgs.instance_eval do
65
- def message_id
66
- "batch-with-#{size}-messages"
67
- end
68
- end
30
+ def receive_messages(queue, limit)
31
+ # AWS limits the batch size by 10
32
+ limit = limit > FETCH_LIMIT ? FETCH_LIMIT : limit
33
+
34
+ options = (Shoryuken.options[:aws][:receive_message] || {}).dup
35
+ options[:max_number_of_messages] = limit
36
+ options[:message_attribute_names] = %w(All)
37
+ options[:attribute_names] = %w(All)
38
+
39
+ options.merge!(queue.options)
69
40
 
70
- sqs_msgs
41
+ Shoryuken::Client.queues(queue.name).receive_messages(options)
71
42
  end
72
43
  end
73
44
  end
@@ -10,17 +10,16 @@ module Shoryuken
10
10
  def initialize
11
11
  @condvar = Celluloid::Condition.new
12
12
  @manager = Shoryuken::Manager.new_link(@condvar)
13
- @fetcher = Shoryuken::Fetcher.new_link(manager)
14
13
 
15
14
  @done = false
16
15
 
17
- manager.fetcher = @fetcher
16
+ manager.fetcher = Shoryuken::Fetcher.new
17
+ manager.polling_strategy = Shoryuken.options[:polling_strategy].new(Shoryuken.queues)
18
18
  end
19
19
 
20
20
  def stop(options = {})
21
21
  watchdog('Launcher#stop') do
22
22
  @done = true
23
- @fetcher.terminate if @fetcher.alive?
24
23
 
25
24
  manager.async.stop(shutdown: !!options[:shutdown], timeout: Shoryuken.options[:timeout])
26
25
  @condvar.wait
@@ -7,9 +7,14 @@ module Shoryuken
7
7
  include Util
8
8
 
9
9
  attr_accessor :fetcher
10
+ attr_accessor :polling_strategy
11
+
12
+ exclusive :dispatch
10
13
 
11
14
  trap_exit :processor_died
12
15
 
16
+ BATCH_LIMIT = 10
17
+
13
18
  def initialize(condvar)
14
19
  @count = Shoryuken.options[:concurrency] || 25
15
20
  raise(ArgumentError, "Concurrency value #{@count} is invalid, it needs to be a positive number") unless @count > 0
@@ -18,9 +23,9 @@ module Shoryuken
18
23
 
19
24
  @done = false
20
25
 
21
- @busy = []
22
- @ready = @count.times.map { build_processor }
23
- @threads = {}
26
+ @busy_processors = []
27
+ @busy_threads = {}
28
+ @ready_processors = @count.times.map { build_processor }
24
29
  end
25
30
 
26
31
  def start
@@ -40,16 +45,14 @@ module Shoryuken
40
45
 
41
46
  fire_event(:shutdown, true)
42
47
 
43
- @fetcher.terminate if @fetcher.alive?
48
+ logger.info { "Shutting down #{@ready_processors.size} quiet workers" }
44
49
 
45
- logger.info { "Shutting down #{@ready.size} quiet workers" }
46
-
47
- @ready.each do |processor|
50
+ @ready_processors.each do |processor|
48
51
  processor.terminate if processor.alive?
49
52
  end
50
- @ready.clear
53
+ @ready_processors.clear
51
54
 
52
- return after(0) { @finished.signal } if @busy.empty?
55
+ return after(0) { @finished.signal } if @busy_processors.empty?
53
56
 
54
57
  if options[:shutdown]
55
58
  hard_shutdown_in(options[:timeout])
@@ -63,14 +66,15 @@ module Shoryuken
63
66
  watchdog('Manager#processor_done died') do
64
67
  logger.debug { "Process done for '#{queue}'" }
65
68
 
66
- @threads.delete(processor.object_id)
67
- @busy.delete processor
69
+ @busy_processors.delete(processor)
70
+ @busy_threads.delete(processor.object_id)
68
71
 
69
72
  if stopped?
70
73
  processor.terminate if processor.alive?
71
- return after(0) { @finished.signal } if @busy.empty?
74
+ return after(0) { @finished.signal } if @busy_processors.empty?
72
75
  else
73
- @ready << processor
76
+ @ready_processors << processor
77
+ async.dispatch
74
78
  end
75
79
  end
76
80
  end
@@ -79,13 +83,14 @@ module Shoryuken
79
83
  watchdog("Manager#processor_died died") do
80
84
  logger.error { "Process died, reason: #{reason}" }
81
85
 
82
- @threads.delete(processor.object_id)
83
- @busy.delete processor
86
+ @busy_processors.delete(processor)
87
+ @busy_threads.delete(processor.object_id)
84
88
 
85
89
  if stopped?
86
- return after(0) { @finished.signal } if @busy.empty?
90
+ return after(0) { @finished.signal } if @busy_processors.empty?
87
91
  else
88
- @ready << build_processor
92
+ @ready_processors << build_processor
93
+ async.dispatch
89
94
  end
90
95
  end
91
96
  end
@@ -94,61 +99,27 @@ module Shoryuken
94
99
  @done
95
100
  end
96
101
 
97
- def assign(queue, sqs_msg)
98
- watchdog('Manager#assign died') do
99
- logger.debug { "Assigning #{sqs_msg.message_id}" }
100
-
101
- processor = @ready.pop
102
- @busy << processor
103
-
104
- processor.async.process(queue, sqs_msg)
105
- end
106
- end
107
-
108
- def rebalance_queue_weight!(queue)
109
- watchdog('Manager#rebalance_queue_weight! died') do
110
- if (original = original_queue_weight(queue)) > (current = current_queue_weight(queue))
111
- logger.info { "Increasing '#{queue}' weight to #{current + 1}, max: #{original}" }
112
-
113
- @queues << queue
114
- end
115
- end
116
- end
117
-
118
- def pause_queue!(queue)
119
- return if !@queues.include?(queue) || Shoryuken.options[:delay].to_f <= 0
120
-
121
- logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because it's empty" }
122
-
123
- @queues.delete(queue)
124
-
125
- after(Shoryuken.options[:delay].to_f) { async.restart_queue!(queue) }
126
- end
127
-
128
-
129
102
  def dispatch
130
103
  return if stopped?
131
104
 
132
- logger.debug { "Ready: #{@ready.size}, Busy: #{@busy.size}, Active Queues: #{unparse_queues(@queues)}" }
105
+ logger.debug { "Ready: #{@ready_processors.size}, Busy: #{@busy_processors.size}, Active Queues: #{polling_strategy.active_queues}" }
133
106
 
134
- if @ready.empty?
107
+ if @ready_processors.empty?
135
108
  logger.debug { 'Pausing fetcher, because all processors are busy' }
136
-
137
109
  dispatch_later
138
110
  return
139
111
  end
140
112
 
141
- if (queue = next_queue)
142
- @fetcher.async.fetch(queue, @ready.size)
143
- else
113
+ queue = polling_strategy.next_queue
114
+ if queue.nil?
144
115
  logger.debug { 'Pausing fetcher, because all queues are paused' }
145
-
146
- @fetcher_paused = true
116
+ dispatch_later
117
+ return
147
118
  end
148
- end
149
119
 
150
- def real_thread(proxy_id, thr)
151
- @threads[proxy_id] = thr
120
+ batched_queue?(queue) ? dispatch_batch(queue) : dispatch_single_messages(queue)
121
+
122
+ async.dispatch
152
123
  end
153
124
 
154
125
  private
@@ -160,67 +131,48 @@ module Shoryuken
160
131
  end
161
132
  end
162
133
 
163
- def build_processor
164
- processor = Processor.new_link(current_actor)
165
- processor.proxy_id = processor.object_id
166
- processor
167
- end
168
-
169
- def restart_queue!(queue)
170
- return if stopped?
171
-
172
- unless @queues.include? queue
173
- logger.debug { "Restarting '#{queue}'" }
174
-
175
- @queues << queue
176
-
177
- if @fetcher_paused
178
- logger.debug { 'Restarting fetcher' }
134
+ def assign(queue, sqs_msg)
135
+ watchdog('Manager#assign died') do
136
+ logger.debug { "Assigning #{sqs_msg.message_id}" }
179
137
 
180
- @fetcher_paused = false
138
+ processor = @ready_processors.pop
139
+ @busy_threads[processor.object_id] = processor.running_thread
140
+ @busy_processors << processor
181
141
 
182
- dispatch
183
- end
142
+ processor.async.process(queue, sqs_msg)
184
143
  end
185
144
  end
186
145
 
187
- def current_queue_weight(queue)
188
- queue_weight(@queues, queue)
146
+ def dispatch_batch(queue)
147
+ batch = fetcher.fetch(queue, BATCH_LIMIT)
148
+ polling_strategy.messages_found(queue.name, batch.size)
149
+ assign(queue.name, patch_batch!(batch))
189
150
  end
190
151
 
191
- def original_queue_weight(queue)
192
- queue_weight(Shoryuken.queues, queue)
152
+ def dispatch_single_messages(queue)
153
+ messages = fetcher.fetch(queue, @ready_processors.size)
154
+ polling_strategy.messages_found(queue.name, messages.size)
155
+ messages.each { |message| assign(queue.name, message) }
193
156
  end
194
157
 
195
- def queue_weight(queues, queue)
196
- queues.count { |q| q == queue }
158
+ def batched_queue?(queue)
159
+ Shoryuken.worker_registry.batch_receive_messages?(queue.name)
197
160
  end
198
161
 
199
- def next_queue
200
- return nil if @queues.empty?
201
-
202
- # get/remove the first queue in the list
203
- queue = @queues.shift
204
-
205
- unless defined?(::ActiveJob) || !Shoryuken.worker_registry.workers(queue).empty?
206
- # when no worker registered pause the queue to avoid endless recursion
207
- logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because no workers registered" }
208
-
209
- after(Shoryuken.options[:delay].to_f) { async.restart_queue!(queue) }
210
-
211
- return next_queue
212
- end
213
-
214
- # add queue back to the end of the list
215
- @queues << queue
162
+ def delay
163
+ Shoryuken.options[:delay].to_f
164
+ end
216
165
 
217
- queue
166
+ def build_processor
167
+ processor = Processor.new_link(current_actor)
168
+ processor.proxy_id = processor.object_id
169
+ processor
218
170
  end
219
171
 
220
172
  def soft_shutdown(delay)
221
- logger.info { "Waiting for #{@busy.size} busy workers" }
173
+ logger.info { "Waiting for #{@busy_processors.size} busy workers" }
222
174
 
223
- if @busy.size > 0
175
+ if @busy_processors.size > 0
224
176
  after(delay) { soft_shutdown(delay) }
225
177
  else
226
178
  @finished.signal
@@ -228,16 +180,16 @@ module Shoryuken
228
180
  end
229
181
 
230
182
  def hard_shutdown_in(delay)
231
- logger.info { "Waiting for #{@busy.size} busy workers" }
183
+ logger.info { "Waiting for #{@busy_processors.size} busy workers" }
232
184
  logger.info { "Pausing up to #{delay} seconds to allow workers to finish..." }
233
185
 
234
186
  after(delay) do
235
187
  watchdog('Manager#hard_shutdown_in died') do
236
- if @busy.size > 0
237
- logger.info { "Hard shutting down #{@busy.size} busy workers" }
188
+ if @busy_processors.size > 0
189
+ logger.info { "Hard shutting down #{@busy_processors.size} busy workers" }
238
190
 
239
- @busy.each do |processor|
240
- if processor.alive? && t = @threads.delete(processor.object_id)
191
+ @busy_processors.each do |processor|
192
+ if processor.alive? && t = @busy_threads.delete(processor.object_id)
241
193
  t.raise Shutdown
242
194
  end
243
195
  end
@@ -247,5 +199,15 @@ module Shoryuken
247
199
  end
248
200
  end
249
201
  end
202
+
203
+ def patch_batch!(sqs_msgs)
204
+ sqs_msgs.instance_eval do
205
+ def message_id
206
+ "batch-with-#{size}-messages"
207
+ end
208
+ end
209
+
210
+ sqs_msgs
211
+ end
250
212
  end
251
213
  end