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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c88cb34dfa2eae0fbeea81ff493f88c389232edb
4
- data.tar.gz: b271d7d9015868d1d5b344d4f338c3e603e57b50
3
+ metadata.gz: 779c3e9c7f708f0a6dccb3e8b0c9f7e2490b25e1
4
+ data.tar.gz: bf260a2793ff8c07ddfb670832ba5f12af8d85d9
5
5
  SHA512:
6
- metadata.gz: 2a7a0ba02ee69d37ee8df087c9c260443cdb9559a48bd1bb58ea93e7c0281a8863acf9e91818b41ce91c211e3569d94fb88cf925ffbad579ccfa33dc917eb431
7
- data.tar.gz: dbb15a8ff723305b3db21c01fa74eca91e9f782c07e5d65440eb71a3141969fd9a6bd38d0fe09b15f96260f7580b127c5ba78121f0444b5027ea011464793049
6
+ metadata.gz: 1f3e7a4b668c929daa1b5424b7a8ab7f028238b3f6dfe0e812351991bbc37923b58491760df6e4ed4921624cd4954761daeac257c6a540fd193d160d121044de
7
+ data.tar.gz: 02f2bf2e813cbe1624ae83d16936196ecf523fe8d3773f9b0c39813aa0822d3c71fd273ee01a63072c20d86bf05f4c1a775eda3fae41cec4bf55ea567f772aef
data/.travis.yml CHANGED
@@ -4,6 +4,7 @@ rvm:
4
4
  - 2.1.0
5
5
  - 2.2.0
6
6
  - 2.3.3
7
+ - 2.4.1
7
8
 
8
9
  notifications:
9
10
  email:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## [v3.1.0] - 2017-07-02
