shoryuken 3.0.6 → 4.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +90 -24
  3. data/.travis.yml +17 -5
  4. data/CHANGELOG.md +265 -62
  5. data/Gemfile +9 -1
  6. data/Gemfile.aws-sdk-core-v2 +13 -0
  7. data/README.md +19 -113
  8. data/Rakefile +1 -1
  9. data/bin/cli/base.rb +0 -3
  10. data/bin/cli/sqs.rb +42 -16
  11. data/bin/shoryuken +4 -9
  12. data/examples/bootstrap_queues.rb +3 -3
  13. data/examples/default_worker.rb +2 -2
  14. data/lib/shoryuken/body_parser.rb +27 -0
  15. data/lib/shoryuken/client.rb +6 -2
  16. data/lib/shoryuken/core_ext.rb +1 -1
  17. data/lib/shoryuken/default_worker_registry.rb +2 -2
  18. data/lib/shoryuken/environment_loader.rb +60 -24
  19. data/lib/shoryuken/extensions/active_job_adapter.rb +21 -11
  20. data/lib/shoryuken/fetcher.rb +58 -19
  21. data/lib/shoryuken/launcher.rb +70 -7
  22. data/lib/shoryuken/logging.rb +1 -6
  23. data/lib/shoryuken/manager.rb +50 -80
  24. data/lib/shoryuken/middleware/chain.rb +4 -0
  25. data/lib/shoryuken/middleware/server/active_record.rb +1 -1
  26. data/lib/shoryuken/middleware/server/auto_delete.rb +4 -9
  27. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +6 -9
  28. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +9 -3
  29. data/lib/shoryuken/middleware/server/timing.rb +12 -16
  30. data/lib/shoryuken/options.rb +225 -0
  31. data/lib/shoryuken/polling/base.rb +67 -0
  32. data/lib/shoryuken/polling/strict_priority.rb +77 -0
  33. data/lib/shoryuken/polling/weighted_round_robin.rb +66 -0
  34. data/lib/shoryuken/processor.rb +30 -39
  35. data/lib/shoryuken/queue.rb +41 -10
  36. data/lib/shoryuken/runner.rb +13 -17
  37. data/lib/shoryuken/util.rb +3 -3
  38. data/lib/shoryuken/version.rb +1 -1
  39. data/lib/shoryuken/worker/default_executor.rb +33 -0
  40. data/lib/shoryuken/worker/inline_executor.rb +37 -0
  41. data/lib/shoryuken/worker.rb +76 -31
  42. data/lib/shoryuken/worker_registry.rb +4 -4
  43. data/lib/shoryuken.rb +54 -173
  44. data/shoryuken.gemspec +6 -6
  45. data/spec/integration/launcher_spec.rb +14 -8
  46. data/spec/shoryuken/body_parser_spec.rb +89 -0
  47. data/spec/shoryuken/client_spec.rb +1 -1
  48. data/spec/shoryuken/core_ext_spec.rb +6 -6
  49. data/spec/shoryuken/default_worker_registry_spec.rb +2 -4
  50. data/spec/shoryuken/environment_loader_spec.rb +32 -12
  51. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +64 -0
  52. data/spec/shoryuken/fetcher_spec.rb +101 -18
  53. data/spec/shoryuken/manager_spec.rb +54 -26
  54. data/spec/shoryuken/middleware/chain_spec.rb +17 -5
  55. data/spec/shoryuken/middleware/server/auto_delete_spec.rb +9 -7
  56. data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +4 -4
  57. data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +6 -4
  58. data/spec/shoryuken/middleware/server/timing_spec.rb +5 -3
  59. data/spec/shoryuken/options_spec.rb +180 -0
  60. data/spec/shoryuken/{polling_spec.rb → polling/strict_priority_spec.rb} +2 -101
  61. data/spec/shoryuken/polling/weighted_round_robin_spec.rb +99 -0
  62. data/spec/shoryuken/processor_spec.rb +26 -127
  63. data/spec/shoryuken/queue_spec.rb +115 -41
  64. data/spec/shoryuken/runner_spec.rb +3 -4
  65. data/spec/shoryuken/util_spec.rb +24 -0
  66. data/spec/shoryuken/worker/default_executor_spec.rb +105 -0
  67. data/spec/shoryuken/worker/inline_executor_spec.rb +49 -0
  68. data/spec/shoryuken/worker_spec.rb +35 -96
  69. data/spec/shoryuken_spec.rb +0 -59
  70. data/spec/spec_helper.rb +14 -3
  71. data/test_workers/endless_interruptive_worker.rb +2 -2
  72. data/test_workers/endless_uninterruptive_worker.rb +4 -4
  73. metadata +31 -12
  74. data/lib/shoryuken/polling.rb +0 -204
