shoryuken 2.1.1 → 3.1.3

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +5 -8
  3. data/.rubocop.yml +8 -2
  4. data/.travis.yml +6 -0
  5. data/CHANGELOG.md +167 -0
  6. data/Gemfile +2 -0
  7. data/README.md +16 -155
  8. data/Rakefile +0 -1
  9. data/bin/cli/base.rb +40 -0
  10. data/bin/cli/sqs.rb +204 -0
  11. data/bin/shoryuken +55 -9
  12. data/examples/default_worker.rb +1 -1
  13. data/lib/shoryuken/client.rb +3 -15
  14. data/lib/shoryuken/default_worker_registry.rb +10 -6
  15. data/lib/shoryuken/environment_loader.rb +60 -57
  16. data/lib/shoryuken/fetcher.rb +21 -51
  17. data/lib/shoryuken/launcher.rb +77 -23
  18. data/lib/shoryuken/logging.rb +0 -5
  19. data/lib/shoryuken/manager.rb +54 -205
  20. data/lib/shoryuken/message.rb +4 -13
  21. data/lib/shoryuken/middleware/chain.rb +1 -18
  22. data/lib/shoryuken/middleware/server/auto_delete.rb +3 -8
  23. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +16 -17
  24. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +37 -22
  25. data/lib/shoryuken/middleware/server/timing.rb +2 -2
  26. data/lib/shoryuken/options.rb +196 -0
  27. data/lib/shoryuken/polling/base.rb +67 -0
  28. data/lib/shoryuken/polling/strict_priority.rb +77 -0
  29. data/lib/shoryuken/polling/weighted_round_robin.rb +66 -0
  30. data/lib/shoryuken/processor.rb +27 -25
  31. data/lib/shoryuken/queue.rb +28 -9
  32. data/lib/shoryuken/runner.rb +131 -0
  33. data/lib/shoryuken/util.rb +1 -9
  34. data/lib/shoryuken/version.rb +1 -1
  35. data/lib/shoryuken/worker.rb +9 -1
  36. data/lib/shoryuken.rb +47 -157
  37. data/shoryuken.gemspec +7 -7
  38. data/spec/integration/launcher_spec.rb +15 -8
  39. data/spec/shoryuken/client_spec.rb +2 -45
  40. data/spec/shoryuken/default_worker_registry_spec.rb +12 -10
  41. data/spec/shoryuken/environment_loader_spec.rb +54 -0
  42. data/spec/shoryuken/fetcher_spec.rb +27 -46
  43. data/spec/shoryuken/manager_spec.rb +80 -93
  44. data/spec/shoryuken/middleware/chain_spec.rb +0 -24
  45. data/spec/shoryuken/middleware/server/auto_delete_spec.rb +2 -2
  46. data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +7 -3
  47. data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +56 -33
  48. data/spec/shoryuken/middleware/server/timing_spec.rb +5 -3
  49. data/spec/shoryuken/options_spec.rb +100 -0
  50. data/spec/shoryuken/polling/strict_priority_spec.rb +140 -0
  51. data/spec/shoryuken/polling/weighted_round_robin_spec.rb +99 -0
  52. data/spec/shoryuken/processor_spec.rb +20 -37
  53. data/spec/shoryuken/queue_spec.rb +72 -26
  54. data/spec/shoryuken/{cli_spec.rb → runner_spec.rb} +11 -26
  55. data/spec/shoryuken_spec.rb +1 -48
  56. data/spec/spec_helper.rb +14 -20
  57. data/test_workers/endless_interruptive_worker.rb +41 -0
  58. data/test_workers/endless_uninterruptive_worker.rb +44 -0
  59. metadata +39 -34
  60. data/lib/shoryuken/aws_config.rb +0 -64
  61. data/lib/shoryuken/cli.rb +0 -215
  62. data/lib/shoryuken/sns_arn.rb +0 -27
  63. data/lib/shoryuken/topic.rb +0 -17
  64. data/spec/shoryuken/sns_arn_spec.rb +0 -42
  65. data/spec/shoryuken/topic_spec.rb +0 -32
  66. data/spec/shoryuken_endpoint.yml +0 -6
