shoryuken 2.0.11 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +23 -0
- data/.travis.yml +2 -5
- data/CHANGELOG.md +5 -0
- data/Gemfile +1 -0
- data/{LICENSE.txt → LICENSE} +0 -0
- data/Rakefile +1 -1
- data/examples/default_worker.rb +1 -12
- data/lib/shoryuken.rb +18 -1
- data/lib/shoryuken/aws_config.rb +64 -0
- data/lib/shoryuken/cli.rb +9 -4
- data/lib/shoryuken/client.rb +2 -12
- data/lib/shoryuken/environment_loader.rb +25 -34
- data/lib/shoryuken/fetcher.rb +2 -2
- data/lib/shoryuken/launcher.rb +1 -1
- data/lib/shoryuken/manager.rb +10 -4
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +16 -12
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +1 -3
- data/lib/shoryuken/queue.rb +35 -35
- data/lib/shoryuken/util.rb +3 -1
- data/lib/shoryuken/version.rb +1 -1
- data/spec/shoryuken/cli_spec.rb +2 -2
- data/spec/shoryuken/client_spec.rb +3 -1
- data/spec/shoryuken/queue_spec.rb +110 -63
- data/spec/spec_helper.rb +7 -4
- metadata +5 -4
- data/.hound.yml +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2708ff803215783c123f043eb85cd5ee2581dc63
|
4
|
+
data.tar.gz: ac1a9753af6e5b482624c37cfe8f3df6af27ae06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f5e83670ab44a2eb84010d1187fcd2dfa699cf6d8ad944ff1e03a36574bd5f3508169799799ee03eec0b00c4e075e664d17334542e842ca5e7d9a46e09a2a81
|
7
|
+
data.tar.gz: 613cec158423dde7e53518a25d02a1418a4e7fc0b4e73b94fe70b9450f03e01364986bc5df5f347a782d2d2034dd3a800eece93bd806246fd59df81158c185c0
|
data/.codeclimate.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
---
|
2
|
+
engines:
|
3
|
+
brakeman:
|
4
|
+
enabled: true
|
5
|
+
bundler-audit:
|
6
|
+
enabled: true
|
7
|
+
duplication:
|
8
|
+
enabled: true
|
9
|
+
config:
|
10
|
+
languages:
|
11
|
+
- ruby
|
12
|
+
fixme:
|
13
|
+
enabled: true
|
14
|
+
reek:
|
15
|
+
enabled: true
|
16
|
+
rubocop:
|
17
|
+
enabled: true
|
18
|
+
ratings:
|
19
|
+
paths:
|
20
|
+
- "**.rb"
|
21
|
+
# exclude_paths:
|
22
|
+
# - "**/vendor/**/*"
|
23
|
+
# - "*/spec/**/*"
|
data/.travis.yml
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
# - 1.9.3
|
4
|
-
# - 1.9.2
|
5
3
|
- 2.0.0
|
6
4
|
- 2.1.0
|
7
5
|
- 2.2.0
|
8
|
-
# - ruby-head
|
9
|
-
# - jruby-19mode
|
10
|
-
# - jruby-head
|
11
6
|
|
12
7
|
notifications:
|
13
8
|
email:
|
@@ -17,3 +12,5 @@ notifications:
|
|
17
12
|
script: SPEC_ALL=true bundle exec rspec spec
|
18
13
|
before_install:
|
19
14
|
- gem update bundler
|
15
|
+
after_success:
|
16
|
+
- bundle exec codeclimate-test-reporter
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/Rakefile
CHANGED
data/examples/default_worker.rb
CHANGED
@@ -4,17 +4,6 @@ class DefaultWorker
|
|
4
4
|
shoryuken_options queue: 'default', auto_delete: true
|
5
5
|
|
6
6
|
def perform(sqs_msg, body)
|
7
|
-
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
# multiple workers for the same queue
|
12
|
-
class DefaultWorker2
|
13
|
-
include Shoryuken::Worker
|
14
|
-
|
15
|
-
shoryuken_options queue: 'default', auto_delete: true
|
16
|
-
|
17
|
-
def perform(sqs_msg, body)
|
18
|
-
puts "DefaultWorker2: '#{body}'"
|
7
|
+
Shoryuken.logger.info("Received message: '#{body}'")
|
19
8
|
end
|
20
9
|
end
|
data/lib/shoryuken.rb
CHANGED
@@ -6,6 +6,7 @@ require 'shoryuken/version'
|
|
6
6
|
require 'shoryuken/core_ext'
|
7
7
|
require 'shoryuken/util'
|
8
8
|
require 'shoryuken/logging'
|
9
|
+
require 'shoryuken/aws_config'
|
9
10
|
require 'shoryuken/environment_loader'
|
10
11
|
require 'shoryuken/queue'
|
11
12
|
require 'shoryuken/message'
|
@@ -72,6 +73,12 @@ module Shoryuken
|
|
72
73
|
@@active_job_queue_name_prefixing = prefixing
|
73
74
|
end
|
74
75
|
|
76
|
+
##
|
77
|
+
# Configuration for Shoryuken server, use like:
|
78
|
+
#
|
79
|
+
# Shoryuken.configure_server do |config|
|
80
|
+
# config.aws = { :sqs_endpoint => '...', :access_key_id: '...', :secret_access_key: '...', region: '...' }
|
81
|
+
# end
|
75
82
|
def configure_server
|
76
83
|
yield self if server?
|
77
84
|
end
|
@@ -82,8 +89,14 @@ module Shoryuken
|
|
82
89
|
@server_chain
|
83
90
|
end
|
84
91
|
|
92
|
+
##
|
93
|
+
# Configuration for Shoryuken client, use like:
|
94
|
+
#
|
95
|
+
# Shoryuken.configure_client do |config|
|
96
|
+
# config.aws = { :sqs_endpoint => '...', :access_key_id: '...', :secret_access_key: '...', region: '...' }
|
97
|
+
# end
|
85
98
|
def configure_client
|
86
|
-
yield self
|
99
|
+
yield self unless server?
|
87
100
|
end
|
88
101
|
|
89
102
|
def client_middleware
|
@@ -118,6 +131,10 @@ module Shoryuken
|
|
118
131
|
@stop_callback = block
|
119
132
|
end
|
120
133
|
|
134
|
+
def aws=(hash)
|
135
|
+
Shoryuken::AwsConfig.setup(hash)
|
136
|
+
end
|
137
|
+
|
121
138
|
# Register a block to run at a point in the Shoryuken lifecycle.
|
122
139
|
# :startup, :quiet or :shutdown are valid events.
|
123
140
|
#
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Shoryuken
|
3
|
+
class AwsConfig
|
4
|
+
class << self
|
5
|
+
attr_writer :options
|
6
|
+
|
7
|
+
def options
|
8
|
+
@options ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup(hash)
|
12
|
+
# aws-sdk tries to load the credentials from the ENV variables: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
|
13
|
+
# when not explicit supplied
|
14
|
+
return if hash.empty?
|
15
|
+
|
16
|
+
self.options = hash
|
17
|
+
|
18
|
+
shoryuken_keys = %w(
|
19
|
+
account_id
|
20
|
+
sns_endpoint
|
21
|
+
sqs_endpoint
|
22
|
+
receive_message
|
23
|
+
).map(&:to_sym)
|
24
|
+
|
25
|
+
@aws_options = hash.reject do |k, _|
|
26
|
+
shoryuken_keys.include?(k)
|
27
|
+
end
|
28
|
+
|
29
|
+
# assume credentials based authentication
|
30
|
+
credentials = Aws::Credentials.new(
|
31
|
+
@aws_options.delete(:access_key_id),
|
32
|
+
@aws_options.delete(:secret_access_key)
|
33
|
+
)
|
34
|
+
|
35
|
+
# but only if the configuration options have valid values
|
36
|
+
@aws_options.merge!(credentials: credentials) if credentials.set?
|
37
|
+
|
38
|
+
if (callback = Shoryuken.aws_initialization_callback)
|
39
|
+
Shoryuken.logger.info { 'Calling Shoryuken.on_aws_initialization block' }
|
40
|
+
callback.call(@aws_options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def sns
|
45
|
+
Aws::SNS::Client.new(aws_client_options(:sns_endpoint))
|
46
|
+
end
|
47
|
+
|
48
|
+
def sqs
|
49
|
+
Aws::SQS::Client.new(aws_client_options(:sqs_endpoint))
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def aws_client_options(service_endpoint_key)
|
55
|
+
environment_endpoint = ENV["AWS_#{service_endpoint_key.to_s.upcase}"]
|
56
|
+
explicit_endpoint = options[service_endpoint_key] || environment_endpoint
|
57
|
+
endpoint = {}.tap do |hash|
|
58
|
+
hash[:endpoint] = explicit_endpoint unless explicit_endpoint.to_s.empty?
|
59
|
+
end
|
60
|
+
@aws_options.to_h.merge(endpoint)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/shoryuken/cli.rb
CHANGED
@@ -31,10 +31,15 @@ module Shoryuken
|
|
31
31
|
|
32
32
|
options = parse_cli_args(args)
|
33
33
|
|
34
|
-
|
35
|
-
write_pid(options)
|
34
|
+
loader = EnvironmentLoader.setup_options(options)
|
36
35
|
|
37
|
-
|
36
|
+
# When cli args exist, override options in config file
|
37
|
+
Shoryuken.options.merge!(options)
|
38
|
+
|
39
|
+
daemonize(Shoryuken.options)
|
40
|
+
write_pid(Shoryuken.options)
|
41
|
+
|
42
|
+
loader.load
|
38
43
|
|
39
44
|
load_celluloid
|
40
45
|
|
@@ -64,7 +69,7 @@ module Shoryuken
|
|
64
69
|
private
|
65
70
|
|
66
71
|
def load_celluloid
|
67
|
-
require 'celluloid/
|
72
|
+
require 'celluloid/current'
|
68
73
|
Celluloid.logger = (Shoryuken.options[:verbose] ? Shoryuken.logger : nil)
|
69
74
|
|
70
75
|
require 'shoryuken/manager'
|
data/lib/shoryuken/client.rb
CHANGED
@@ -9,7 +9,7 @@ module Shoryuken
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def sns
|
12
|
-
@sns ||=
|
12
|
+
@sns ||= Shoryuken::AwsConfig.sns
|
13
13
|
end
|
14
14
|
|
15
15
|
def sns_arn
|
@@ -17,7 +17,7 @@ module Shoryuken
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def sqs
|
20
|
-
@sqs ||=
|
20
|
+
@sqs ||= Shoryuken::AwsConfig.sqs
|
21
21
|
end
|
22
22
|
|
23
23
|
def topics(name)
|
@@ -26,16 +26,6 @@ module Shoryuken
|
|
26
26
|
|
27
27
|
attr_accessor :account_id
|
28
28
|
attr_writer :sns, :sqs, :sqs_resource, :sns_arn
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def aws_client_options(service_endpoint_key)
|
33
|
-
environment_endpoint = ENV["AWS_#{service_endpoint_key.to_s.upcase}"]
|
34
|
-
explicit_endpoint = Shoryuken.options[:aws][service_endpoint_key] || environment_endpoint
|
35
|
-
options = {}
|
36
|
-
options[:endpoint] = explicit_endpoint unless explicit_endpoint.to_s.empty?
|
37
|
-
options
|
38
|
-
end
|
39
29
|
end
|
40
30
|
end
|
41
31
|
end
|
@@ -2,23 +2,29 @@ module Shoryuken
|
|
2
2
|
class EnvironmentLoader
|
3
3
|
attr_reader :options
|
4
4
|
|
5
|
-
def self.
|
6
|
-
new(options)
|
5
|
+
def self.setup_options(options)
|
6
|
+
instance = new(options)
|
7
|
+
instance.setup_options
|
8
|
+
instance
|
7
9
|
end
|
8
10
|
|
9
11
|
def self.load_for_rails_console
|
10
|
-
|
12
|
+
instance = setup_options(config_file: (Rails.root + 'config' + 'shoryuken.yml'))
|
13
|
+
instance.load
|
11
14
|
end
|
12
15
|
|
13
16
|
def initialize(options)
|
14
17
|
@options = options
|
15
18
|
end
|
16
19
|
|
17
|
-
def
|
18
|
-
load_rails if options[:rails]
|
20
|
+
def setup_options
|
19
21
|
initialize_options
|
20
22
|
initialize_logger
|
21
23
|
merge_cli_defined_queues
|
24
|
+
end
|
25
|
+
|
26
|
+
def load
|
27
|
+
load_rails if options[:rails]
|
22
28
|
prefix_active_job_queue_names
|
23
29
|
parse_queues
|
24
30
|
require_workers
|
@@ -43,35 +49,12 @@ module Shoryuken
|
|
43
49
|
YAML.load(ERB.new(IO.read(path)).result).deep_symbolize_keys
|
44
50
|
end
|
45
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)
|
46
55
|
def initialize_aws
|
47
|
-
|
48
|
-
|
49
|
-
return if Shoryuken.options[:aws].empty?
|
50
|
-
|
51
|
-
shoryuken_keys = %w(
|
52
|
-
account_id
|
53
|
-
sns_endpoint
|
54
|
-
sqs_endpoint
|
55
|
-
receive_message).map(&:to_sym)
|
56
|
-
|
57
|
-
aws_options = Shoryuken.options[:aws].reject do |k, v|
|
58
|
-
shoryuken_keys.include?(k)
|
59
|
-
end
|
60
|
-
|
61
|
-
# assume credentials based authentication
|
62
|
-
credentials = Aws::Credentials.new(
|
63
|
-
aws_options.delete(:access_key_id),
|
64
|
-
aws_options.delete(:secret_access_key))
|
65
|
-
|
66
|
-
# but only if the configuration options have valid values
|
67
|
-
aws_options = aws_options.merge(credentials: credentials) if credentials.set?
|
68
|
-
|
69
|
-
if (callback = Shoryuken.aws_initialization_callback)
|
70
|
-
Shoryuken.logger.info { 'Calling Shoryuken.on_aws_initialization block' }
|
71
|
-
callback.call(aws_options)
|
72
|
-
end
|
73
|
-
|
74
|
-
Aws.config = aws_options
|
56
|
+
Shoryuken.logger.warn { "[DEPRECATION] aws in shoryuken.yml is deprecated. Please use configure_server and configure_client in your initializer"} unless Shoryuken.options[:aws].nil?
|
57
|
+
Shoryuken::AwsConfig.setup(Shoryuken.options[:aws])
|
75
58
|
end
|
76
59
|
|
77
60
|
def initialize_logger
|
@@ -153,7 +136,15 @@ module Shoryuken
|
|
153
136
|
end
|
154
137
|
|
155
138
|
def require_workers
|
156
|
-
|
139
|
+
required = Shoryuken.options[:require]
|
140
|
+
|
141
|
+
return unless required
|
142
|
+
|
143
|
+
if File.directory?(required)
|
144
|
+
Dir[File.join(required, '**', '*.rb')].each(&method(:require))
|
145
|
+
else
|
146
|
+
require required
|
147
|
+
end
|
157
148
|
end
|
158
149
|
|
159
150
|
def validate_queues
|
data/lib/shoryuken/fetcher.rb
CHANGED
@@ -13,7 +13,7 @@ module Shoryuken
|
|
13
13
|
# AWS limits the batch size by 10
|
14
14
|
limit = limit > FETCH_LIMIT ? FETCH_LIMIT : limit
|
15
15
|
|
16
|
-
options = (Shoryuken.options[:
|
16
|
+
options = (Shoryuken::AwsConfig.options[:receive_message] || {}).dup
|
17
17
|
options[:max_number_of_messages] = limit
|
18
18
|
options[:message_attribute_names] = %w(All)
|
19
19
|
options[:attribute_names] = %w(All)
|
@@ -32,7 +32,7 @@ module Shoryuken
|
|
32
32
|
limit = batch ? FETCH_LIMIT : available_processors
|
33
33
|
|
34
34
|
if (sqs_msgs = Array(receive_messages(queue, limit))).any?
|
35
|
-
logger.
|
35
|
+
logger.debug { "Found #{sqs_msgs.size} messages for '#{queue}'" }
|
36
36
|
|
37
37
|
if batch
|
38
38
|
@manager.async.assign(queue, patch_sqs_msgs!(sqs_msgs))
|
data/lib/shoryuken/launcher.rb
CHANGED
@@ -36,7 +36,7 @@ module Shoryuken
|
|
36
36
|
|
37
37
|
def actor_died(actor, reason)
|
38
38
|
return if @done
|
39
|
-
logger.warn {
|
39
|
+
logger.warn { "Shoryuken died due to the following error, cannot recover, process exiting: #{reason}" }
|
40
40
|
exit 1
|
41
41
|
end
|
42
42
|
end
|
data/lib/shoryuken/manager.rb
CHANGED
@@ -77,7 +77,7 @@ module Shoryuken
|
|
77
77
|
|
78
78
|
def processor_died(processor, reason)
|
79
79
|
watchdog("Manager#processor_died died") do
|
80
|
-
logger.error { "Process died, reason: #{reason}"
|
80
|
+
logger.error { "Process died, reason: #{reason}" }
|
81
81
|
|
82
82
|
@threads.delete(processor.object_id)
|
83
83
|
@busy.delete processor
|
@@ -134,8 +134,7 @@ module Shoryuken
|
|
134
134
|
if @ready.empty?
|
135
135
|
logger.debug { 'Pausing fetcher, because all processors are busy' }
|
136
136
|
|
137
|
-
|
138
|
-
|
137
|
+
dispatch_later
|
139
138
|
return
|
140
139
|
end
|
141
140
|
|
@@ -154,6 +153,13 @@ module Shoryuken
|
|
154
153
|
|
155
154
|
private
|
156
155
|
|
156
|
+
def dispatch_later
|
157
|
+
@_dispatch_timer ||= after(1) do
|
158
|
+
@_dispatch_timer = nil
|
159
|
+
dispatch
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
157
163
|
def build_processor
|
158
164
|
processor = Processor.new_link(current_actor)
|
159
165
|
processor.proxy_id = processor.object_id
|
@@ -196,7 +202,7 @@ module Shoryuken
|
|
196
202
|
# get/remove the first queue in the list
|
197
203
|
queue = @queues.shift
|
198
204
|
|
199
|
-
unless defined?(::ActiveJob) ||
|
205
|
+
unless defined?(::ActiveJob) || !Shoryuken.worker_registry.workers(queue).empty?
|
200
206
|
# when no worker registered pause the queue to avoid endless recursion
|
201
207
|
logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because no workers registered" }
|
202
208
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'celluloid' unless defined?(Celluloid)
|
1
|
+
require 'celluloid/current' unless defined?(Celluloid)
|
2
2
|
|
3
3
|
module Shoryuken
|
4
4
|
module Middleware
|
@@ -7,11 +7,14 @@ module Shoryuken
|
|
7
7
|
EXTEND_UPFRONT_SECONDS = 5
|
8
8
|
|
9
9
|
def call(worker, queue, sqs_msg, body)
|
10
|
-
timer = auto_visibility_timer(queue, sqs_msg,
|
10
|
+
timer = auto_visibility_timer(worker, queue, sqs_msg, body)
|
11
11
|
begin
|
12
12
|
yield
|
13
13
|
ensure
|
14
|
-
|
14
|
+
if timer
|
15
|
+
timer.cancel
|
16
|
+
@visibility_extender.terminate
|
17
|
+
end
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
@@ -21,31 +24,32 @@ module Shoryuken
|
|
21
24
|
include Celluloid
|
22
25
|
include Util
|
23
26
|
|
24
|
-
def auto_extend(queue, sqs_msg,
|
27
|
+
def auto_extend(worker, queue, sqs_msg, body)
|
25
28
|
queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
|
26
29
|
|
27
30
|
every(queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
|
28
31
|
begin
|
29
32
|
logger.debug do
|
30
|
-
"Extending message #{worker_name(
|
31
|
-
|
33
|
+
"Extending message #{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
|
34
|
+
"visibility timeout by #{queue_visibility_timeout}s."
|
32
35
|
end
|
33
36
|
|
34
37
|
sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
|
35
38
|
rescue => e
|
36
39
|
logger.error do
|
37
|
-
|
38
|
-
|
40
|
+
'Could not auto extend the message ' \
|
41
|
+
"#{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
|
42
|
+
"visibility timeout. Error: #{e.message}"
|
39
43
|
end
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
43
47
|
end
|
44
48
|
|
45
|
-
def auto_visibility_timer(queue, sqs_msg,
|
46
|
-
return unless
|
47
|
-
@visibility_extender
|
48
|
-
@visibility_extender.auto_extend(queue, sqs_msg,
|
49
|
+
def auto_visibility_timer(worker, queue, sqs_msg, body)
|
50
|
+
return unless worker.class.auto_visibility_timeout?
|
51
|
+
@visibility_extender = MessageVisibilityExtender.new_link
|
52
|
+
@visibility_extender.auto_extend(worker, queue, sqs_msg, body)
|
49
53
|
end
|
50
54
|
end
|
51
55
|
end
|
@@ -20,9 +20,7 @@ module Shoryuken
|
|
20
20
|
private
|
21
21
|
|
22
22
|
def handle_failure(sqs_msg, started_at, retry_intervals)
|
23
|
-
attempts = sqs_msg.attributes['ApproximateReceiveCount']
|
24
|
-
|
25
|
-
return unless attempts
|
23
|
+
return unless attempts = sqs_msg.attributes['ApproximateReceiveCount']
|
26
24
|
|
27
25
|
attempts = attempts.to_i - 1
|
28
26
|
|
data/lib/shoryuken/queue.rb
CHANGED
@@ -1,22 +1,21 @@
|
|
1
1
|
module Shoryuken
|
2
2
|
class Queue
|
3
|
+
FIFO_ATTR = 'FifoQueue'
|
4
|
+
MESSAGE_GROUP_ID = 'ShoryukenMessage'
|
5
|
+
VISIBILITY_TIMEOUT_ATTR = 'VisibilityTimeout'
|
6
|
+
|
3
7
|
attr_accessor :name, :client, :url
|
4
8
|
|
5
9
|
def initialize(client, name)
|
6
10
|
self.name = name
|
7
11
|
self.client = client
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
raise e, "The specified queue '#{name}' does not exist"
|
12
|
-
end
|
12
|
+
self.url = client.get_queue_url(queue_name: name).queue_url
|
13
|
+
rescue Aws::SQS::Errors::NonExistentQueue => e
|
14
|
+
raise e, "The specified queue '#{name}' does not exist."
|
13
15
|
end
|
14
16
|
|
15
17
|
def visibility_timeout
|
16
|
-
|
17
|
-
queue_url: url,
|
18
|
-
attribute_names: ['VisibilityTimeout']
|
19
|
-
).attributes['VisibilityTimeout'].to_i
|
18
|
+
queue_attributes.attributes[VISIBILITY_TIMEOUT_ATTR].to_i
|
20
19
|
end
|
21
20
|
|
22
21
|
def delete_messages(options)
|
@@ -41,49 +40,50 @@ module Shoryuken
|
|
41
40
|
map { |m| Message.new(client, self, m) }
|
42
41
|
end
|
43
42
|
|
43
|
+
def fifo?
|
44
|
+
@_fifo ||= queue_attributes.attributes[FIFO_ATTR] == 'true'
|
45
|
+
end
|
46
|
+
|
44
47
|
private
|
45
48
|
|
49
|
+
def queue_attributes
|
50
|
+
# Note: Retrieving all queue attributes as requesting `FifoQueue` on non-FIFO queue raises error.
|
51
|
+
# See issue: https://github.com/aws/aws-sdk-ruby/issues/1350
|
52
|
+
client.get_queue_attributes(queue_url: url, attribute_names: ['All'])
|
53
|
+
end
|
54
|
+
|
46
55
|
def sanitize_messages!(options)
|
47
|
-
options
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end }
|
52
|
-
when options.is_a?(Hash)
|
53
|
-
options
|
54
|
-
end
|
56
|
+
if options.is_a?(Array)
|
57
|
+
entries = options.map.with_index do |m, index|
|
58
|
+
{ id: index.to_s }.merge(m.is_a?(Hash) ? m : { message_body: m })
|
59
|
+
end
|
55
60
|
|
56
|
-
|
61
|
+
options = { entries: entries }
|
62
|
+
end
|
63
|
+
|
64
|
+
options[:entries].each(&method(:sanitize_message!))
|
57
65
|
|
58
66
|
options
|
59
67
|
end
|
60
68
|
|
61
|
-
def
|
62
|
-
|
63
|
-
when options.is_a?(String)
|
64
|
-
# send_message('message')
|
65
|
-
{ message_body: options }
|
66
|
-
when options.is_a?(Hash)
|
67
|
-
options
|
68
|
-
end
|
69
|
+
def add_fifo_attributes!(options)
|
70
|
+
return unless fifo?
|
69
71
|
|
70
|
-
|
72
|
+
options[:message_group_id] ||= MESSAGE_GROUP_ID
|
73
|
+
options[:message_deduplication_id] ||= Digest::SHA256.hexdigest(options[:message_body].to_s)
|
71
74
|
|
72
75
|
options
|
73
76
|
end
|
74
77
|
|
75
|
-
def
|
76
|
-
options
|
77
|
-
end
|
78
|
+
def sanitize_message!(options)
|
79
|
+
options = { message_body: options } if options.is_a?(String)
|
78
80
|
|
79
|
-
|
80
|
-
body = options[:message_body]
|
81
|
-
if body.is_a?(Hash)
|
81
|
+
if (body = options[:message_body]).is_a?(Hash)
|
82
82
|
options[:message_body] = JSON.dump(body)
|
83
|
-
elsif !body.is_a?(String)
|
84
|
-
fail ArgumentError, "The message body must be a String and you passed a #{body.class}"
|
85
83
|
end
|
86
84
|
|
85
|
+
add_fifo_attributes!(options)
|
86
|
+
|
87
87
|
options
|
88
88
|
end
|
89
89
|
end
|
data/lib/shoryuken/util.rb
CHANGED
@@ -42,7 +42,9 @@ module Shoryuken
|
|
42
42
|
&& !sqs_msg.is_a?(Array) \
|
43
43
|
&& sqs_msg.message_attributes \
|
44
44
|
&& sqs_msg.message_attributes['shoryuken_class'] \
|
45
|
-
&& sqs_msg.message_attributes['shoryuken_class'][:string_value]
|
45
|
+
&& sqs_msg.message_attributes['shoryuken_class'][:string_value] \
|
46
|
+
== ActiveJob::QueueAdapters::ShoryukenAdapter::JobWrapper.to_s \
|
47
|
+
&& body
|
46
48
|
|
47
49
|
"ActiveJob/#{body['job_class']}"
|
48
50
|
else
|
data/lib/shoryuken/version.rb
CHANGED
data/spec/shoryuken/cli_spec.rb
CHANGED
@@ -5,7 +5,7 @@ require 'shoryuken/launcher'
|
|
5
5
|
RSpec.describe Shoryuken::CLI do
|
6
6
|
let(:cli) { Shoryuken::CLI.instance }
|
7
7
|
|
8
|
-
before
|
8
|
+
before do
|
9
9
|
# make sure we do not bail
|
10
10
|
allow(cli).to receive(:exit)
|
11
11
|
|
@@ -32,7 +32,7 @@ RSpec.describe Shoryuken::CLI do
|
|
32
32
|
cli.run(['--daemon', '--logfile', '/dev/null'])
|
33
33
|
end
|
34
34
|
|
35
|
-
it 'does NOT daemonize with --
|
35
|
+
it 'does NOT daemonize with --logfile' do
|
36
36
|
expect(Process).to_not receive(:daemon)
|
37
37
|
cli.run(['--logfile', '/dev/null'])
|
38
38
|
end
|
@@ -26,6 +26,7 @@ describe Shoryuken::Client do
|
|
26
26
|
ENV['AWS_SNS_ENDPOINT'] = sns_endpoint
|
27
27
|
ENV['AWS_REGION'] = 'us-east-1'
|
28
28
|
Shoryuken.options[:aws] = {}
|
29
|
+
Shoryuken::AwsConfig.options = {}
|
29
30
|
end
|
30
31
|
|
31
32
|
it 'will use config file settings if set' do
|
@@ -57,6 +58,7 @@ describe Shoryuken::Client do
|
|
57
58
|
|
58
59
|
def load_config_file_by_file_name(file_name)
|
59
60
|
path_name = file_name ? File.join(File.expand_path('../../..', __FILE__), 'spec', file_name) : nil
|
60
|
-
Shoryuken::EnvironmentLoader.
|
61
|
+
loader = Shoryuken::EnvironmentLoader.setup_options(config_file: path_name)
|
62
|
+
loader.load
|
61
63
|
end
|
62
64
|
end
|
@@ -2,13 +2,21 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Shoryuken::Queue do
|
4
4
|
let(:credentials) { Aws::Credentials.new('access_key_id', 'secret_access_key') }
|
5
|
-
let(:sqs)
|
6
|
-
let(:queue_name)
|
7
|
-
let(:queue_url)
|
5
|
+
let(:sqs) { Aws::SQS::Client.new(stub_responses: true, credentials: credentials) }
|
6
|
+
let(:queue_name) { 'shoryuken' }
|
7
|
+
let(:queue_url) { 'https://eu-west-1.amazonaws.com:6059/123456789012/shoryuken' }
|
8
8
|
|
9
9
|
subject { described_class.new(sqs, queue_name) }
|
10
|
+
before {
|
11
|
+
# Required as Aws::SQS::Client.get_queue_url returns 'String' when responses are stubbed,
|
12
|
+
# which is not accepted by Aws::SQS::Client.get_queue_attributes for :queue_name parameter.
|
13
|
+
allow(subject).to receive(:url).and_return(queue_url)
|
14
|
+
}
|
10
15
|
|
11
16
|
describe '#send_message' do
|
17
|
+
before {
|
18
|
+
allow(subject).to receive(:fifo?).and_return(false)
|
19
|
+
}
|
12
20
|
it 'accepts SQS request parameters' do
|
13
21
|
# https://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#send_message-instance_method
|
14
22
|
expect(sqs).to receive(:send_message).with(hash_including(message_body: 'msg1'))
|
@@ -22,95 +30,134 @@ describe Shoryuken::Queue do
|
|
22
30
|
subject.send_message('msg1')
|
23
31
|
end
|
24
32
|
|
25
|
-
context 'when
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
}.to raise_error(ArgumentError, 'The message body must be a String and you passed a NilClass')
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'raises ArgumentError for Fixnum' do
|
33
|
-
expect {
|
34
|
-
subject.send_message(message_body: 1)
|
35
|
-
}.to raise_error(ArgumentError, 'The message body must be a String and you passed a Fixnum')
|
36
|
-
end
|
33
|
+
context 'when a client middleware' do
|
34
|
+
class MyClientMiddleware
|
35
|
+
def call(options)
|
36
|
+
options[:message_body] = 'changed'
|
37
37
|
|
38
|
-
|
39
|
-
class MyClientMiddleware
|
40
|
-
def call(options)
|
41
|
-
options[:message_body] = 'changed'
|
42
|
-
|
43
|
-
yield
|
44
|
-
end
|
38
|
+
yield
|
45
39
|
end
|
40
|
+
end
|
46
41
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
42
|
+
before do
|
43
|
+
allow(Shoryuken).to receive(:server?).and_return(false)
|
44
|
+
Shoryuken.configure_client do |config|
|
45
|
+
config.client_middleware do |chain|
|
46
|
+
chain.add MyClientMiddleware
|
52
47
|
end
|
53
48
|
end
|
49
|
+
end
|
54
50
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
51
|
+
after do
|
52
|
+
Shoryuken.configure_client do |config|
|
53
|
+
config.client_middleware do |chain|
|
54
|
+
chain.remove MyClientMiddleware
|
60
55
|
end
|
61
56
|
end
|
57
|
+
end
|
62
58
|
|
63
|
-
|
64
|
-
|
59
|
+
it 'invokes MyClientMiddleware' do
|
60
|
+
expect(sqs).to receive(:send_message).with(hash_including(message_body: 'changed'))
|
65
61
|
|
66
|
-
|
67
|
-
end
|
62
|
+
subject.send_message(message_body: 'original')
|
68
63
|
end
|
69
64
|
end
|
70
65
|
end
|
71
66
|
|
72
67
|
describe '#send_messages' do
|
68
|
+
before {
|
69
|
+
allow(subject).to receive(:fifo?).and_return(false)
|
70
|
+
}
|
73
71
|
it 'accepts SQS request parameters' do
|
74
72
|
# https://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#send_message_batch-instance_method
|
75
|
-
expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{
|
73
|
+
expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{id: '0', message_body: 'msg1'}, {id: '1', message_body: 'msg2'}]))
|
76
74
|
|
77
|
-
subject.send_messages(entries: [{
|
75
|
+
subject.send_messages(entries: [{id: '0', message_body: 'msg1'}, {id: '1', message_body: 'msg2'}])
|
78
76
|
end
|
79
77
|
|
80
78
|
it 'accepts an array of messages' do
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
79
|
+
options = { entries: [{ id: '0',
|
80
|
+
message_body: 'msg1',
|
81
|
+
delay_seconds: 1,
|
82
|
+
message_attributes: { attr: 'attr1' } },
|
83
|
+
{ id: '1',
|
84
|
+
message_body: 'msg2',
|
85
|
+
delay_seconds: 1,
|
86
|
+
message_attributes: { attr: 'attr2' } }] }
|
87
|
+
|
88
|
+
expect(sqs).to receive(:send_message_batch).with(hash_including(options))
|
89
|
+
|
90
|
+
subject.send_messages([{ message_body: 'msg1',
|
91
|
+
delay_seconds: 1,
|
92
|
+
message_attributes: { attr: 'attr1' }
|
93
|
+
}, {
|
94
|
+
message_body: 'msg2',
|
95
|
+
delay_seconds: 1,
|
96
|
+
message_attributes: { attr: 'attr2' }
|
97
|
+
}])
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when FIFO' do
|
101
|
+
before do
|
102
|
+
allow(subject).to receive(:fifo?).and_return(true)
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'and message_group_id and message_deduplication_id are absent' do
|
106
|
+
it 'sets default values' do
|
107
|
+
expect(sqs).to receive(:send_message_batch) do |arg|
|
108
|
+
first_entry = arg[:entries].first
|
109
|
+
|
110
|
+
expect(first_entry[:message_group_id]).to eq described_class::MESSAGE_GROUP_ID
|
111
|
+
expect(first_entry[:message_deduplication_id]).to be
|
112
|
+
end
|
113
|
+
|
114
|
+
subject.send_messages([{ message_body: 'msg1', message_attributes: { attr: 'attr1' } }])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'and message_group_id and message_deduplication_id are present' do
|
119
|
+
it 'preserves existing values' do
|
120
|
+
expect(sqs).to receive(:send_message_batch) do |arg|
|
121
|
+
first_entry = arg[:entries].first
|
122
|
+
|
123
|
+
expect(first_entry[:message_group_id]).to eq 'my group'
|
124
|
+
expect(first_entry[:message_deduplication_id]).to eq 'my id'
|
125
|
+
end
|
126
|
+
|
127
|
+
subject.send_messages([{ message_body: 'msg1',
|
128
|
+
message_attributes: { attr: 'attr1' },
|
129
|
+
message_group_id: 'my group',
|
130
|
+
message_deduplication_id: 'my id' }])
|
131
|
+
end
|
132
|
+
end
|
94
133
|
end
|
95
134
|
|
96
135
|
it 'accepts an array of string' do
|
97
|
-
expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1'}, { id: '1', message_body: 'msg2' }]))
|
136
|
+
expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1' }, { id: '1', message_body: 'msg2' }]))
|
98
137
|
|
99
138
|
subject.send_messages(%w(msg1 msg2))
|
100
139
|
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe '#fifo?' do
|
143
|
+
before do
|
144
|
+
attribute_response = double 'Aws::SQS::Types::GetQueueAttributesResponse'
|
145
|
+
|
146
|
+
allow(attribute_response).to receive(:attributes).and_return('FifoQueue' => fifo.to_s, 'ContentBasedDeduplication' => 'true')
|
147
|
+
allow(subject).to receive(:url).and_return(queue_url)
|
148
|
+
allow(sqs).to receive(:get_queue_attributes).with(queue_url: queue_url, attribute_names: ['All']).and_return(attribute_response)
|
149
|
+
end
|
101
150
|
|
102
|
-
context 'when
|
103
|
-
|
104
|
-
expect {
|
105
|
-
subject.send_messages(entries: [message_body: nil])
|
106
|
-
}.to raise_error(ArgumentError, 'The message body must be a String and you passed a NilClass')
|
107
|
-
end
|
151
|
+
context 'when queue is FIFO' do
|
152
|
+
let(:fifo) { true }
|
108
153
|
|
109
|
-
it
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
154
|
+
it { expect(subject.fifo?).to be }
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'when queue is not FIFO' do
|
158
|
+
let(:fifo) { false }
|
159
|
+
|
160
|
+
it { expect(subject.fifo?).to_not be }
|
114
161
|
end
|
115
162
|
end
|
116
163
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,7 @@ require 'bundler/setup'
|
|
2
2
|
Bundler.setup
|
3
3
|
|
4
4
|
require 'pry-byebug'
|
5
|
-
require 'celluloid'
|
5
|
+
require 'celluloid/current'
|
6
6
|
require 'shoryuken'
|
7
7
|
require 'json'
|
8
8
|
require 'multi_xml'
|
@@ -10,13 +10,13 @@ require 'dotenv'
|
|
10
10
|
Dotenv.load
|
11
11
|
|
12
12
|
if ENV['CODECLIMATE_REPO_TOKEN']
|
13
|
-
require '
|
14
|
-
|
13
|
+
require 'simplecov'
|
14
|
+
SimpleCov.start
|
15
15
|
end
|
16
16
|
|
17
17
|
config_file = File.join(File.expand_path('../..', __FILE__), 'spec', 'shoryuken.yml')
|
18
18
|
|
19
|
-
Shoryuken::EnvironmentLoader.
|
19
|
+
Shoryuken::EnvironmentLoader.setup_options(config_file: config_file)
|
20
20
|
|
21
21
|
Shoryuken.logger.level = Logger::UNKNOWN
|
22
22
|
Celluloid.logger.level = Logger::UNKNOWN
|
@@ -57,6 +57,9 @@ RSpec.configure do |config|
|
|
57
57
|
Shoryuken.options[:concurrency] = 1
|
58
58
|
Shoryuken.options[:delay] = 1
|
59
59
|
Shoryuken.options[:timeout] = 1
|
60
|
+
Shoryuken.options[:daemon] = nil
|
61
|
+
Shoryuken.options[:logfile] = nil
|
62
|
+
|
60
63
|
Shoryuken.options[:aws].delete(:receive_message)
|
61
64
|
|
62
65
|
TestWorker.get_shoryuken_options.clear
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shoryuken
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pablo Cantero
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-12-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -132,20 +132,21 @@ executables:
|
|
132
132
|
extensions: []
|
133
133
|
extra_rdoc_files: []
|
134
134
|
files:
|
135
|
+
- ".codeclimate.yml"
|
135
136
|
- ".gitignore"
|
136
|
-
- ".hound.yml"
|
137
137
|
- ".rspec"
|
138
138
|
- ".rubocop.yml"
|
139
139
|
- ".travis.yml"
|
140
140
|
- CHANGELOG.md
|
141
141
|
- Gemfile
|
142
|
-
- LICENSE
|
142
|
+
- LICENSE
|
143
143
|
- README.md
|
144
144
|
- Rakefile
|
145
145
|
- bin/shoryuken
|
146
146
|
- examples/bootstrap_queues.rb
|
147
147
|
- examples/default_worker.rb
|
148
148
|
- lib/shoryuken.rb
|
149
|
+
- lib/shoryuken/aws_config.rb
|
149
150
|
- lib/shoryuken/cli.rb
|
150
151
|
- lib/shoryuken/client.rb
|
151
152
|
- lib/shoryuken/core_ext.rb
|