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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +5 -8
- data/.rubocop.yml +8 -2
- data/.travis.yml +6 -0
- data/CHANGELOG.md +167 -0
- data/Gemfile +2 -0
- data/README.md +16 -155
- data/Rakefile +0 -1
- data/bin/cli/base.rb +40 -0
- data/bin/cli/sqs.rb +204 -0
- data/bin/shoryuken +55 -9
- data/examples/default_worker.rb +1 -1
- data/lib/shoryuken/client.rb +3 -15
- data/lib/shoryuken/default_worker_registry.rb +10 -6
- data/lib/shoryuken/environment_loader.rb +60 -57
- data/lib/shoryuken/fetcher.rb +21 -51
- data/lib/shoryuken/launcher.rb +77 -23
- data/lib/shoryuken/logging.rb +0 -5
- data/lib/shoryuken/manager.rb +54 -205
- data/lib/shoryuken/message.rb +4 -13
- data/lib/shoryuken/middleware/chain.rb +1 -18
- data/lib/shoryuken/middleware/server/auto_delete.rb +3 -8
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +16 -17
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +37 -22
- data/lib/shoryuken/middleware/server/timing.rb +2 -2
- data/lib/shoryuken/options.rb +196 -0
- data/lib/shoryuken/polling/base.rb +67 -0
- data/lib/shoryuken/polling/strict_priority.rb +77 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +66 -0
- data/lib/shoryuken/processor.rb +27 -25
- data/lib/shoryuken/queue.rb +28 -9
- data/lib/shoryuken/runner.rb +131 -0
- data/lib/shoryuken/util.rb +1 -9
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker.rb +9 -1
- data/lib/shoryuken.rb +47 -157
- data/shoryuken.gemspec +7 -7
- data/spec/integration/launcher_spec.rb +15 -8
- data/spec/shoryuken/client_spec.rb +2 -45
- data/spec/shoryuken/default_worker_registry_spec.rb +12 -10
- data/spec/shoryuken/environment_loader_spec.rb +54 -0
- data/spec/shoryuken/fetcher_spec.rb +27 -46
- data/spec/shoryuken/manager_spec.rb +80 -93
- data/spec/shoryuken/middleware/chain_spec.rb +0 -24
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +2 -2
- data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +7 -3
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +56 -33
- data/spec/shoryuken/middleware/server/timing_spec.rb +5 -3
- data/spec/shoryuken/options_spec.rb +100 -0
- data/spec/shoryuken/polling/strict_priority_spec.rb +140 -0
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +99 -0
- data/spec/shoryuken/processor_spec.rb +20 -37
- data/spec/shoryuken/queue_spec.rb +72 -26
- data/spec/shoryuken/{cli_spec.rb → runner_spec.rb} +11 -26
- data/spec/shoryuken_spec.rb +1 -48
- data/spec/spec_helper.rb +14 -20
- data/test_workers/endless_interruptive_worker.rb +41 -0
- data/test_workers/endless_uninterruptive_worker.rb +44 -0
- metadata +39 -34
- data/lib/shoryuken/aws_config.rb +0 -64
- data/lib/shoryuken/cli.rb +0 -215
- data/lib/shoryuken/sns_arn.rb +0 -27
- data/lib/shoryuken/topic.rb +0 -17
- data/spec/shoryuken/sns_arn_spec.rb +0 -42
- data/spec/shoryuken/topic_spec.rb +0 -32
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
data/examples/default_worker.rb
CHANGED
data/lib/shoryuken/client.rb
CHANGED
|
@@ -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
|
-
|
|
11
|
+
@@sqs ||= Shoryuken.sqs_client
|
|
21
12
|
end
|
|
22
13
|
|
|
23
|
-
def
|
|
24
|
-
@@
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
message.message_attributes &&
|
|
18
|
+
message.message_attributes['shoryuken_class'] &&
|
|
19
|
+
message.message_attributes['shoryuken_class'][:string_value]
|
|
20
20
|
|
|
21
|
-
worker_class =
|
|
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
|
|
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
|
|
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)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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
|
|
78
|
+
cli_defined_queues = options[:queues].to_a
|
|
87
79
|
|
|
88
80
|
cli_defined_queues.each do |cli_defined_queue|
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
|
123
|
-
Shoryuken.
|
|
124
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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.
|
|
151
|
+
return Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.ungrouped_queues.empty?
|
|
154
152
|
|
|
155
153
|
non_existent_queues = []
|
|
156
154
|
|
|
157
|
-
Shoryuken.
|
|
155
|
+
Shoryuken.ungrouped_queues.uniq.each do |queue|
|
|
158
156
|
begin
|
|
159
|
-
Shoryuken::Client.queues
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
data/lib/shoryuken/fetcher.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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,
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
logger.debug { "Found #{sqs_msgs.size} messages for '#{queue}'" }
|
|
18
|
+
sqs_msgs = Array(receive_messages(queue, [FETCH_LIMIT, limit].min))
|
|
36
19
|
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
51
|
-
rescue => ex
|
|
52
|
-
logger.error { "Error fetching message: #{ex}" }
|
|
53
|
-
logger.error { ex.backtrace.first }
|
|
54
|
-
end
|
|
26
|
+
private
|
|
55
27
|
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
32
|
+
options = Shoryuken.sqs_client_receive_message_opts[group].to_h.dup
|
|
60
33
|
|
|
61
|
-
|
|
34
|
+
options[:max_number_of_messages] = limit
|
|
35
|
+
options[:message_attribute_names] = %w(All)
|
|
36
|
+
options[:attribute_names] = %w(All)
|
|
62
37
|
|
|
63
|
-
|
|
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
|
-
|
|
40
|
+
Shoryuken::Client.queues(queue.name).receive_messages(options)
|
|
71
41
|
end
|
|
72
42
|
end
|
|
73
43
|
end
|