data/bin/cli/sqs.rb ADDED
@@ -0,0 +1,204 @@
1
+ require 'date'
2
+
3
+ # rubocop:disable Metrics/AbcSize, Metrics/BlockLength
4
+ module Shoryuken
5
+ module CLI
6
+ class SQS < Base
7
+ namespace :sqs
8
+
9
+ no_commands do
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
17
+ end
18
+
19
+ def sqs
20
+ @_sqs ||= Aws::SQS::Client.new
21
+ end
22
+
23
+ def find_queue_url(queue_name)
24
+ sqs.get_queue_url(queue_name: queue_name).queue_url
25
+ rescue Aws::SQS::Errors::NonExistentQueue
26
+ fail_task "The specified queue #{queue_name} does not exist"
27
+ end
28
+
29
+ def batch_delete(url, messages)
30
+ messages.to_a.flatten.each_slice(10) do |batch|
31
+ sqs.delete_message_batch(
32
+ queue_url: url,
33
+ entries: batch.map { |message| { id: message.message_id, receipt_handle: message.receipt_handle } }
34
+ ).failed.any? do |failure|
35
+ say(
36
+ "Could not delete #{failure.id}, code: #{failure.code}, message: #{failure.message}, sender_fault: #{failure.sender_fault}",
37
+ :yellow
38
+ )
39
+ end
40
+ end
41
+ end
42
+
43
+ def batch_send(url, messages)
44
+ messages.to_a.flatten.map(&method(:normalize_dump_message)).each_slice(10) do |batch|
45
+ sqs.send_message_batch(queue_url: url, entries: batch).failed.any? do |failure|
46
+ say "Could not requeue #{failure.id}, code: #{failure.code}", :yellow
47
+ end
48
+ end
49
+ end
50
+
51
+ def find_all(url, limit, &block)
52
+ count = 0
53
+ batch_size = limit > 10 ? 10 : limit
54
+
55
+ loop do
56
+ n = limit - count
57
+ batch_size = n if n < batch_size
58
+
59
+ messages = sqs.receive_message(
60
+ queue_url: url,
61
+ max_number_of_messages: batch_size,
62
+ message_attribute_names: ['All']
63
+ ).messages
64
+
65
+ messages.each { |m| yield m }
66
+
67
+ count += messages.size
68
+
69
+ break if count >= limit
70
+ break if messages.empty?
71
+ end
72
+
73
+ count
74
+ end
75
+
76
+ def list_and_print_queues(urls)
77
+ attrs = %w(QueueArn ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible LastModifiedTimestamp)
78
+
79
+ entries = urls.map { |u| sqs.get_queue_attributes(queue_url: u, attribute_names: attrs).attributes }.map do |q|
80
+ [
81
+ q['QueueArn'].split(':').last,
82
+ q['ApproximateNumberOfMessages'],
83
+ q['ApproximateNumberOfMessagesNotVisible'],
84
+ Time.at(q['LastModifiedTimestamp'].to_i)
85
+ ]
86
+ end
87
+
88
+ entries.unshift(['Queue', 'Messages Available', 'Messages Inflight', 'Last Modified'])
89
+
90
+ print_table(entries)
91
+ end
92
+
93
+ def dump_file(path, queue_name)
94
+ File.join(path, "#{queue_name}-#{Date.today}.jsonl")
95
+ end
96
+ end
97
+
98
+ desc 'ls [QUEUE-NAME-PREFIX]', 'Lists queues'
99
+ method_option :watch, aliases: '-w', type: :boolean, desc: 'watch queues'
100
+ method_option :watch_interval, type: :numeric, default: 10, desc: 'watch interval'
101
+ def ls(queue_name_prefix = '')
102
+ trap('SIGINT', 'EXIT') # expect ctrl-c from loop
103
+
104
+ urls = sqs.list_queues(queue_name_prefix: queue_name_prefix).queue_urls
105
+
106
+ loop do
107
+ list_and_print_queues(urls)
108
+
109
+ break unless options[:watch]
110
+
111
+ sleep options[:watch_interval]
112
+ puts
113
+ end
114
+ end
115
+
116
+ desc 'dump QUEUE-NAME', 'Dumps messages from a queue into a JSON lines file'
117
+ method_option :number, aliases: '-n', type: :numeric, default: Float::INFINITY, desc: 'number of messages to dump'
118
+ method_option :path, aliases: '-p', type: :string, default: './', desc: 'path to save the dump file'
119
+ method_option :delete, aliases: '-d', type: :boolean, default: true, desc: 'delete from the queue'
120
+ def dump(queue_name)
121
+ path = dump_file(options[:path], queue_name)
122
+
123
+ fail_task "File #{path} already exists" if File.exist?(path)
124
+
125
+ url = find_queue_url(queue_name)
126
+
127
+ messages = []
128
+
129
+ file = nil
130
+
131
+ count = find_all(url, options[:number]) do |m|
132
+ file ||= File.open(path, 'w')
133
+
134
+ file.puts(JSON.dump(m.to_h))
135
+
136
+ messages << m if options[:delete]
137
+ end
138
+
139
+ batch_delete(url, messages) if options[:delete]
140
+
141
+ if count.zero?
142
+ say "Queue #{queue_name} is empty", :yellow
143
+ else
144
+ say "Dump saved in #{path} with #{count} messages", :green
145
+ end
146
+ ensure
147
+ file.close if file
148
+ end
149
+
150
+ desc 'requeue QUEUE-NAME PATH', 'Requeues messages from a dump file'
151
+ def requeue(queue_name, path)
152
+ fail_task "Path #{path} not found" unless File.exist?(path)
153
+
154
+ messages = File.readlines(path).map { |line| JSON.parse(line, symbolize_names: true) }
155
+
156
+ batch_send(find_queue_url(queue_name), messages)
157
+
158
+ say "Requeued #{messages.size} messages from #{path} to #{queue_name}", :green
159
+ end
160
+
161
+ desc 'mv QUEUE-NAME-SOURCE QUEUE-NAME-TARGET', 'Moves messages from one queue (source) to another (target)'
162
+ method_option :number, aliases: '-n', type: :numeric, default: Float::INFINITY, desc: 'number of messages to move'
163
+ method_option :delete, aliases: '-d', type: :boolean, default: true, desc: 'delete from the queue'
164
+ def mv(queue_name_source, queue_name_target)
165
+ url_source = find_queue_url(queue_name_source)
166
+ messages = []
167
+
168
+ count = find_all(url_source, options[:number]) do |m|
169
+ messages << m
170
+ end
171
+
172
+ batch_send(find_queue_url(queue_name_target), messages.map(&:to_h))
173
+ batch_delete(url_source, messages) if options[:delete]
174
+
175
+ if count.zero?
176
+ say "Queue #{queue_name_source} is empty", :yellow
177
+ else
178
+ say "Moved #{count} messages from #{queue_name_source} to #{queue_name_target}", :green
179
+ end
180
+ end
181
+
182
+ desc 'purge QUEUE-NAME', 'Deletes the messages in a queue'
183
+ def purge(queue_name)
184
+ sqs.purge_queue(queue_url: find_queue_url(queue_name))
185
+
186
+ say "Purge request sent for #{queue_name}. The message deletion process takes up to 60 seconds", :yellow
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
202
+ end
203
+ end
204
+ end
data/bin/shoryuken CHANGED
@@ -1,12 +1,58 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/shoryuken/cli'
4
-
5
- begin
6
- Shoryuken::CLI.instance.run(ARGV)
7
- rescue => e
8
- raise e if $DEBUG
9
- STDERR.puts e.message
10
- STDERR.puts e.backtrace.join("\n")
11
- exit 1
3
+ require 'rubygems'
4
+
5
+ require 'thor'
6
+ require 'aws-sdk-core'
7
+ require_relative 'cli/base'
8
+ require_relative 'cli/sqs'
9
+ require_relative '../lib/shoryuken/runner'
10
+
11
+ # rubocop:disable Metrics/AbcSize
12
+ module Shoryuken
13
+ module CLI
14
+ class Runner < Base
15
+ default_task :start
16
+
17
+ register(Shoryuken::CLI::SQS, 'sqs', 'sqs COMMAND', 'SQS commands')
18
+
19
+ desc 'start', 'Starts shoryuken'
20
+ method_option :concurrency, aliases: '-c', type: :numeric, desc: 'Processor threads to use'
21
+ method_option :daemon, aliases: '-d', type: :boolean, desc: 'Daemonize process'
22
+ method_option :queues, aliases: '-q', type: :array, desc: 'Queues to process with optional weights'
23
+ method_option :require, aliases: '-r', type: :string, desc: 'Dir or path of the workers'
24
+ method_option :config, aliases: '-C', type: :string, desc: 'Path to config file'
25
+ method_option :config_file, type: :string, desc: 'Path to config file (backwards compatibility)'
26
+ method_option :rails, aliases: '-R', type: :boolean, desc: 'Load Rails'
27
+ method_option :logfile, aliases: '-L', type: :string, desc: 'Path to logfile'
28
+ method_option :pidfile, aliases: '-P', type: :string, desc: 'Path to pidfile'
29
+ method_option :verbose, aliases: '-v', type: :boolean, desc: 'Print more verbose output'
30
+ method_option :delay, aliases: '-D', type: :numeric, desc: 'Number of seconds to pause fetching from an empty queue'
31
+ def start
32
+ opts = options.to_h.symbolize_keys
33
+
34
+ if opts[:config_file]
35
+ say "[DEPRECATED] Please use --config instead of --config-file", :yellow
36
+ end
37
+
38
+ opts[:config_file] = opts.delete(:config) if opts[:config]
39
+
40
+ # Keep compatibility with old CLI queue format
41
+ opts[:queues] = options[:queues].map { |q| q.split(',') } if options[:queues]
42
+
43
+ if options[:daemon] && options[:logfile].nil?
44
+ fail_task "You should set a logfile if you're going to daemonize"
45
+ end
46
+
47
+ Shoryuken::Runner.instance.run(opts.freeze)
48
+ end
49
+
50
+ desc 'version', 'Prints version'
51
+ def version
52
+ say "Shoryuken #{Shoryuken::VERSION}"
53
+ end
54
+ end
55
+ end
12
56
  end
