shoryuken 1.0.0 → 2.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.hound.yml +6 -2
- data/.rubocop.yml +50 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +4 -1
- data/README.md +6 -52
- data/examples/bootstrap_queues.rb +1 -1
- data/lib/shoryuken/cli.rb +14 -14
- data/lib/shoryuken/client.rb +4 -7
- data/lib/shoryuken/default_worker_registry.rb +1 -1
- data/lib/shoryuken/environment_loader.rb +34 -16
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -1
- data/lib/shoryuken/fetcher.rb +8 -6
- data/lib/shoryuken/launcher.rb +1 -1
- data/lib/shoryuken/logging.rb +4 -6
- data/lib/shoryuken/manager.rb +13 -13
- data/lib/shoryuken/message.rb +70 -0
- data/lib/shoryuken/middleware/chain.rb +2 -2
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +50 -0
- data/lib/shoryuken/middleware/server/timing.rb +4 -4
- data/lib/shoryuken/processor.rb +28 -14
- data/lib/shoryuken/queue.rb +84 -0
- data/lib/shoryuken/sns_arn.rb +3 -3
- data/lib/shoryuken/util.rb +5 -6
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker.rb +1 -2
- data/lib/shoryuken.rb +29 -7
- data/shoryuken.gemspec +15 -16
- data/spec/shoryuken/client_spec.rb +45 -4
- data/spec/shoryuken/default_worker_registry_spec.rb +1 -1
- data/spec/shoryuken/fetcher_spec.rb +6 -6
- data/spec/shoryuken/middleware/chain_spec.rb +3 -1
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +2 -2
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +77 -0
- data/spec/shoryuken/middleware/server/timing_spec.rb +23 -9
- data/spec/shoryuken/processor_spec.rb +39 -13
- data/spec/shoryuken/queue_spec.rb +100 -0
- data/spec/shoryuken/util_spec.rb +1 -1
- data/spec/shoryuken/worker_spec.rb +0 -13
- data/spec/shoryuken_endpoint.yml +6 -0
- data/spec/spec_helper.rb +4 -4
- metadata +36 -39
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Shoryuken
|
|
2
|
+
class Message
|
|
3
|
+
attr_accessor :client, :queue_url, :queue_name, :data
|
|
4
|
+
|
|
5
|
+
def initialize(client, queue, data)
|
|
6
|
+
self.client = client
|
|
7
|
+
self.data = data
|
|
8
|
+
|
|
9
|
+
if queue.is_a?(Shoryuken::Queue)
|
|
10
|
+
self.queue_url = queue.url
|
|
11
|
+
self.queue_name = queue.name
|
|
12
|
+
else
|
|
13
|
+
# TODO: Remove next major release
|
|
14
|
+
Shoryuken.loggger.warn do
|
|
15
|
+
'[DEPRECATION] Passing a queue url into Shoryuken::Message is deprecated, please pass the queue itself'
|
|
16
|
+
end
|
|
17
|
+
self.queue_url = queue
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def delete
|
|
22
|
+
client.delete_message(
|
|
23
|
+
queue_url: queue_url,
|
|
24
|
+
receipt_handle: data.receipt_handle
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def change_visibility(options)
|
|
29
|
+
client.change_message_visibility(
|
|
30
|
+
options.merge(queue_url: queue_url, receipt_handle: data.receipt_handle)
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def visibility_timeout=(timeout)
|
|
35
|
+
client.change_message_visibility(
|
|
36
|
+
queue_url: queue_url,
|
|
37
|
+
receipt_handle: data.receipt_handle,
|
|
38
|
+
visibility_timeout: timeout
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def message_id
|
|
43
|
+
data.message_id
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def receipt_handle
|
|
47
|
+
data.receipt_handle
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def md5_of_body
|
|
51
|
+
data.md5_of_body
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def body
|
|
55
|
+
data.body
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def attributes
|
|
59
|
+
data.attributes
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def md5_of_message_attributes
|
|
63
|
+
data.md5_of_message_attributes
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def message_attributes
|
|
67
|
+
data.message_attributes
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -72,7 +72,7 @@ module Shoryuken
|
|
|
72
72
|
i = entries.index { |entry| entry.klass == newklass }
|
|
73
73
|
new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
|
|
74
74
|
i = entries.find_index { |entry| entry.klass == oldklass } || entries.count - 1
|
|
75
|
-
entries.insert(i+1, new_entry)
|
|
75
|
+
entries.insert(i + 1, new_entry)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def exists?(klass)
|
|
@@ -117,7 +117,7 @@ module Shoryuken
|
|
|
117
117
|
|
|
118
118
|
def patch_deprecated_middleware!(klass)
|
|
119
119
|
if klass.instance_method(:call).arity == 3
|
|
120
|
-
Shoryuken.logger.warn "[DEPRECATION] #{klass.name}#call(worker_instance, queue, sqs_msg) is deprecated. Please use #{klass.name}#call(worker_instance, queue, sqs_msg, body)"
|
|
120
|
+
Shoryuken.logger.warn { "[DEPRECATION] #{klass.name}#call(worker_instance, queue, sqs_msg) is deprecated. Please use #{klass.name}#call(worker_instance, queue, sqs_msg, body)" }
|
|
121
121
|
|
|
122
122
|
klass.class_eval do
|
|
123
123
|
alias_method :deprecated_call, :call
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Shoryuken
|
|
2
|
+
module Middleware
|
|
3
|
+
module Server
|
|
4
|
+
class ExponentialBackoffRetry
|
|
5
|
+
include Util
|
|
6
|
+
|
|
7
|
+
def call(worker, queue, sqs_msg, body)
|
|
8
|
+
started_at = Time.now
|
|
9
|
+
yield
|
|
10
|
+
rescue
|
|
11
|
+
retry_intervals = Array(worker.class.get_shoryuken_options['retry_intervals'])
|
|
12
|
+
|
|
13
|
+
if retry_intervals.empty? || !handle_failure(sqs_msg, started_at, retry_intervals)
|
|
14
|
+
# Re-raise the exception if the job is not going to be exponential backoff retried.
|
|
15
|
+
# This allows custom middleware (like exception notifiers) to be aware of the unhandled failure.
|
|
16
|
+
raise
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def handle_failure(sqs_msg, started_at, retry_intervals)
|
|
23
|
+
attempts = sqs_msg.attributes['ApproximateReceiveCount']
|
|
24
|
+
|
|
25
|
+
return unless attempts
|
|
26
|
+
|
|
27
|
+
attempts = attempts.to_i - 1
|
|
28
|
+
|
|
29
|
+
interval = if attempts < retry_intervals.size
|
|
30
|
+
retry_intervals[attempts]
|
|
31
|
+
else
|
|
32
|
+
retry_intervals.last
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Visibility timeouts are limited to a total 12 hours, starting from the receipt of the message.
|
|
36
|
+
# We calculate the maximum timeout by subtracting the amount of time since the receipt of the message.
|
|
37
|
+
#
|
|
38
|
+
# From the docs: "Amazon SQS restarts the timeout period using the new value."
|
|
39
|
+
# http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html#AboutVT-extending-message-visibility-timeout
|
|
40
|
+
max_timeout = 43200 - (Time.now - started_at).ceil - 1
|
|
41
|
+
interval = max_timeout if interval > max_timeout
|
|
42
|
+
|
|
43
|
+
sqs_msg.change_visibility(visibility_timeout: interval.to_i)
|
|
44
|
+
|
|
45
|
+
logger.info { "Message #{sqs_msg.message_id} failed, will be retried in #{interval} seconds." }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -9,19 +9,19 @@ module Shoryuken
|
|
|
9
9
|
begin
|
|
10
10
|
started_at = Time.now
|
|
11
11
|
|
|
12
|
-
logger.info "started at #{started_at}"
|
|
12
|
+
logger.info { "started at #{started_at}" }
|
|
13
13
|
|
|
14
14
|
yield
|
|
15
15
|
|
|
16
16
|
total_time = elapsed(started_at)
|
|
17
17
|
|
|
18
18
|
if (total_time / 1000.0) > (timeout = Shoryuken::Client.queues(queue).visibility_timeout)
|
|
19
|
-
logger.warn "exceeded the queue visibility timeout by #{total_time - (timeout * 1000)} ms"
|
|
19
|
+
logger.warn { "exceeded the queue visibility timeout by #{total_time - (timeout * 1000)} ms" }
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
logger.info "completed in: #{total_time} ms"
|
|
22
|
+
logger.info { "completed in: #{total_time} ms" }
|
|
23
23
|
rescue => e
|
|
24
|
-
logger.info "failed in: #{elapsed(started_at)} ms"
|
|
24
|
+
logger.info { "failed in: #{elapsed(started_at)} ms" }
|
|
25
25
|
raise e
|
|
26
26
|
end
|
|
27
27
|
end
|
data/lib/shoryuken/processor.rb
CHANGED
|
@@ -19,12 +19,10 @@ module Shoryuken
|
|
|
19
19
|
timer = auto_visibility_timeout(queue, sqs_msg, worker.class)
|
|
20
20
|
|
|
21
21
|
begin
|
|
22
|
-
|
|
23
|
-
body = get_body(worker.class, sqs_msg)
|
|
22
|
+
body = get_body(worker.class, sqs_msg)
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
24
|
+
worker.class.server_middleware.invoke(worker, queue, sqs_msg, body) do
|
|
25
|
+
worker.perform(sqs_msg, body)
|
|
28
26
|
end
|
|
29
27
|
|
|
30
28
|
@manager.async.processor_done(queue, current_actor)
|
|
@@ -35,22 +33,31 @@ module Shoryuken
|
|
|
35
33
|
|
|
36
34
|
private
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
class MessageVisibilityExtender
|
|
37
|
+
include Celluloid
|
|
38
|
+
include Util
|
|
39
|
+
|
|
40
|
+
def auto_extend(queue, sqs_msg, worker_class)
|
|
40
41
|
queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
every(queue_visibility_timeout - 5) do
|
|
43
44
|
begin
|
|
44
|
-
logger.debug "Extending message #{worker_name(worker_class, sqs_msg)}/#{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s."
|
|
45
|
+
logger.debug { "Extending message #{worker_name(worker_class, sqs_msg)}/#{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s." }
|
|
45
46
|
|
|
46
47
|
sqs_msg.visibility_timeout = queue_visibility_timeout
|
|
47
48
|
rescue => e
|
|
48
|
-
logger.error "Could not auto extend the message #{worker_class}/#{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{e.message}"
|
|
49
|
+
logger.error { "Could not auto extend the message #{worker_class}/#{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{e.message}" }
|
|
49
50
|
end
|
|
50
51
|
end
|
|
51
52
|
end
|
|
53
|
+
end
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
def auto_visibility_timeout(queue, sqs_msg, worker_class)
|
|
56
|
+
return unless worker_class.auto_visibility_timeout?
|
|
57
|
+
|
|
58
|
+
@visibility_extender ||= MessageVisibilityExtender.new_link
|
|
59
|
+
|
|
60
|
+
@visibility_extender.auto_extend(queue, sqs_msg, worker_class)
|
|
54
61
|
end
|
|
55
62
|
|
|
56
63
|
def get_body(worker_class, sqs_msg)
|
|
@@ -72,11 +79,18 @@ module Shoryuken
|
|
|
72
79
|
when :text, nil
|
|
73
80
|
sqs_msg.body
|
|
74
81
|
else
|
|
75
|
-
|
|
82
|
+
if body_parser.respond_to?(:parse)
|
|
83
|
+
# JSON.parse
|
|
84
|
+
body_parser.parse(sqs_msg.body)
|
|
85
|
+
elsif body_parser.respond_to?(:load)
|
|
86
|
+
# see https://github.com/phstc/shoryuken/pull/91
|
|
87
|
+
# JSON.load
|
|
88
|
+
body_parser.load(sqs_msg.body)
|
|
89
|
+
end
|
|
76
90
|
end
|
|
77
91
|
rescue => e
|
|
78
|
-
logger.error "Error parsing the message body: #{e.message}\nbody_parser: #{body_parser}\nsqs_msg.body: #{sqs_msg.body}"
|
|
79
|
-
|
|
92
|
+
logger.error { "Error parsing the message body: #{e.message}\nbody_parser: #{body_parser}\nsqs_msg.body: #{sqs_msg.body}" }
|
|
93
|
+
raise
|
|
80
94
|
end
|
|
81
95
|
end
|
|
82
96
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Shoryuken
|
|
2
|
+
class Queue
|
|
3
|
+
attr_accessor :name, :client, :url
|
|
4
|
+
|
|
5
|
+
def initialize(client, name)
|
|
6
|
+
self.name = name
|
|
7
|
+
self.client = client
|
|
8
|
+
self.url = client.get_queue_url(queue_name: name).queue_url
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def visibility_timeout
|
|
12
|
+
client.get_queue_attributes(
|
|
13
|
+
queue_url: url,
|
|
14
|
+
attribute_names: ['VisibilityTimeout']
|
|
15
|
+
).attributes['VisibilityTimeout'].to_i
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def delete_messages(options)
|
|
19
|
+
client.delete_message_batch(options.merge(queue_url: url))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def send_message(options)
|
|
23
|
+
options = sanitize_message!(options).merge(queue_url: url)
|
|
24
|
+
|
|
25
|
+
Shoryuken.client_middleware.invoke(options) do
|
|
26
|
+
client.send_message(options)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def send_messages(options)
|
|
31
|
+
client.send_message_batch(sanitize_messages!(options).merge(queue_url: url))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def receive_messages(options)
|
|
35
|
+
client.receive_message(options.merge(queue_url: url)).
|
|
36
|
+
messages.
|
|
37
|
+
map { |m| Message.new(client, self, m) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def sanitize_messages!(options)
|
|
43
|
+
options = case
|
|
44
|
+
when options.is_a?(Array)
|
|
45
|
+
{ entries: options.map.with_index { |m, index| { id: index.to_s, message_body: m } } }
|
|
46
|
+
when options.is_a?(Hash)
|
|
47
|
+
options
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
validate_messages!(options)
|
|
51
|
+
|
|
52
|
+
options
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def sanitize_message!(options)
|
|
56
|
+
options = case
|
|
57
|
+
when options.is_a?(String)
|
|
58
|
+
# send_message('message')
|
|
59
|
+
{ message_body: options }
|
|
60
|
+
when options.is_a?(Hash)
|
|
61
|
+
options
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
validate_message!(options)
|
|
65
|
+
|
|
66
|
+
options
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate_messages!(options)
|
|
70
|
+
options[:entries].map { |m| validate_message!(m) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_message!(options)
|
|
74
|
+
body = options[:message_body]
|
|
75
|
+
if body.is_a?(Hash)
|
|
76
|
+
options[:message_body] = JSON.dump(body)
|
|
77
|
+
elsif !body.is_a?(String)
|
|
78
|
+
fail ArgumentError, "The message body must be a String and you passed a #{body.class}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
options
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/shoryuken/sns_arn.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Shoryuken
|
|
2
2
|
class SnsArn
|
|
3
|
-
def initialize
|
|
3
|
+
def initialize(topic)
|
|
4
4
|
@topic = topic
|
|
5
5
|
end
|
|
6
6
|
|
|
@@ -13,14 +13,14 @@ module Shoryuken
|
|
|
13
13
|
def account_id
|
|
14
14
|
Shoryuken::Client.account_id.tap do |account_id|
|
|
15
15
|
if account_id.to_s.empty?
|
|
16
|
-
fail
|
|
16
|
+
fail 'To generate SNS ARNs, you must assign an :account_id in your Shoryuken::Client.'
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def region
|
|
22
22
|
Aws.config.fetch(:region) do
|
|
23
|
-
fail
|
|
23
|
+
fail 'To generate SNS ARNs, you must include a :region in your AWS config.'
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
end
|
data/lib/shoryuken/util.rb
CHANGED
|
@@ -3,9 +3,9 @@ module Shoryuken
|
|
|
3
3
|
def watchdog(last_words)
|
|
4
4
|
yield
|
|
5
5
|
rescue => ex
|
|
6
|
-
logger.error last_words
|
|
7
|
-
logger.error ex
|
|
8
|
-
logger.error ex.backtrace.join("\n")
|
|
6
|
+
logger.error { last_words }
|
|
7
|
+
logger.error { ex }
|
|
8
|
+
logger.error { ex.backtrace.join("\n") }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def logger
|
|
@@ -18,9 +18,8 @@ module Shoryuken
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def unparse_queues(queues)
|
|
21
|
-
queues.
|
|
21
|
+
queues.each_with_object({}) do |name, queue_and_weights|
|
|
22
22
|
queue_and_weights[name] = queue_and_weights[name].to_i + 1
|
|
23
|
-
queue_and_weights
|
|
24
23
|
end.to_a
|
|
25
24
|
end
|
|
26
25
|
|
|
@@ -31,7 +30,7 @@ module Shoryuken
|
|
|
31
30
|
&& sqs_msg.message_attributes['shoryuken_class'] \
|
|
32
31
|
&& sqs_msg.message_attributes['shoryuken_class'][:string_value] == ActiveJob::QueueAdapters::ShoryukenAdapter::JobWrapper.to_s
|
|
33
32
|
|
|
34
|
-
"ActiveJob/#{body['job_class']
|
|
33
|
+
"ActiveJob/#{body['job_class']}"
|
|
35
34
|
else
|
|
36
35
|
worker_class.to_s
|
|
37
36
|
end
|
data/lib/shoryuken/version.rb
CHANGED
data/lib/shoryuken/worker.rb
CHANGED
|
@@ -13,7 +13,6 @@ module Shoryuken
|
|
|
13
13
|
data_type: 'String'
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
body = JSON.dump(body) if body.is_a?(Hash)
|
|
17
16
|
options[:message_body] = body
|
|
18
17
|
|
|
19
18
|
Shoryuken::Client.queues(get_shoryuken_options['queue']).send_message(options)
|
|
@@ -40,7 +39,7 @@ module Shoryuken
|
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
def shoryuken_options(opts = {})
|
|
43
|
-
@shoryuken_options = get_shoryuken_options.merge(stringify_keys(
|
|
42
|
+
@shoryuken_options = get_shoryuken_options.merge(stringify_keys(opts || {}))
|
|
44
43
|
queue = @shoryuken_options['queue']
|
|
45
44
|
if queue.respond_to? :call
|
|
46
45
|
queue = queue.call
|
data/lib/shoryuken.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require 'yaml'
|
|
2
2
|
require 'aws-sdk-core'
|
|
3
|
-
require 'aws-sdk-resources'
|
|
4
3
|
require 'time'
|
|
5
4
|
|
|
6
5
|
require 'shoryuken/version'
|
|
@@ -8,12 +7,15 @@ require 'shoryuken/core_ext'
|
|
|
8
7
|
require 'shoryuken/util'
|
|
9
8
|
require 'shoryuken/logging'
|
|
10
9
|
require 'shoryuken/environment_loader'
|
|
10
|
+
require 'shoryuken/queue'
|
|
11
|
+
require 'shoryuken/message'
|
|
11
12
|
require 'shoryuken/client'
|
|
12
13
|
require 'shoryuken/worker'
|
|
13
14
|
require 'shoryuken/worker_registry'
|
|
14
15
|
require 'shoryuken/default_worker_registry'
|
|
15
16
|
require 'shoryuken/middleware/chain'
|
|
16
17
|
require 'shoryuken/middleware/server/auto_delete'
|
|
18
|
+
require 'shoryuken/middleware/server/exponential_backoff_retry'
|
|
17
19
|
require 'shoryuken/middleware/server/timing'
|
|
18
20
|
require 'shoryuken/sns_arn'
|
|
19
21
|
require 'shoryuken/topic'
|
|
@@ -27,8 +29,9 @@ module Shoryuken
|
|
|
27
29
|
timeout: 8
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
@@queues
|
|
32
|
+
@@queues = []
|
|
31
33
|
@@worker_registry = DefaultWorkerRegistry.new
|
|
34
|
+
@@active_job_queue_name_prefixing = false
|
|
32
35
|
|
|
33
36
|
class << self
|
|
34
37
|
def options
|
|
@@ -55,11 +58,14 @@ module Shoryuken
|
|
|
55
58
|
@@worker_registry
|
|
56
59
|
end
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
def active_job_queue_name_prefixing
|
|
62
|
+
@@active_job_queue_name_prefixing
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def active_job_queue_name_prefixing=(prefixing)
|
|
66
|
+
@@active_job_queue_name_prefixing = prefixing
|
|
67
|
+
end
|
|
68
|
+
|
|
63
69
|
def configure_server
|
|
64
70
|
yield self if server?
|
|
65
71
|
end
|
|
@@ -70,12 +76,23 @@ module Shoryuken
|
|
|
70
76
|
@server_chain
|
|
71
77
|
end
|
|
72
78
|
|
|
79
|
+
def configure_client
|
|
80
|
+
yield self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def client_middleware
|
|
84
|
+
@client_chain ||= default_client_middleware
|
|
85
|
+
yield @client_chain if block_given?
|
|
86
|
+
@client_chain
|
|
87
|
+
end
|
|
88
|
+
|
|
73
89
|
def default_worker_options
|
|
74
90
|
@@default_worker_options ||= {
|
|
75
91
|
'queue' => 'default',
|
|
76
92
|
'delete' => false,
|
|
77
93
|
'auto_delete' => false,
|
|
78
94
|
'auto_visibility_timeout' => false,
|
|
95
|
+
'retry_intervals' => nil,
|
|
79
96
|
'batch' => false }
|
|
80
97
|
end
|
|
81
98
|
|
|
@@ -104,6 +121,7 @@ module Shoryuken
|
|
|
104
121
|
def default_server_middleware
|
|
105
122
|
Middleware::Chain.new do |m|
|
|
106
123
|
m.add Middleware::Server::Timing
|
|
124
|
+
m.add Middleware::Server::ExponentialBackoffRetry
|
|
107
125
|
m.add Middleware::Server::AutoDelete
|
|
108
126
|
if defined?(::ActiveRecord::Base)
|
|
109
127
|
require 'shoryuken/middleware/server/active_record'
|
|
@@ -112,6 +130,10 @@ module Shoryuken
|
|
|
112
130
|
end
|
|
113
131
|
end
|
|
114
132
|
|
|
133
|
+
def default_client_middleware
|
|
134
|
+
Middleware::Chain.new
|
|
135
|
+
end
|
|
136
|
+
|
|
115
137
|
def server?
|
|
116
138
|
defined?(Shoryuken::CLI)
|
|
117
139
|
end
|
data/shoryuken.gemspec
CHANGED
|
@@ -4,27 +4,26 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
|
4
4
|
require 'shoryuken/version'
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name =
|
|
7
|
+
spec.name = 'shoryuken'
|
|
8
8
|
spec.version = Shoryuken::VERSION
|
|
9
|
-
spec.authors = [
|
|
10
|
-
spec.email = [
|
|
11
|
-
spec.description = spec.summary = %q
|
|
12
|
-
spec.homepage =
|
|
13
|
-
spec.license =
|
|
9
|
+
spec.authors = ['Pablo Cantero']
|
|
10
|
+
spec.email = ['pablo@pablocantero.com']
|
|
11
|
+
spec.description = spec.summary = %q(Shoryuken is a super efficient AWS SQS thread based message processor)
|
|
12
|
+
spec.homepage = 'https://github.com/phstc/shoryuken'
|
|
13
|
+
spec.license = 'LGPL-3.0'
|
|
14
14
|
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0")
|
|
16
16
|
spec.executables = %w[shoryuken]
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
-
spec.require_paths = [
|
|
18
|
+
spec.require_paths = ['lib']
|
|
19
19
|
|
|
20
|
-
spec.add_development_dependency
|
|
21
|
-
spec.add_development_dependency
|
|
22
|
-
spec.add_development_dependency
|
|
23
|
-
spec.add_development_dependency
|
|
24
|
-
spec.add_development_dependency
|
|
25
|
-
spec.add_development_dependency
|
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
|
21
|
+
spec.add_development_dependency 'rake'
|
|
22
|
+
spec.add_development_dependency 'rspec'
|
|
23
|
+
spec.add_development_dependency 'pry-byebug'
|
|
24
|
+
spec.add_development_dependency 'nokogiri'
|
|
25
|
+
spec.add_development_dependency 'dotenv'
|
|
26
26
|
|
|
27
|
-
spec.add_dependency
|
|
28
|
-
spec.add_dependency
|
|
29
|
-
spec.add_dependency "celluloid", "~> 0.16.0"
|
|
27
|
+
spec.add_dependency 'aws-sdk-core', '~> 2.0.21'
|
|
28
|
+
spec.add_dependency 'celluloid', '~> 0.16.0'
|
|
30
29
|
end
|
|
@@ -5,12 +5,13 @@ describe Shoryuken::Client do
|
|
|
5
5
|
let(:sqs) { Aws::SQS::Client.new(stub_responses: true, credentials: credentials) }
|
|
6
6
|
let(:queue_name) { 'shoryuken' }
|
|
7
7
|
let(:queue_url) { 'https://eu-west-1.amazonaws.com:6059/123456789012/shoryuken' }
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
described_class.sqs = sqs
|
|
11
|
-
end
|
|
8
|
+
let(:sqs_endpoint) { 'http://localhost:4568' }
|
|
9
|
+
let(:sns_endpoint) { 'http://0.0.0.0:4568' }
|
|
12
10
|
|
|
13
11
|
describe '.queue' do
|
|
12
|
+
before do
|
|
13
|
+
described_class.sqs = sqs
|
|
14
|
+
end
|
|
14
15
|
it 'memoizes queues' do
|
|
15
16
|
sqs.stub_responses(:get_queue_url, { queue_url: queue_url }, { queue_url: 'xyz' })
|
|
16
17
|
|
|
@@ -18,4 +19,44 @@ describe Shoryuken::Client do
|
|
|
18
19
|
expect(Shoryuken::Client.queues(queue_name).url).to eq queue_url
|
|
19
20
|
end
|
|
20
21
|
end
|
|
22
|
+
|
|
23
|
+
describe 'environment variable endpoints' do
|
|
24
|
+
before do
|
|
25
|
+
ENV['AWS_SQS_ENDPOINT'] = sqs_endpoint
|
|
26
|
+
ENV['AWS_SNS_ENDPOINT'] = sns_endpoint
|
|
27
|
+
ENV['AWS_REGION'] = 'us-east-1'
|
|
28
|
+
Shoryuken.options[:aws] = {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'will use config file settings if set' do
|
|
32
|
+
load_config_file_by_file_name('shoryuken_endpoint.yml')
|
|
33
|
+
expect(described_class.sqs.config.endpoint.to_s).to eql('https://github.com/phstc/shoryuken:4568')
|
|
34
|
+
expect(described_class.sns.config.endpoint.to_s).to eq('http://127.0.0.1:4568')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'should fallback to environment variable if config file not found or set' do
|
|
38
|
+
load_config_file_by_file_name(nil)
|
|
39
|
+
expect(described_class.sqs.config.endpoint.to_s).to eql(sqs_endpoint)
|
|
40
|
+
expect(described_class.sns.config.endpoint.to_s).to eq(sns_endpoint)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'should fallback to environment variable if config file found but settings not set' do
|
|
44
|
+
load_config_file_by_file_name('shoryuken.yml')
|
|
45
|
+
expect(described_class.sqs.config.endpoint.to_s).to eql(sqs_endpoint)
|
|
46
|
+
expect(described_class.sns.config.endpoint.to_s).to eq(sns_endpoint)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'will fallback to default settings if no config file settings or environment variables found' do
|
|
50
|
+
ENV['AWS_SQS_ENDPOINT'] = nil
|
|
51
|
+
ENV['AWS_SNS_ENDPOINT'] = nil
|
|
52
|
+
load_config_file_by_file_name('shoryuken.yml')
|
|
53
|
+
expect(described_class.sqs.config.endpoint.to_s).to eql('https://sqs.us-east-1.amazonaws.com')
|
|
54
|
+
expect(described_class.sns.config.endpoint.to_s).to eq('https://sns.us-east-1.amazonaws.com')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def load_config_file_by_file_name(file_name)
|
|
59
|
+
path_name = file_name ? File.join(File.expand_path('../../..', __FILE__), 'spec', file_name) : nil
|
|
60
|
+
Shoryuken::EnvironmentLoader.load(config_file: path_name)
|
|
61
|
+
end
|
|
21
62
|
end
|
|
@@ -48,7 +48,7 @@ describe Shoryuken::DefaultWorkerRegistry do
|
|
|
48
48
|
string_value: explicit_worker.to_s,
|
|
49
49
|
data_type: 'String' } if explicit_worker
|
|
50
50
|
|
|
51
|
-
double
|
|
51
|
+
double Shoryuken::Message,
|
|
52
52
|
body: 'test',
|
|
53
53
|
message_attributes: attributes,
|
|
54
54
|
message_id: SecureRandom.uuid
|
|
@@ -4,11 +4,11 @@ require 'shoryuken/fetcher'
|
|
|
4
4
|
|
|
5
5
|
describe Shoryuken::Fetcher do
|
|
6
6
|
let(:manager) { double Shoryuken::Manager }
|
|
7
|
-
let(:queue) { double
|
|
7
|
+
let(:queue) { double Shoryuken::Queue }
|
|
8
8
|
let(:queue_name) { 'default' }
|
|
9
9
|
|
|
10
10
|
let(:sqs_msg) do
|
|
11
|
-
double
|
|
11
|
+
double Shoryuken::Message,
|
|
12
12
|
queue_url: queue_name,
|
|
13
13
|
body: 'test',
|
|
14
14
|
message_id: 'fc754df7-9cc2-4c41-96ca-5996a44b771e'
|
|
@@ -24,7 +24,7 @@ describe Shoryuken::Fetcher do
|
|
|
24
24
|
|
|
25
25
|
describe '#fetch' do
|
|
26
26
|
it 'calls pause when no message' do
|
|
27
|
-
allow(queue).to receive(:receive_messages).with(max_number_of_messages: 1, message_attribute_names: ['All']).and_return([])
|
|
27
|
+
allow(queue).to receive(:receive_messages).with(max_number_of_messages: 1, attribute_names: ['All'], message_attribute_names: ['All']).and_return([])
|
|
28
28
|
|
|
29
29
|
expect(manager).to receive(:pause_queue!).with(queue_name)
|
|
30
30
|
expect(manager).to receive(:dispatch)
|
|
@@ -33,7 +33,7 @@ describe Shoryuken::Fetcher do
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
it 'assigns messages' do
|
|
36
|
-
allow(queue).to receive(:receive_messages).with(max_number_of_messages: 5, message_attribute_names: ['All']).and_return(sqs_msg)
|
|
36
|
+
allow(queue).to receive(:receive_messages).with(max_number_of_messages: 5, attribute_names: ['All'], message_attribute_names: ['All']).and_return(sqs_msg)
|
|
37
37
|
|
|
38
38
|
expect(manager).to receive(:rebalance_queue_weight!).with(queue_name)
|
|
39
39
|
expect(manager).to receive(:assign).with(queue_name, sqs_msg)
|
|
@@ -45,7 +45,7 @@ describe Shoryuken::Fetcher do
|
|
|
45
45
|
it 'assigns messages in batch' do
|
|
46
46
|
TestWorker.get_shoryuken_options['batch'] = true
|
|
47
47
|
|
|
48
|
-
allow(queue).to receive(:receive_messages).with(max_number_of_messages: described_class::FETCH_LIMIT, message_attribute_names: ['All']).and_return(sqs_msg)
|
|
48
|
+
allow(queue).to receive(:receive_messages).with(max_number_of_messages: described_class::FETCH_LIMIT, attribute_names: ['All'], message_attribute_names: ['All']).and_return(sqs_msg)
|
|
49
49
|
|
|
50
50
|
expect(manager).to receive(:rebalance_queue_weight!).with(queue_name)
|
|
51
51
|
expect(manager).to receive(:assign).with(queue_name, [sqs_msg])
|
|
@@ -58,7 +58,7 @@ describe Shoryuken::Fetcher do
|
|
|
58
58
|
let(:queue_name) { 'notfound' }
|
|
59
59
|
|
|
60
60
|
it 'ignores batch' do
|
|
61
|
-
allow(queue).to receive(:receive_messages).with(max_number_of_messages: 5, message_attribute_names: ['All']).and_return(sqs_msg)
|
|
61
|
+
allow(queue).to receive(:receive_messages).with(max_number_of_messages: 5, attribute_names: ['All'], message_attribute_names: ['All']).and_return(sqs_msg)
|
|
62
62
|
|
|
63
63
|
expect(manager).to receive(:rebalance_queue_weight!).with(queue_name)
|
|
64
64
|
expect(manager).to receive(:assign).with(queue_name, sqs_msg)
|