data/Gemfile CHANGED
@@ -4,7 +4,15 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :test do
7
+ gem 'activejob', '~> 4'
8
+ gem 'aws-sdk-core', '~> 3'
9
+ gem 'aws-sdk-sqs'
7
10
  gem 'codeclimate-test-reporter', require: nil
8
- gem 'simplecov'
11
+ gem 'httparty'
9
12
  gem 'multi_xml'
13
+ gem 'simplecov'
14
+ end
15
+
16
+ group :development do
17
+ gem 'rubocop'
10
18
  end
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in shoryuken.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'activejob', '~> 4'
8
+ gem 'aws-sdk-core', '~> 2'
9
+ gem 'codeclimate-test-reporter', require: nil
10
+ gem 'httparty'
11
+ gem 'multi_xml'
12
+ gem 'simplecov'
13
+ end
data/README.md CHANGED
@@ -1,42 +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
- Given this configuration:
17
-
18
- ```yaml
19
- concurrency: 25
20
- delay: 25
21
- queues:
22
- - [high_priority, 6]
23
- - [normal_priority, 2]
24
- - [low_priority, 1]
25
- ```
26
-
27
- And supposing all the queues are full of messages, the configuration above will make Shoryuken to process `high_priority` 3 times more than `normal_priority` and 6 times more than `low_priority`,
28
- splitting the work load among all available processors `concurrency: 25` .
29
-
30
- If `high_priority` gets empty, Shoryuken will keep using the 25 processors, but only to process `normal_priority` and `low_priority`.
31
-
32
- If `high_priority` receives a new message, Shoryuken will smoothly increase back its weight one by one until it reaches the weight of 6 again.
33
-
34
- [If a queue gets empty, Shoryuken will pause checking it for `delay: 25`](https://github.com/phstc/shoryuken/wiki/Shoryuken-options#delay).
35
-
36
-
37
- ### Fetch in batches
38
-
39
- 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`
40
21
 
41
22
  ## Requirements
42
23
 
@@ -50,102 +31,27 @@ Add this line to your application's Gemfile:
50
31
  gem 'shoryuken'
51
32
  ```
52
33
 
53
- Or to get the latest updates:
54
-
55
- ```ruby
56
- gem 'shoryuken', github: 'phstc/shoryuken', branch: 'master'
57
- ```
58
-
59
- And then execute:
60
-
61
- $ bundle
62
-
63
- Or install it yourself as:
64
-
65
- $ gem install shoryuken
66
-
67
- ## Usage
68
-
69
- ### Worker class
70
-
71
- ```ruby
72
- class MyWorker
73
- include Shoryuken::Worker
74
-
75
- shoryuken_options queue: 'default', auto_delete: true
76
- # shoryuken_options queue: ->{ "#{ENV['environment']}_default" }
77
-
78
- # shoryuken_options body_parser: :json
79
- # shoryuken_options body_parser: ->(sqs_msg){ REXML::Document.new(sqs_msg.body) }
80
- # shoryuken_options body_parser: JSON
81
-
82
- def perform(sqs_msg, body)
83
- puts body
84
- end
85
- end
86
- ```
87
-
88
- [Check the Worker options documention](https://github.com/phstc/shoryuken/wiki/Worker-options).
89
-
90
- ### Sending a message
91
-
92
- [Check the Sending a message documentation](https://github.com/phstc/shoryuken/wiki/Sending-a-message)
93
-
94
- ### Middleware
34
+ If you are using AWS SDK version 3, please also add this line:
95
35
 
96
36
  ```ruby
97
- class MyMiddleware
98
- def call(worker_instance, queue, sqs_msg, body)
99
- puts 'Before work'
100
- yield
101
- puts 'After work'
102
- end
103
- end
104
- ```
105
-
106
- [Check the Middleware documentation](https://github.com/phstc/shoryuken/wiki/Middleware).
107
-
108
- ### Shoryuken Configuration
109
-
110
- Sample configuration file `shoryuken.yml`.
111
-
112
- ```yaml
113
- concurrency: 25 # The number of allocated threads to process messages. Default 25
114
- delay: 25 # The delay in seconds to pause a queue when it's empty. Default 0
115
- queues:
116
- - [high_priority, 6]
117
- - [normal_priority, 2]
118
- - [low_priority, 1]
37
+ gem 'aws-sdk-sqs'
119
38
  ```
120
39
 
121
- #### AWS Configuration
122
-
123
- [Check the Configure AWS Client documentation](https://github.com/phstc/shoryuken/wiki/Configure-the-AWS-Client)
124
-
125
- ### Rails Integration
40
+ The extra gem `aws-sdk-sqs` is required in order to keep Shoryuken compatible with AWS SDK version 2 and 3.
126
41
 
127
- [Check the Rails Integration Active Job documention](https://github.com/phstc/shoryuken/wiki/Rails-Integration-Active-Job).
128
-
129
- ### Start Shoryuken
42
+ And then execute:
130
43
 
131
44
  ```shell
132
- bundle exec shoryuken -r worker.rb -C shoryuken.yml
45
+ $ bundle
133
46
  ```
134
47
 
135
- For other options check `bundle exec shoryuken help start`
136
-
137
- #### SQS commands
138
-
139
- Check also some available SQS commands `bundle exec shoryuken help sqs`, such as:
48
+ ## Usage
140
49
 
141
- - `ls` list queues
142
- - `mv` move messages from one queue to another
143
- - `dump` dump messages from a queue into a JSON lines file
144
- - `requeue` requeue messages from a dump file
50
+ Check the [Getting Started](https://github.com/phstc/shoryuken/wiki/Getting-Started) page.
145
51
 
146
52
  ## More Information
147
53
 
148
- 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).
54
+ For more information check the [wiki page](https://github.com/phstc/shoryuken/wiki).
149
55
 
150
56
  ## Credits
151
57
 
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ task :console do
12
12
  require 'pry'
13
13
  require 'shoryuken'
14
14
 
15
- config_file = File.join File.expand_path('..', __FILE__), 'shoryuken.yml'
15
+ config_file = File.join File.expand_path(__dir__), 'shoryuken.yml'
16
16
 
17
17
  if File.exist? config_file
18
18
  config = YAML.load File.read(config_file)
data/bin/cli/base.rb CHANGED
@@ -1,4 +1,3 @@
1
- # rubocop:disable Metrics/BlockLength
2
1
  module Shoryuken
3
2
  module CLI
4
3
  class Base < Thor
@@ -25,10 +24,8 @@ module Shoryuken
25
24
  end
26
25
 
27
26
  def print_format_column(column, size)
28
- size = 40 if size > 40
29
27
  size_with_padding = size + 4
30
28
  column = column.to_s.ljust(size_with_padding)
31
- column = "#{column[0...size - 2]}.." if column.size > size_with_padding
32
29
  column
33
30
  end
34
31
 
data/bin/cli/sqs.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'date'
2
2
 
3
- # rubocop:disable Metrics/AbcSize, Metrics/BlockLength
3
+ # rubocop:disable Metrics/BlockLength
4
4
  module Shoryuken
5
5
  module CLI
6
6
  class SQS < Base
@@ -8,12 +8,15 @@ module Shoryuken
8
8
 
9
9
  no_commands do
10
10
  def normalize_dump_message(message)
11
- message[:id] = message.delete(:message_id)
12
- message[:message_body] = message.delete(:body)
13
- message.delete(:receipt_handle)
14
- message.delete(:md5_of_body)
15
- message.delete(:md5_of_message_attributes)
16
- message
11
+ # symbolize_keys is needed for keeping it compatible with `requeue`
12
+ attributes = message[:attributes].symbolize_keys
13
+ {
14
+ id: message[:message_id],
15
+ message_body: message[:body],
16
+ message_attributes: message[:message_attributes],
17
+ message_deduplication_id: attributes[:MessageDeduplicationId],
18
+ message_group_id: attributes[:MessageGroupId]
19
+ }
17
20
  end
18
21
 
19
22
  def sqs
@@ -32,20 +35,23 @@ module Shoryuken
32
35
  queue_url: url,
33
36
  entries: batch.map { |message| { id: message.message_id, receipt_handle: message.receipt_handle } }
34
37
  ).failed.any? do |failure|
35
- say "Could not delete #{failure.id}, code: #{failure.code}", :yellow
38
+ say(
39
+ "Could not delete #{failure.id}, code: #{failure.code}, message: #{failure.message}, sender_fault: #{failure.sender_fault}",
40
+ :yellow
41
+ )
36
42
  end
37
43
  end
38
44
  end
39
45
 
40
- def batch_send(url, messages)
41
- messages.to_a.flatten.map(&method(:normalize_dump_message)).each_slice(10) do |batch|
46
+ def batch_send(url, messages, messages_per_batch = 10)
47
+ messages.to_a.flatten.map(&method(:normalize_dump_message)).each_slice(messages_per_batch) do |batch|
42
48
  sqs.send_message_batch(queue_url: url, entries: batch).failed.any? do |failure|
43
49
  say "Could not requeue #{failure.id}, code: #{failure.code}", :yellow
44
50
  end
45
51
  end
46
52
  end
47
53
 
48
- def find_all(url, limit, &block)
54
+ def find_all(url, limit)
49
55
  count = 0
50
56
  batch_size = limit > 10 ? 10 : limit
51
57
 
@@ -56,6 +62,7 @@ module Shoryuken
56
62
  messages = sqs.receive_message(
57
63
  queue_url: url,
58
64
  max_number_of_messages: batch_size,
65
+ attribute_names: ['All'],
59
66
  message_attribute_names: ['All']
60
67
  ).messages
61
68
 
@@ -71,7 +78,7 @@ module Shoryuken
71
78
  end
72
79
 
73
80
  def list_and_print_queues(urls)
74
- attrs = %w(QueueArn ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible LastModifiedTimestamp)
81
+ attrs = %w[QueueArn ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible LastModifiedTimestamp]
75
82
 
76
83
  entries = urls.map { |u| sqs.get_queue_attributes(queue_url: u, attribute_names: attrs).attributes }.map do |q|
77
84
  [
@@ -93,8 +100,8 @@ module Shoryuken
93
100
  end
94
101
 
95
102
  desc 'ls [QUEUE-NAME-PREFIX]', 'Lists queues'
96
- method_option :watch, aliases: '-w', type: :boolean, desc: 'watch queues'
97
- method_option :watch_interval, type: :numeric, default: 10, desc: 'watch interval'
103
+ method_option :watch, aliases: '-w', type: :boolean, desc: 'watch queues'
104
+ method_option :interval, aliases: '-n', type: :numeric, default: 2, desc: 'watch interval in seconds'
98
105
  def ls(queue_name_prefix = '')
99
106
  trap('SIGINT', 'EXIT') # expect ctrl-c from loop
100
107
 
@@ -105,7 +112,7 @@ module Shoryuken
105
112
 
106
113
  break unless options[:watch]
107
114
 
108
- sleep options[:watch_interval]
115
+ sleep options[:interval]
109
116
  puts
110
117
  end
111
118
  end
@@ -145,12 +152,13 @@ module Shoryuken
145
152
  end
146
153
 
147
154
  desc 'requeue QUEUE-NAME PATH', 'Requeues messages from a dump file'
155
+ method_option :batch_size, aliases: '-n', type: :numeric, default: 10, desc: 'number of messages per batch to send'
148
156
  def requeue(queue_name, path)
149
157
  fail_task "Path #{path} not found" unless File.exist?(path)
150
158
 
151
159
  messages = File.readlines(path).map { |line| JSON.parse(line, symbolize_names: true) }
152
160
 
153
- batch_send(find_queue_url(queue_name), messages)
161
+ batch_send(find_queue_url(queue_name), messages, options[:batch_size])
154
162
 
155
163
  say "Requeued #{messages.size} messages from #{path} to #{queue_name}", :green
156
164
  end
@@ -182,6 +190,24 @@ module Shoryuken
182
190
 
183
191
  say "Purge request sent for #{queue_name}. The message deletion process takes up to 60 seconds", :yellow
184
192
  end
193
+
194
+ desc 'create QUEUE-NAME', 'Create a queue'
195
+ method_option :attributes, aliases: '-a', type: :hash, default: {}, desc: 'queue attributes'
196
+ def create(queue_name)
197
+ attributes = options[:attributes]
198
+ attributes['FifoQueue'] ||= 'true' if queue_name.end_with?('.fifo')
199
+
200
+ queue_url = sqs.create_queue(queue_name: queue_name, attributes: attributes).queue_url
201
+
202
+ say "Queue #{queue_name} was successfully created. Queue URL #{queue_url}", :green
203
+ end
204
+
205
+ desc 'delete QUEUE-NAME', 'delete a queue'
206
+ def delete(queue_name)
207
+ sqs.delete_queue(queue_url: find_queue_url(queue_name))
208
+
209
+ say "Queue #{queue_name} was successfully delete", :green
210
+ end
185
211
  end
186
212
  end
187
213
  end
data/bin/shoryuken CHANGED
@@ -7,8 +7,6 @@ require 'aws-sdk-core'
7
7
  require_relative 'cli/base'
8
8
  require_relative 'cli/sqs'
9
9
  require_relative '../lib/shoryuken/runner'
10
-
11
- # rubocop:disable Metrics/AbcSize
12
10
  module Shoryuken
13
11
  module CLI
14
12
  class Runner < Base
@@ -21,6 +19,7 @@ module Shoryuken
21
19
  method_option :daemon, aliases: '-d', type: :boolean, desc: 'Daemonize process'
22
20
  method_option :queues, aliases: '-q', type: :array, desc: 'Queues to process with optional weights'
23
21
  method_option :require, aliases: '-r', type: :string, desc: 'Dir or path of the workers'
22
+ method_option :timeout, aliases: '-t', type: :numeric, desc: 'Hard shutdown timeout'
24
23
  method_option :config, aliases: '-C', type: :string, desc: 'Path to config file'
25
24
  method_option :config_file, type: :string, desc: 'Path to config file (backwards compatibility)'
26
25
  method_option :rails, aliases: '-R', type: :boolean, desc: 'Load Rails'
@@ -31,18 +30,14 @@ module Shoryuken
31
30
  def start
32
31
  opts = options.to_h.symbolize_keys
33
32
 
34
- if opts[:config_file]
35
- say "[DEPRECATED] Please use --config instead of --config-file", :yellow
36
- end
33
+ say '[DEPRECATED] Please use --config instead of --config-file', :yellow if opts[:config_file]
37
34
 
38
35
  opts[:config_file] = opts.delete(:config) if opts[:config]
39
36
 
40
37
  # Keep compatibility with old CLI queue format
41
- opts[:queues] = options[:queues].map { |q| q.split(',') } if options[:queues]
38
+ opts[:queues] = opts[:queues].reject(&:empty?).map { |q| q.split(',') } if opts[:queues]
42
39
 
43
- if options[:daemon] && options[:logfile].nil?
44
- fail_task "You should set a logfile if you're going to daemonize"
45
- end
40
+ fail_task "You should set a logfile if you're going to daemonize" if opts[:daemon] && opts[:logfile].nil?
46
41
 
47
42
  Shoryuken::Runner.instance.run(opts.freeze)
48
43
  end
@@ -2,7 +2,7 @@ require 'yaml'
2
2
  require 'shoryuken'
3
3
 
4
4
  # load SQS credentials
5
- config = YAML.load File.read(File.join(File.expand_path('..', __FILE__), 'shoryuken.yml'))
5
+ config = YAML.load File.read(File.join(File.expand_path(__dir__), 'shoryuken.yml'))
6
6
 
7
7
  Aws.config = config['aws']
8
8
 
@@ -18,11 +18,11 @@ if sqs.config['endpoint'] =~ /amazonaws.com/
18
18
 
19
19
  dead_letter_queue_arn = sqs.get_queue_attributes(
20
20
  queue_url: dead_letter_queue_url,
21
- attribute_names: %w(QueueArn)
21
+ attribute_names: %w[QueueArn]
22
22
  ).attributes['QueueArn']
23
23
 
24
24
  attributes = {}
25
- attributes['RedrivePolicy'] = %Q({"maxReceiveCount":"7", "deadLetterTargetArn":"#{dead_letter_queue_arn}"})
25
+ attributes['RedrivePolicy'] = %({"maxReceiveCount":"7", "deadLetterTargetArn":"#{dead_letter_queue_arn}"})
26
26
 
27
27
  sqs.set_queue_attributes queue_url: default_queue_url, attributes: attributes
28
28
  end
@@ -3,7 +3,7 @@ class DefaultWorker
3
3
 
4
4
  shoryuken_options queue: 'default', auto_delete: true
5
5
 
6
- def perform(sqs_msg, body)
7
- Shoryuken.logger.debug("Received message: '#{body}'")
6
+ def perform(_sqs_msg, body)
7
+ Shoryuken.logger.debug("Received message: #{body}")
8
8
  end
9
9
  end
@@ -0,0 +1,27 @@
1
+ module Shoryuken
2
+ class BodyParser
3
+ class << self
4
+ def parse(worker_class, sqs_msg)
5
+ body_parser = worker_class.get_shoryuken_options['body_parser']
6
+
7
+ case body_parser
8
+ when :json
9
+ JSON.parse(sqs_msg.body)
10
+ when Proc
11
+ body_parser.call(sqs_msg)
12
+ when :text, nil
13
+ sqs_msg.body
14
+ else
15
+ if body_parser.respond_to?(:parse)
16
+ # JSON.parse
17
+ body_parser.parse(sqs_msg.body)
18
+ elsif body_parser.respond_to?(:load)
19
+ # see https://github.com/phstc/shoryuken/pull/91
20
+ # JSON.load
21
+ body_parser.load(sqs_msg.body)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -8,11 +8,15 @@ module Shoryuken
8
8
  end
9
9
 
10
10
  def sqs
11
- @@sqs ||= Shoryuken.sqs_client
11
+ Shoryuken.sqs_client
12
12
  end
13
13
 
14
14
  def sqs=(sqs)
15
- @@sqs = sqs
15
+ # Since the @@queues values (Shoryuken::Queue objects) are built referencing @@sqs, if it changes, we need to
16
+ # re-build them on subsequent calls to `.queues(name)`.
17
+ @@queues = {}
18
+
19
+ Shoryuken.sqs_client = sqs
16
20
  end
17
21
  end
18
22
  end
@@ -34,7 +34,7 @@ module Shoryuken
34
34
  module StringExt
35
35
  module Constantize
36
36
  def constantize
37
- names = self.split('::')
37
+ names = split('::')
38
38
  names.shift if names.empty? || names.first.empty?
39
39
 
40
40
  constant = Object
@@ -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
@@ -42,9 +42,13 @@ module Shoryuken
42
42
  def config_file_options
43
43
  return {} unless (path = options[:config_file])
44
44
 
45
- fail ArgumentError, "The supplied config file '#{path}' does not exist" unless File.exist?(path)
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
@@ -62,10 +66,12 @@ module Shoryuken
62
66
  else
63
67
  # Painful contortions, see 1791 for discussion
64
68
  require File.expand_path('config/application.rb')
65
- ::Rails::Application.initializer 'shoryuken.eager_load' do
66
- ::Rails.application.config.eager_load = true
69
+ if ::Rails::VERSION::MAJOR == 4
70
+ ::Rails::Application.initializer 'shoryuken.eager_load' do
71
+ ::Rails.application.config.eager_load = true
72
+ end
67
73
  end
68
- require 'shoryuken/extensions/active_job_adapter' if defined?(::ActiveJob)
74
+ require 'shoryuken/extensions/active_job_adapter' if Shoryuken.active_job?
69
75
  require File.expand_path('config/environment.rb')
70
76
  end
71
77
  end
@@ -81,28 +87,53 @@ module Shoryuken
81
87
  end
82
88
  end
83
89
 
84
- def prefix_active_job_queue_names
85
- return unless defined? ::ActiveJob
86
- return unless Shoryuken.active_job_queue_name_prefixing
87
-
90
+ def prefix_active_job_queue_name(queue_name, weight)
88
91
  queue_name_prefix = ::ActiveJob::Base.queue_name_prefix
89
92
  queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
90
93
 
91
94
  # See https://github.com/rails/rails/blob/master/activejob/lib/active_job/queue_name.rb#L27
95
+ name_parts = [queue_name_prefix.presence, queue_name]
96
+ prefixed_queue_name = name_parts.compact.join(queue_name_delimiter)
97
+ [prefixed_queue_name, weight]
98
+ end
99
+
100
+ def prefix_active_job_queue_names
101
+ return unless Shoryuken.active_job?
102
+ return unless Shoryuken.active_job_queue_name_prefixing
103
+
92
104
  Shoryuken.options[:queues].to_a.map! do |queue_name, weight|
93
- name_parts = [queue_name_prefix.presence, queue_name]
94
- prefixed_queue_name = name_parts.compact.join(queue_name_delimiter)
95
- [prefixed_queue_name, weight]
105
+ prefix_active_job_queue_name(queue_name, weight)
106
+ end
107
+
108
+ Shoryuken.options[:groups].to_a.map! do |group, options|
109
+ if options[:queues]
110
+ options[:queues].map! do |queue_name, weight|
111
+ prefix_active_job_queue_name(queue_name, weight)
112
+ end
113
+ end
114
+
115
+ [group, options]
96
116
  end
97
117
  end
98
118
 
99
- def parse_queue(queue, weight = nil)
100
- Shoryuken.add_queue(queue, [weight.to_i, 1].max)
119
+ def parse_queue(queue, weight, group)
120
+ Shoryuken.add_queue(queue, [weight.to_i, 1].max, group)
101
121
  end
102
122
 
103
123
  def parse_queues
104
- Shoryuken.options[:queues].to_a.each do |queue_and_weight|
105
- parse_queue(*queue_and_weight)
124
+ if Shoryuken.options[:queues].to_a.any?
125
+ Shoryuken.add_group('default', Shoryuken.options.fetch(:concurrency, 25))
126
+
127
+ Shoryuken.options[:queues].to_a.each do |queue, weight|
128
+ parse_queue(queue, weight, 'default')
129
+ end
130
+ end
131
+
132
+ Shoryuken.options[:groups].to_a.each do |group, options|
133
+ Shoryuken.add_group(group, options.fetch(:concurrency, 25))
134
+ options[:queues].to_a.each do |queue, weight|
135
+ parse_queue(queue, weight, group)
136
+ end
106
137
  end
107
138
  end
108
139
 
@@ -119,29 +150,34 @@ module Shoryuken
119
150
  end
120
151
 
121
152
  def validate_queues
122
- Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.queues.empty?
153
+ return Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.ungrouped_queues.empty?
123
154
 
124
155
  non_existent_queues = []
125
156
 
126
- Shoryuken.queues.uniq.each do |queue|
157
+ Shoryuken.ungrouped_queues.uniq.each do |queue|
127
158
  begin
128
159
  Shoryuken::Client.queues(queue)
129
- rescue Aws::SQS::Errors::NonExistentQueue
160
+ rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
130
161
  non_existent_queues << queue
131
162
  end
132
163
  end
133
164
 
134
- fail ArgumentError, "The specified queue(s) #{non_existent_queues} do not exist" if non_existent_queues.any?
165
+ return if non_existent_queues.none?
166
+
167
+ fail(
168
+ ArgumentError,
169
+ "The specified queue(s) #{non_existent_queues.join(', ')} do not exist.\nTry 'shoryuken sqs create QUEUE-NAME' for creating a queue with default settings"
170
+ )
135
171
  end
136
172
 
137
173
  def validate_workers
138
- return if defined?(::ActiveJob)
174
+ return if Shoryuken.active_job?
139
175
 
140
- all_queues = Shoryuken.queues
176
+ all_queues = Shoryuken.ungrouped_queues
141
177
  queues_with_workers = Shoryuken.worker_registry.queues
142
178
 
143
179
  (all_queues - queues_with_workers).each do |queue|
144
- Shoryuken.logger.warn { "No worker supplied for '#{queue}'" }
180
+ Shoryuken.logger.warn { "No worker supplied for #{queue}" }
145
181
  end
146
182
  end
147
183
  end