57
+
58
+ Shoryuken::CLI::Runner.start
@@ -4,6 +4,6 @@ class DefaultWorker
4
4
  shoryuken_options queue: 'default', auto_delete: true
5
5
 
6
6
  def perform(sqs_msg, body)
7
- Shoryuken.logger.info("Received message: '#{body}'")
7
+ Shoryuken.logger.debug("Received message: #{body}")
8
8
  end
9
9
  end
@@ -1,31 +1,19 @@
1
1
  module Shoryuken
2
2
  class Client
3
3
  @@queues = {}
4
- @@topics = {}
5
4
 
6
5
  class << self
7
6
  def queues(name)
8
7
  @@queues[name.to_s] ||= Shoryuken::Queue.new(sqs, name)
9
8
  end
10
9
 
11
- def sns
12
- @sns ||= Shoryuken::AwsConfig.sns
13
- end
14
-
15
- def sns_arn
16
- @sns_arn ||= SnsArn
17
- end
18
-
19
10
  def sqs
20
- @sqs ||= Shoryuken::AwsConfig.sqs
11
+ @@sqs ||= Shoryuken.sqs_client
21
12
  end
22
13
 
23
- def topics(name)
24
- @@topics[name.to_s] ||= Topic.new(name, sns)
14
+ def sqs=(sqs)
15
+ @@sqs = sqs
25
16
  end