2
+
3
+ - Add shoryuken sqs delete command
4
+ - [#395](https://github.com/phstc/shoryuken/pull/395)
5
+
6
+ - Add processing groups support; Concurrency per queue support
7
+ - [#389](https://github.com/phstc/shoryuken/pull/389)
8
+
9
+ - Terminate Shoryuken if the fetcher crashes
10
+ - [#389](https://github.com/phstc/shoryuken/pull/389)
11
+
12
+ ## [v3.0.11] - 2017-06-24
13
+
14
+ - Add shoryuken sqs create command
15
+ - [#388](https://github.com/phstc/shoryuken/pull/388)
16
+
17
+ ## [v3.0.10] - 2017-06-24
18
+
19
+ - Allow aws sdk v3
20
+ - [#381](https://github.com/phstc/shoryuken/pull/381)
21
+
22
+ - Allow configuring Rails via the config file
23
+ - [#387](https://github.com/phstc/shoryuken/pull/387)
24
+
1
25
  ## [v3.0.9] - 2017-06-05
2
26
 
3
27
  - Allow configuring queue URLs instead of names
data/Gemfile CHANGED
@@ -7,4 +7,5 @@ group :test do
7
7
  gem 'codeclimate-test-reporter', require: nil
8
8
  gem 'simplecov'
9
9
  gem 'multi_xml'
10
+ gem 'httparty'
10
11
  end
data/README.md CHANGED
@@ -1,35 +1,23 @@
1
1
  # Shoryuken
2
2
 
3
- ![](shoryuken.jpg)
3
+ ![Shoryuken](shoryuken.jpg)
4
4
 
5
- Shoryuken _sho-ryu-ken_ is a super-efficient [AWS SQS](https://aws.amazon.com/sqs/) thread-based message processor.
5
+ Shoryuken _sho-ryu-ken_ is a super-efficient [Amazon SQS](https://aws.amazon.com/sqs/) thread-based message processor.
6
6
 
7
7
  [![Build Status](https://travis-ci.org/phstc/shoryuken.svg)](https://travis-ci.org/phstc/shoryuken)
8
8
  [![Code Climate](https://codeclimate.com/github/phstc/shoryuken/badges/gpa.svg)](https://codeclimate.com/github/phstc/shoryuken)
9
9
 
10
10
  ## Key features
11
11
 
12
- ### Load balancing
13
-
14
- Yeah, Shoryuken load balances the messages consumption!
15
-
16
- ```yaml
17
- concurrency: 25
18
- delay: 0
19
- queues:
20
- - [high_priority, 6]
21
- - [normal_priority, 2]
22
- - [low_priority, 1]
23
- ```
24
-
25
- Or you can set them all to 1 for having the same priorities.
26
-
27
- - `concurrency` (default 25) is the number of threads you want to make available for processing.
28
- - `delay` (default to 0) is the number of seconds to pause fetching from an empty queue. SQS charges per request, pause checking empty queues for a while can be a cost efficient strategy.
29
-
30
- ### Fetch in batches
31
-
32
- To be even more performant and cost effective, Shoryuken fetches SQS messages in batches, so a single SQS request can fetch up to 10 messages.
12
+ - [Rails Active Job](https://github.com/phstc/shoryuken/wiki/Rails-Integration-Active-Job)
13
+ - [Queue Load balancing](https://github.com/phstc/shoryuken/wiki/Shoryuken-options#load-balancing)
14
+ - [Concurrency per queue](https://github.com/phstc/shoryuken/wiki/Processing-Groups)
15
+ - [Long Polling](https://github.com/phstc/shoryuken/wiki/Long-Polling)
16
+ - [Batch processing](https://github.com/phstc/shoryuken/wiki/Worker-options#batch)
17
+ - [Auto extend visibility timeout](https://github.com/phstc/shoryuken/wiki/Worker-options#auto_visibility_timeout)
18
+ - [Exponential backoff](https://github.com/phstc/shoryuken/wiki/Worker-options#retry_intervals)
19
+ - [Middleware support](https://github.com/phstc/shoryuken/wiki/Middleware)
20
+ - Amazon SQS CLI. See `shoryuken help sqs`
33
21
 
34
22
  ## Requirements
35
23
 
@@ -43,102 +31,19 @@ Add this line to your application's Gemfile:
43
31
  gem 'shoryuken'
44
32
  ```
45
33
 
46
- Or to get the latest updates:
47
-
48
- ```ruby
49
- gem 'shoryuken', github: 'phstc/shoryuken', branch: 'master'
50
- ```
51
-
52
34
  And then execute:
53
35
 
54
- $ bundle
55
-
56
- Or install it yourself as:
57
-
58
- $ gem install shoryuken
59
-
60
- ## Usage
61
-
62
- ### Worker class
63
-
64
- ```ruby
65
- class MyWorker
66
- include Shoryuken::Worker
67
-
68
- shoryuken_options queue: 'default', auto_delete: true
69
- # shoryuken_options queue: ->{ "#{ENV['environment']}_default" }
70
-
71
- # shoryuken_options body_parser: :json
72
- # shoryuken_options body_parser: ->(sqs_msg){ REXML::Document.new(sqs_msg.body) }
73
- # shoryuken_options body_parser: JSON
74
-
75
- def perform(sqs_msg, body)
76
- puts body
77
- end
78
- end
79
- ```
80
-
81
- [Check the Worker options documention](https://github.com/phstc/shoryuken/wiki/Worker-options).
82
-
83
- ### Sending a message
84
-
85
- [Check the Sending a message documentation](https://github.com/phstc/shoryuken/wiki/Sending-a-message)
86
-
87
- ### Middleware
88
-
89
- ```ruby
90
- class MyMiddleware
91
- def call(worker_instance, queue, sqs_msg, body)
92
- puts 'Before work'
93
- yield
94
- puts 'After work'
95
- end
96
- end
97
- ```
98
-
99
- [Check the Middleware documentation](https://github.com/phstc/shoryuken/wiki/Middleware).
100
-
101
- ### Shoryuken Configuration
102
-
103
- Sample configuration file `shoryuken.yml`.
104
-
105
- ```yaml
106
- concurrency: 25 # The number of allocated threads to process messages. Default 25
107
- delay: 0 # The delay in seconds to pause empty queues. Default 0
108
- queues:
109
- - [queue1, 1]
110
- - [queue2, 1]
111
- - [queue3, 1]
112
- ```
113
-
114
- #### AWS Configuration
115
-
116
- [Check the Configure AWS Client documentation](https://github.com/phstc/shoryuken/wiki/Configure-the-AWS-Client)
117
-
118
- ### Rails Integration
119
-
120
- [Check the Rails Integration Active Job documention](https://github.com/phstc/shoryuken/wiki/Rails-Integration-Active-Job).
121
-
122
- ### Start Shoryuken
123
-
124
36
  ```shell
125
- bundle exec shoryuken -r worker.rb -C shoryuken.yml
37
+ $ bundle
126
38
  ```
127
39
 
128
- For other options check `bundle exec shoryuken help start`
129
-
130
- #### SQS commands
131
-
132
- Check also some available SQS commands `bundle exec shoryuken help sqs`, such as:
40
+ ## Usage
133
41
 
134
- - `ls` list queues
135
- - `mv` move messages from one queue to another
136
- - `dump` dump messages from a queue into a JSON lines file
137
- - `requeue` requeue messages from a dump file
42
+ Check the [Getting Started](https://github.com/phstc/shoryuken/wiki/Getting-Started) page.
138
43
 
139
44
  ## More Information
140
45
 
141
- For more information on advanced topics such as signals (shutdown), ActiveJob integration, and so on please check the [Shoryuken Wiki](https://github.com/phstc/shoryuken/wiki).
46
+ For more information check the [wiki page](https://github.com/phstc/shoryuken/wiki).
142
47
 
143
48
  ## Credits
144
49
 
data/bin/cli/sqs.rb CHANGED
@@ -33,7 +33,7 @@ module Shoryuken
33
33
  entries: batch.map { |message| { id: message.message_id, receipt_handle: message.receipt_handle } }
34
34
  ).failed.any? do |failure|
35
35
  say(
36
- "Could not delete #{failure.id}, code: '#{failure.code}', message: '#{failure.message}', sender_fault: #{failure.sender_fault}",
36
+ "Could not delete #{failure.id}, code: #{failure.code}, message: #{failure.message}, sender_fault: #{failure.sender_fault}",
37
37
  :yellow
38
38
  )
39
39
  end
@@ -185,6 +185,20 @@ module Shoryuken
185
185
 
186
186
  say "Purge request sent for #{queue_name}. The message deletion process takes up to 60 seconds", :yellow
187
187
  end
188
+
189
+ desc 'create QUEUE-NAME', 'Create a queue'
190
+ def create(queue_name)
191
+ queue_url = sqs.create_queue(queue_name: queue_name).queue_url
192
+
193
+ say "Queue #{queue_name} was successfully created. Queue URL #{queue_url}", :green
194
+ end
195
+
196
+ desc 'delete QUEUE-NAME', 'delete a queue'
197
+ def delete(queue_name)
198
+ sqs.delete_queue(queue_url: find_queue_url(queue_name))
199
+
200
+ say "Queue #{queue_name} was successfully delete", :green
201
+ end
188
202
  end
189
203
  end
190
204
  end
@@ -24,7 +24,7 @@ module Shoryuken
24
24
  @workers[queue]
25
25
  end
26
26
 
27
- worker_class.new
27
+ worker_class.new if worker_class
28
28
  end
29
29
 
30
30
  def queues
@@ -34,7 +34,7 @@ module Shoryuken
34
34
  def register_worker(queue, clazz)
35
35
  if (worker_class = @workers[queue])
36
36
  if worker_class.get_shoryuken_options['batch'] == true || clazz.get_shoryuken_options['batch'] == true
37
- fail ArgumentError, "Could not register #{clazz} for '#{queue}', "\
37
+ fail ArgumentError, "Could not register #{clazz} for #{queue}, "\
38
38
  "because #{worker_class} is already registered for this queue, "\
39
39
  "and Shoryuken doesn't support a batchable worker for a queue with multiple workers"
40
40
  end
@@ -24,7 +24,7 @@ module Shoryuken
24
24
  end
25
25
 
26
26
  def load
27
- load_rails if options[:rails]
27
+ load_rails if Shoryuken.options[:rails]
28
28
  prefix_active_job_queue_names
29
29
  parse_queues
30
30
  require_workers
@@ -44,7 +44,11 @@ module Shoryuken
44
44
 
45
45
  fail ArgumentError, "The supplied config file #{path} does not exist" unless File.exist?(path)
46
46
 
47
- YAML.load(ERB.new(IO.read(path)).result).deep_symbolize_keys
47
+ if result = YAML.load(ERB.new(IO.read(path)).result)
48
+ result.deep_symbolize_keys
49
+ else
50
+ {}
51
+ end
48
52
  end
49
53
 
50
54
  def initialize_logger
@@ -96,13 +100,24 @@ module Shoryuken
96
100
  end
97
101
  end
98
102
 
99
- def parse_queue(queue, weight = nil)
100
- Shoryuken.add_queue(queue, [weight.to_i, 1].max)
103
+ def parse_queue(queue, weight = nil, group)
104
+ Shoryuken.add_queue(queue, [weight.to_i, 1].max, group)
101
105
  end
102
106
 
103
107
  def parse_queues
104
- Shoryuken.options[:queues].to_a.each do |queue_and_weight|
105
- parse_queue(*queue_and_weight)
108
+ if Shoryuken.options[:queues].to_a.any?
109
+ Shoryuken.add_group('default', Shoryuken.options.fetch(:concurrency, 25))
110
+
111
+ Shoryuken.options[:queues].to_a.each do |queue, weight|
112
+ parse_queue(queue, weight, 'default')
113
+ end
114
+ end
115
+
116
+ Shoryuken.options[:groups].to_a.each do |group, options|
117
+ Shoryuken.add_group(group, options.fetch(:concurrency, 25))
118
+ options[:queues].to_a.each do |queue, weight|
119
+ parse_queue(queue, weight, group)
120
+ end
106
121
  end
107
122
  end
108
123
 
@@ -119,25 +134,30 @@ module Shoryuken
119
134
  end
120
135
 
121
136
  def validate_queues
122
- return Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.queues.empty?
137
+ return Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.ungrouped_queues.empty?
123
138
 
124
139
  non_existent_queues = []
125
140
 
126
- Shoryuken.queues.uniq.each do |queue|
141
+ Shoryuken.ungrouped_queues.uniq.each do |queue|
127
142
  begin
128
143
  Shoryuken::Client.queues(queue)
129
- rescue Aws::SQS::Errors::NonExistentQueue
144
+ rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
130
145
  non_existent_queues << queue
131
146
  end
132
147
  end
133
148
 
134
- fail ArgumentError, "The specified queue(s) #{non_existent_queues.join(', ')} do not exist" if non_existent_queues.any?
149
+ return if non_existent_queues.none?
150
+
151
+ fail(
152
+ ArgumentError,
153
+ "The specified queue(s) #{non_existent_queues.join(', ')} do not exist.\nTry 'shoryuken sqs create QUEUE-NAME' for creating a queue with default settings"
154
+ )
135
155
  end
136
156
 
137
157
  def validate_workers
138
158
  return if defined?(::ActiveJob)
139
159
 
140
- all_queues = Shoryuken.queues
160
+ all_queues = Shoryuken.ungrouped_queues
141
161
  queues_with_workers = Shoryuken.worker_registry.queues
142
162
 
143
163
  (all_queues - queues_with_workers).each do |queue|
@@ -4,23 +4,23 @@ module Shoryuken
4
4
 
5
5
  FETCH_LIMIT = 10
6
6
 
7
- def fetch(queue, available_processors)
7
+ attr_reader :group
8
+
9
+ def initialize(group)
10
+ @group = group
11
+ end
12
+
13
+ def fetch(queue, limit)
8
14
  started_at = Time.now
9
15
 
10
16
  logger.debug { "Looking for new messages in #{queue}" }
11
17
 
12
- begin
13
- limit = available_processors > FETCH_LIMIT ? FETCH_LIMIT : available_processors
14
-
15
- sqs_msgs = Array(receive_messages(queue, limit))
16
- logger.info { "Found #{sqs_msgs.size} messages for #{queue.name}" } unless sqs_msgs.empty?
17
- logger.debug { "Fetcher for #{queue} completed in #{elapsed(started_at)} ms" }
18
- sqs_msgs
19
- rescue => ex
20
- logger.error { "Error fetching message: #{ex.message}" }
21
- logger.error { ex.backtrace.join("\n") } unless ex.backtrace.nil?
22
- []
23
- end
18
+ sqs_msgs = Array(receive_messages(queue, [FETCH_LIMIT, limit].min))
19
+
20
+ logger.info { "Found #{sqs_msgs.size} messages for #{queue.name}" } unless sqs_msgs.empty?
21
+ logger.debug { "Fetcher for #{queue} completed in #{elapsed(started_at)} ms" }
22
+
23
+ sqs_msgs
24
24
  end
25
25
 
26
26
  private
@@ -29,10 +29,11 @@ module Shoryuken
29
29
  # AWS limits the batch size by 10
30
30
  limit = limit > FETCH_LIMIT ? FETCH_LIMIT : limit
31
31
 
32
- options = Shoryuken.sqs_client_receive_message_opts.to_h.dup
33
- options[:max_number_of_messages] = limit
32
+ options = Shoryuken.sqs_client_receive_message_opts[group].to_h.dup
33
+
34
+ options[:max_number_of_messages] = limit
34
35
  options[:message_attribute_names] = %w(All)
35
- options[:attribute_names] = %w(All)
36
+ options[:attribute_names] = %w(All)
36
37
 
37
38
  options.merge!(queue.options)
38
39
 
@@ -3,17 +3,96 @@ module Shoryuken
3
3
  include Util
4
4
 
5
5
  def initialize
6
- @manager = Shoryuken::Manager.new(Shoryuken::Fetcher.new,
7
- Shoryuken.options[:polling_strategy].new(Shoryuken.queues))
6
+ @managers = create_managers
7
+ @shutdowning = Concurrent::AtomicBoolean.new(false)
8
8
  end
9
9
 
10
- def stop(options = {})
11
- @manager.stop(shutdown: !options[:shutdown].nil?,
12
- timeout: Shoryuken.options[:timeout])
10
+ def start
11
+ logger.info { 'Starting' }
12
+
13
+ start_callback
14
+ start_managers
15
+ end
16
+
17
+ def stop!
18
+ initiate_stop
19
+
20
+ executor.shutdown
21
+
22
+ return if executor.wait_for_termination(Shoryuken.options[:timeout])
23
+
24
+ executor.kill
25
+ end
26
+
27
+ def stop
28
+ fire_event(:quiet, true)
29
+
30
+ initiate_stop
31
+
32
+ executor.shutdown
33
+ executor.wait_for_termination
34
+ end
35
+
36
+ private
37
+
38
+ def executor
39
+ Concurrent.global_io_executor
40
+ end
41
+
42
+ def start_managers
43
+ @managers.each do |manager|
44
+ Concurrent::Promise.execute { manager.start }.rescue do |ex|
45
+ log_manager_failure(ex)
46
+ start_soft_shutdown
47
+ end
48
+ end
49
+ end
50
+
51
+ def start_soft_shutdown
52
+ Process.kill('USR1', Process.pid) if @shutdowning.make_true
53
+ end
54
+
55
+ def log_manager_failure(ex)
56
+ return unless ex
57
+
58
+ logger.error { "Manager failed: #{ex.message}" }
59
+ logger.error { ex.backtrace.join("\n") } unless ex.backtrace.nil?
60
+ end
61
+
62
+ def initiate_stop
63
+ logger.info { 'Shutting down' }
64
+
65
+ @managers.each(&:stop)
66
+
67
+ stop_callback
68
+ end
69
+
70
+ def start_callback
71
+ if (callback = Shoryuken.start_callback)
72
+ logger.debug { 'Calling start_callback' }
73
+ callback.call
74
+ end
75
+
76
+ fire_event(:startup)
77
+ end
78
+
79
+ def stop_callback
80
+ if (callback = Shoryuken.stop_callback)
81
+ logger.debug { 'Calling stop_callback' }
82
+ callback.call
83
+ end
84
+
85
+ fire_event(:shutdown, true)
13
86
  end
14
87
 
15
- def run
16
- @manager.start
88
+ def create_managers
89
+ Shoryuken.groups.map do |group, options|
90
+ Shoryuken::Manager.new(
91
+ Shoryuken::Fetcher.new(group),
92
+ Shoryuken.polling_strategy(group).new(options[:queues]),
93
+ options[:concurrency]
94
+ )
95
+ end
17
96
  end
18
97
  end
19
98
  end
@@ -6,95 +6,71 @@ module Shoryuken
6
6
  # See https://github.com/phstc/shoryuken/issues/348#issuecomment-292847028
7
7
  MIN_DISPATCH_INTERVAL = 0.1
8
8
 
9
- def initialize(fetcher, polling_strategy)
10
- @count = Shoryuken.options.fetch(:concurrency, 25)
11
-
12
- raise(ArgumentError, "Concurrency value #{@count} is invalid, it needs to be a positive number") unless @count > 0
13
-
14
- @queues = Shoryuken.queues.dup.uniq
15
-
16
- @done = Concurrent::AtomicBoolean.new(false)
17
-
18
- @fetcher = fetcher
9
+ def initialize(fetcher, polling_strategy, concurrency)
10
+ @fetcher = fetcher
19
11
  @polling_strategy = polling_strategy
20
-
21
- @pool = Concurrent::FixedThreadPool.new(@count, max_queue: @count)
22
- @dispatcher_executor = Concurrent::SingleThreadExecutor.new
12
+ @max_processors = concurrency
13
+ @busy_processors = Concurrent::AtomicFixnum.new(0)
14
+ @done = Concurrent::AtomicBoolean.new(false)
23
15
  end
24
16
 
25
17
  def start
26
- logger.info { 'Starting' }
27
-
28
- dispatch_async
18
+ dispatch
29
19
  end
30
20
 
31
- def stop(options = {})
21
+ def stop
32
22
  @done.make_true
33
-
34
- if (callback = Shoryuken.stop_callback)
35
- logger.info { 'Calling on_stop callback' }
36
- callback.call
37
- end
38
-
39
- fire_event(:shutdown, true)
40
-
41
- logger.info { 'Shutting down workers' }
42
-
43
- @dispatcher_executor.kill
44
-
45
- if options[:shutdown]
46
- hard_shutdown_in(options[:timeout])
47
- else
48
- soft_shutdown
49
- end
50
23
  end
51
24
 
52
- def processor_failed(ex)
53
- logger.error { "Processor failed: #{ex.message}" }
54
- logger.error { ex.backtrace.join("\n") } unless ex.backtrace.nil?
55
- end
25
+ private
56
26
 
57
- def processor_done(queue)
58
- logger.debug { "Process done for #{queue}" }
27
+ def stopped?
28
+ @done.true? || !Concurrent.global_io_executor.running?
59
29
  end
60
30
 
61
- private
31
+ def dispatch
32
+ return if stopped?
62
33
 
63
- def dispatch_async
64
- @dispatcher_executor.post(&method(:dispatch_now))
65
- end
34
+ if !ready.positive? || (queue = @polling_strategy.next_queue).nil?
35
+ return dispatch_later
36
+ end
66
37
 
67
- def dispatch_now
68
- return if @done.true?
38
+ fire_event(:dispatch)
69
39
 
70
- begin
71
- if ready.zero? || (queue = @polling_strategy.next_queue).nil?
72
- sleep MIN_DISPATCH_INTERVAL
73
- return
74
- end
40
+ logger.info { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{@polling_strategy.active_queues}" }
75
41
 
76
- fire_event(:dispatch)
42
+ batched_queue?(queue) ? dispatch_batch(queue) : dispatch_single_messages(queue)
77
43
 
78
- logger.debug { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{@polling_strategy.active_queues}" }
44
+ dispatch
45
+ end
79
46
 
80
- batched_queue?(queue) ? dispatch_batch(queue) : dispatch_single_messages(queue)
81
- ensure
82
- dispatch_async
83
- end
47
+ def dispatch_later
48
+ sleep(MIN_DISPATCH_INTERVAL)
49
+ dispatch
84
50
  end
85
51
 
86
52
  def busy
87
- @count - ready
53
+ @busy_processors.value
88
54
  end
89
55
 
90
56
  def ready
91
- @pool.remaining_capacity
57
+ @max_processors - busy
92
58
  end
93
59
 
94
- def assign(queue, sqs_msg)
60
+ def processor_done
61
+ @busy_processors.decrement
62
+ end
63
+
64
+ def assign(queue_name, sqs_msg)
65
+ return if stopped?
66
+
95
67
  logger.debug { "Assigning #{sqs_msg.message_id}" }
96
68
 
97
- @pool.post { Processor.new(self).process(queue, sqs_msg) }
69
+ @busy_processors.increment
70
+
71
+ Concurrent::Promise.execute {
72
+ Processor.new(queue_name, sqs_msg).process
73
+ }.then { processor_done }.rescue { processor_done }
98
74
  end
99
75
 
100
76
  def dispatch_batch(queue)
@@ -105,6 +81,7 @@ module Shoryuken
105
81
 
106
82
  def dispatch_single_messages(queue)
107
83
  messages = @fetcher.fetch(queue, ready)
84
+
108
85
  @polling_strategy.messages_found(queue.name, messages.size)
109
86
  messages.each { |message| assign(queue.name, message) }
110
87
  end
@@ -113,24 +90,6 @@ module Shoryuken
113
90
  Shoryuken.worker_registry.batch_receive_messages?(queue.name)
114
91
  end
115
92
 
116
- def soft_shutdown
117
- @pool.shutdown
118
- @pool.wait_for_termination
119
- end
120
-
121
- def hard_shutdown_in(delay)
122
- if busy > 0
123
- logger.info { "Pausing up to #{delay} seconds to allow workers to finish..." }
124
- end
125
-
126
- @pool.shutdown
127
-
128
- return if @pool.wait_for_termination(delay)
129
-
130
- logger.info { "Hard shutting down #{busy} busy workers" }
131
- @pool.kill
132
- end
133
-
134
93
  def patch_batch!(sqs_msgs)
135
94
  sqs_msgs.instance_eval do
136
95
  def message_id