26
-
27
- attr_accessor :account_id
28
- attr_writer :sns, :sqs, :sqs_resource, :sns_arn
29
17
  end
30
18
  end
31
19
  end
@@ -14,13 +14,17 @@ module Shoryuken
14
14
 
15
15
  def fetch_worker(queue, message)
16
16
  worker_class = !message.is_a?(Array) &&
17
- message.message_attributes &&
18
- message.message_attributes['shoryuken_class'] &&
19
- message.message_attributes['shoryuken_class'][:string_value]
17
+ message.message_attributes &&
18
+ message.message_attributes['shoryuken_class'] &&
19
+ message.message_attributes['shoryuken_class'][:string_value]
20
20
 
21
- worker_class = (worker_class.constantize rescue nil) || @workers[queue]
21
+ worker_class = begin
22
+ worker_class.constantize
23
+ rescue
24
+ @workers[queue]
25
+ end
22
26
 
23
- worker_class.new
27
+ worker_class.new if worker_class
24
28
  end
25
29
 
26
30
  def queues
@@ -30,7 +34,7 @@ module Shoryuken
30
34
  def register_worker(queue, clazz)
31
35
  if (worker_class = @workers[queue])
32
36
  if worker_class.get_shoryuken_options['batch'] == true || clazz.get_shoryuken_options['batch'] == true
33
- fail ArgumentError, "Could not register #{clazz} for '#{queue}', "\
37
+ fail ArgumentError, "Could not register #{clazz} for #{queue}, "\
34
38
  "because #{worker_class} is already registered for this queue, "\
35
39
  "and Shoryuken doesn't support a batchable worker for a queue with multiple workers"
36
40
  end
@@ -24,14 +24,12 @@ 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
31
- initialize_aws
32
31
  validate_queues
33
32
  validate_workers
34
- patch_deprecated_workers
35
33
  end
36
34
 
37
35
  private
@@ -44,24 +42,18 @@ module Shoryuken
44
42
  def config_file_options
45
43
  return {} unless (path = options[:config_file])
46
44
 
47
- 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)
48
46
 
49
- YAML.load(ERB.new(IO.read(path)).result).deep_symbolize_keys
50
- end
51
-
52
- # DEPRECATED: Please use configure_server and configure_client in
53
- # https://github.com/phstc/shoryuken/blob/a81637d577b36c5cf245882733ea91a335b6602f/lib/shoryuken.rb#L82
54
- # Please delete this method afert next release (v2.0.12 or later)
55
- def initialize_aws
56
- unless Shoryuken.options[:aws].to_h.empty?
57
- Shoryuken.logger.warn { '[DEPRECATION] aws in shoryuken.yml is deprecated. Please use configure_server and configure_client in your initializer' }
47
+ if (result = YAML.load(ERB.new(IO.read(path)).result))
48
+ result.deep_symbolize_keys
49
+ else
50
+ {}
58
51
  end
59
- Shoryuken::AwsConfig.setup(Shoryuken.options[:aws])
60
52
  end
61
53
 
62
54
  def initialize_logger
63
- Shoryuken::Logging.initialize_logger(options[:logfile]) if options[:logfile]
64
- Shoryuken.logger.level = Logger::DEBUG if options[:verbose]
55
+ Shoryuken::Logging.initialize_logger(Shoryuken.options[:logfile]) if Shoryuken.options[:logfile]
56
+ Shoryuken.logger.level = Logger::DEBUG if Shoryuken.options[:verbose]
65
57
  end
66
58
 
67
59
  def load_rails
@@ -77,62 +69,68 @@ module Shoryuken
77
69
  ::Rails::Application.initializer 'shoryuken.eager_load' do
78
70
  ::Rails.application.config.eager_load = true
79
71
  end
80
- require 'shoryuken/extensions/active_job_adapter' if defined?(::ActiveJob)
72
+ require 'shoryuken/extensions/active_job_adapter' if Shoryuken.active_job?
81
73
  require File.expand_path('config/environment.rb')
82
74
  end
83
75
  end
84
76
 
85
77
  def merge_cli_defined_queues
86
- cli_defined_queues = options.delete(:queues) || []
78
+ cli_defined_queues = options[:queues].to_a
87
79
 
88
80
  cli_defined_queues.each do |cli_defined_queue|
89
- Shoryuken.options[:queues].delete_if do |config_file_queue|
90
- config_file_queue[0] == cli_defined_queue[0]
91
- end
81
+ # CLI defined queues override config_file defined queues
82
+ Shoryuken.options[:queues].delete_if { |config_file_queue| config_file_queue[0] == cli_defined_queue[0] }
92
83
 
93
84
  Shoryuken.options[:queues] << cli_defined_queue
94
85
  end
95
86
  end
96
87
 
97
- def prefix_active_job_queue_names
98
- return unless defined? ::ActiveJob
99
- return unless Shoryuken.active_job_queue_name_prefixing
100
-
88
+ def prefix_active_job_queue_name(queue_name, weight)
101
89
  queue_name_prefix = ::ActiveJob::Base.queue_name_prefix
102
90
  queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
103
91
 
104
92
  # See https://github.com/rails/rails/blob/master/activejob/lib/active_job/queue_name.rb#L27
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]
96
+ end
97
+
98
+ def prefix_active_job_queue_names
99
+ return unless Shoryuken.active_job?
100
+ return unless Shoryuken.active_job_queue_name_prefixing
101
+
105
102
  Shoryuken.options[:queues].to_a.map! do |queue_name, weight|
106
- name_parts = [queue_name_prefix.presence, queue_name]
107
- prefixed_queue_name = name_parts.compact.join(queue_name_delimiter)
108
- [prefixed_queue_name, weight]
103
+ prefix_active_job_queue_name(queue_name, weight)
109
104
  end
110
- end
111
105
 
112
- def parse_queue(queue, weight = nil)
113
- [weight.to_i, 1].max.times { Shoryuken.queues << queue }
114
- end
106
+ Shoryuken.options[:groups].to_a.map! do |group, options|
107
+ if options[:queues]
108
+ options[:queues].map! do |queue_name, weight|
109
+ prefix_active_job_queue_name(queue_name, weight)
110
+ end
111
+ end
115
112
 
116
- def parse_queues
117
- Shoryuken.options[:queues].to_a.each do |queue_and_weight|
118
- parse_queue(*queue_and_weight)
113
+ [group, options]
119
114
  end
120
115
  end
121
116
 
122
- def patch_deprecated_workers
123
- Shoryuken.worker_registry.queues.each do |queue|
124
- Shoryuken.worker_registry.workers(queue).each do |worker_class|
125
- if worker_class.instance_method(:perform).arity == 1
126
- Shoryuken.logger.warn { "[DEPRECATION] #{worker_class.name}#perform(sqs_msg) is deprecated. Please use #{worker_class.name}#perform(sqs_msg, body)" }
117
+ def parse_queue(queue, weight, group)
118
+ Shoryuken.add_queue(queue, [weight.to_i, 1].max, group)
119
+ end
127
120
 
128
- worker_class.class_eval do
129
- alias_method :deprecated_perform, :perform
121
+ def parse_queues
122
+ if Shoryuken.options[:queues].to_a.any?
123
+ Shoryuken.add_group('default', Shoryuken.options.fetch(:concurrency, 25))
130
124
 
131
- def perform(sqs_msg, body = nil)
132
- deprecated_perform(sqs_msg)
133
- end
134
- end
135
- end
125
+ Shoryuken.options[:queues].to_a.each do |queue, weight|
126
+ parse_queue(queue, weight, 'default')
127
+ end
128
+ end
129
+
130
+ Shoryuken.options[:groups].to_a.each do |group, options|
131
+ Shoryuken.add_group(group, options.fetch(:concurrency, 25))
132
+ options[:queues].to_a.each do |queue, weight|
133
+ parse_queue(queue, weight, group)
136
134
  end
137
135
  end
138
136
  end
@@ -150,29 +148,34 @@ module Shoryuken
150
148
  end
151
149
 
152
150
  def validate_queues
153
- Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.queues.empty?
151
+ return Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.ungrouped_queues.empty?
154
152
 
155
153
  non_existent_queues = []
156
154
 
157
- Shoryuken.queues.uniq.each do |queue|
155
+ Shoryuken.ungrouped_queues.uniq.each do |queue|
158
156
  begin
159
- Shoryuken::Client.queues queue
160
- rescue Aws::SQS::Errors::NonExistentQueue
157
+ Shoryuken::Client.queues(queue)
158
+ rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue
161
159
  non_existent_queues << queue
162
160
  end
163
161
  end
164
162
 
165
- fail ArgumentError, "The specified queue(s) #{non_existent_queues} do not exist" if non_existent_queues.any?
163
+ return if non_existent_queues.none?
164
+
165
+ fail(
166
+ ArgumentError,
167
+ "The specified queue(s) #{non_existent_queues.join(', ')} do not exist.\nTry 'shoryuken sqs create QUEUE-NAME' for creating a queue with default settings"
168
+ )
166
169
  end
167
170
 
168
171
  def validate_workers
169
- all_queues = Shoryuken.queues
172
+ return if Shoryuken.active_job?
173
+
174
+ all_queues = Shoryuken.ungrouped_queues
170
175
  queues_with_workers = Shoryuken.worker_registry.queues
171
176
 
172
- unless defined?(::ActiveJob)
173
- (all_queues - queues_with_workers).each do |queue|
174
- Shoryuken.logger.warn { "No worker supplied for '#{queue}'" }
175
- end
177
+ (all_queues - queues_with_workers).each do |queue|
178
+ Shoryuken.logger.warn { "No worker supplied for #{queue}" }
176
179
  end
177
180
  end
178
181
  end
@@ -1,73 +1,43 @@
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
7
+ attr_reader :group
15
8
 
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
9
+ def initialize(group)
10
+ @group = group
22
11
  end
23
12
 
24
- def fetch(queue, available_processors)
25
- watchdog('Fetcher#fetch died') do
26
- started_at = Time.now
27
-
28
- logger.debug { "Looking for new messages in '#{queue}'" }
13
+ def fetch(queue, limit)
14
+ started_at = Time.now
29
15
 
30
- begin
31
- batch = Shoryuken.worker_registry.batch_receive_messages?(queue)
32
- limit = batch ? FETCH_LIMIT : available_processors
16
+ logger.debug { "Looking for new messages in #{queue}" }
33
17
 
34
- if (sqs_msgs = Array(receive_messages(queue, limit))).any?
35
- logger.debug { "Found #{sqs_msgs.size} messages for '#{queue}'" }
18
+ sqs_msgs = Array(receive_messages(queue, [FETCH_LIMIT, limit].min))
36
19
 
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
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" }
42
22
 
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
23
+ sqs_msgs
24
+ end
49
25
 
50
- logger.debug { "Fetcher for '#{queue}' completed in #{elapsed(started_at)} ms" }
51
- rescue => ex
52
- logger.error { "Error fetching message: #{ex}" }
53
- logger.error { ex.backtrace.first }
54
- end
26
+ private
55
27
 
56
- @manager.async.dispatch
57
- end
28
+ def receive_messages(queue, limit)
29
+ # AWS limits the batch size by 10
30
+ limit = limit > FETCH_LIMIT ? FETCH_LIMIT : limit
58
31
 
59
- end
32
+ options = Shoryuken.sqs_client_receive_message_opts[group].to_h.dup
60
33
 
61
- private
34
+ options[:max_number_of_messages] = limit
35
+ options[:message_attribute_names] = %w(All)
36
+ options[:attribute_names] = %w(All)
62
37
 
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
38
+ options.merge!(queue.options)
69
39
 
70
- sqs_msgs
40
+ Shoryuken::Client.queues(queue.name).receive_messages(options)
71
41
  end
72
42
  end
73
43
  end