shoryuken 5.0.4 → 5.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/specs.yml +62 -0
- data/.reek.yml +5 -0
- data/.rubocop.yml +1 -1
- data/Appraisals +36 -0
- data/CHANGELOG.md +63 -0
- data/Gemfile +3 -1
- data/README.md +24 -2
- data/Rakefile +15 -1
- data/bin/cli/sqs.rb +50 -5
- data/gemfiles/.gitignore +1 -0
- data/gemfiles/aws_sdk_core_2.gemfile +21 -0
- data/gemfiles/rails_4_2.gemfile +20 -0
- data/gemfiles/rails_5_2.gemfile +21 -0
- data/gemfiles/rails_6_0.gemfile +21 -0
- data/gemfiles/rails_6_1.gemfile +21 -0
- data/lib/shoryuken/environment_loader.rb +7 -1
- data/lib/shoryuken/extensions/active_job_adapter.rb +25 -18
- data/lib/shoryuken/extensions/active_job_extensions.rb +38 -0
- data/lib/shoryuken/launcher.rb +1 -0
- data/lib/shoryuken/manager.rb +24 -5
- data/lib/shoryuken/options.rb +1 -0
- data/lib/shoryuken/polling/base.rb +2 -0
- data/lib/shoryuken/polling/strict_priority.rb +6 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +11 -0
- data/lib/shoryuken/queue.rb +39 -11
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken.rb +1 -0
- data/shoryuken.gemspec +0 -1
- data/spec/integration/launcher_spec.rb +29 -2
- data/spec/shared_examples_for_active_job.rb +226 -9
- data/spec/shoryuken/environment_loader_spec.rb +22 -2
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +1 -1
- data/spec/shoryuken/extensions/active_job_base_spec.rb +84 -0
- data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +4 -0
- data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +20 -0
- data/spec/shoryuken/manager_spec.rb +35 -1
- data/spec/shoryuken/polling/strict_priority_spec.rb +10 -0
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +10 -0
- data/spec/shoryuken/queue_spec.rb +23 -0
- data/spec/spec_helper.rb +5 -9
- metadata +20 -22
- data/.travis.yml +0 -34
- data/Gemfile.aws-sdk-core-v2 +0 -13
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
group :test do
|
6
|
+
gem "activejob", "~> 6.1"
|
7
|
+
gem "aws-sdk-core", "~> 3"
|
8
|
+
gem "aws-sdk-sqs"
|
9
|
+
gem "codeclimate-test-reporter", require: nil
|
10
|
+
gem "httparty"
|
11
|
+
gem "multi_xml"
|
12
|
+
gem "simplecov"
|
13
|
+
end
|
14
|
+
|
15
|
+
group :development do
|
16
|
+
gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
|
17
|
+
gem "pry-byebug", "3.9.0"
|
18
|
+
gem "rubocop"
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec path: "../"
|
@@ -70,12 +70,18 @@ module Shoryuken
|
|
70
70
|
::Rails.application.config.eager_load = true
|
71
71
|
end
|
72
72
|
end
|
73
|
-
|
73
|
+
if Shoryuken.active_job?
|
74
|
+
require 'shoryuken/extensions/active_job_extensions'
|
75
|
+
require 'shoryuken/extensions/active_job_adapter'
|
76
|
+
require 'shoryuken/extensions/active_job_concurrent_send_adapter'
|
77
|
+
end
|
74
78
|
require File.expand_path('config/environment.rb')
|
75
79
|
end
|
76
80
|
end
|
77
81
|
|
78
82
|
def prefix_active_job_queue_name(queue_name, weight)
|
83
|
+
return [queue_name, weight] if queue_name.start_with?('https://', 'arn:')
|
84
|
+
|
79
85
|
queue_name_prefix = ::ActiveJob::Base.queue_name_prefix
|
80
86
|
queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
|
81
87
|
|
@@ -33,8 +33,12 @@ module ActiveJob
|
|
33
33
|
def enqueue(job, options = {}) #:nodoc:
|
34
34
|
register_worker!(job)
|
35
35
|
|
36
|
+
job.sqs_send_message_parameters.merge! options
|
37
|
+
|
36
38
|
queue = Shoryuken::Client.queues(job.queue_name)
|
37
|
-
|
39
|
+
send_message_params = message queue, job
|
40
|
+
job.sqs_send_message_parameters = send_message_params
|
41
|
+
queue.send_message send_message_params
|
38
42
|
end
|
39
43
|
|
40
44
|
def enqueue_at(job, timestamp) #:nodoc:
|
@@ -50,44 +54,47 @@ module ActiveJob
|
|
50
54
|
delay
|
51
55
|
end
|
52
56
|
|
53
|
-
def message(queue, job
|
57
|
+
def message(queue, job)
|
54
58
|
body = job.serialize
|
59
|
+
job_params = job.sqs_send_message_parameters
|
60
|
+
|
61
|
+
attributes = job_params[:message_attributes] || {}
|
55
62
|
|
56
|
-
msg = {
|
63
|
+
msg = {
|
64
|
+
message_body: body,
|
65
|
+
message_attributes: attributes.merge(MESSAGE_ATTRIBUTES)
|
66
|
+
}
|
57
67
|
|
58
68
|
if queue.fifo?
|
59
69
|
# See https://github.com/phstc/shoryuken/issues/457
|
60
70
|
msg[:message_deduplication_id] = Digest::SHA256.hexdigest(JSON.dump(body.except('job_id')))
|
61
71
|
end
|
62
72
|
|
63
|
-
msg
|
64
|
-
msg[:message_attributes] = message_attributes
|
65
|
-
|
66
|
-
msg.merge(options)
|
73
|
+
msg.merge(job_params.except(:message_attributes))
|
67
74
|
end
|
68
75
|
|
69
76
|
def register_worker!(job)
|
70
77
|
Shoryuken.register_worker(job.queue_name, JobWrapper)
|
71
78
|
end
|
72
79
|
|
73
|
-
def message_attributes
|
74
|
-
@message_attributes ||= {
|
75
|
-
'shoryuken_class' => {
|
76
|
-
string_value: JobWrapper.to_s,
|
77
|
-
data_type: 'String'
|
78
|
-
}
|
79
|
-
}
|
80
|
-
end
|
81
|
-
|
82
80
|
class JobWrapper #:nodoc:
|
83
81
|
include Shoryuken::Worker
|
84
82
|
|
85
83
|
shoryuken_options body_parser: :json, auto_delete: true
|
86
84
|
|
87
|
-
def perform(
|
88
|
-
|
85
|
+
def perform(sqs_msg, hash)
|
86
|
+
receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
|
87
|
+
past_receives = receive_count - 1
|
88
|
+
Base.execute hash.merge({ 'executions' => past_receives })
|
89
89
|
end
|
90
90
|
end
|
91
|
+
|
92
|
+
MESSAGE_ATTRIBUTES = {
|
93
|
+
'shoryuken_class' => {
|
94
|
+
string_value: JobWrapper.to_s,
|
95
|
+
data_type: 'String'
|
96
|
+
}
|
97
|
+
}.freeze
|
91
98
|
end
|
92
99
|
end
|
93
100
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Shoryuken
|
2
|
+
module ActiveJobExtensions
|
3
|
+
# Adds an accessor for SQS SendMessage parameters on ActiveJob jobs
|
4
|
+
# (instances of ActiveJob::Base). Shoryuken ActiveJob queue adapters use
|
5
|
+
# these parameters when enqueueing jobs; other adapters can ignore them.
|
6
|
+
module SQSSendMessageParametersAccessor
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
attr_accessor :sqs_send_message_parameters
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Initializes SQS SendMessage parameters on instances of ActiveJobe::Base
|
15
|
+
# to the empty hash, and populates it whenever `#enqueue` is called, such
|
16
|
+
# as when using ActiveJob::Base.set.
|
17
|
+
module SQSSendMessageParametersSupport
|
18
|
+
def initialize(*arguments)
|
19
|
+
super(*arguments)
|
20
|
+
self.sqs_send_message_parameters = {}
|
21
|
+
end
|
22
|
+
ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
|
23
|
+
|
24
|
+
def enqueue(options = {})
|
25
|
+
sqs_options = options.extract! :message_attributes,
|
26
|
+
:message_system_attributes,
|
27
|
+
:message_deduplication_id,
|
28
|
+
:message_group_id
|
29
|
+
sqs_send_message_parameters.merge! sqs_options
|
30
|
+
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
ActiveJob::Base.include Shoryuken::ActiveJobExtensions::SQSSendMessageParametersAccessor
|
38
|
+
ActiveJob::Base.prepend Shoryuken::ActiveJobExtensions::SQSSendMessageParametersSupport
|
data/lib/shoryuken/launcher.rb
CHANGED
@@ -71,6 +71,7 @@ module Shoryuken
|
|
71
71
|
def create_managers
|
72
72
|
Shoryuken.groups.map do |group, options|
|
73
73
|
Shoryuken::Manager.new(
|
74
|
+
group,
|
74
75
|
Shoryuken::Fetcher.new(group),
|
75
76
|
Shoryuken.polling_strategy(group).new(options[:queues], Shoryuken.delay(group)),
|
76
77
|
options[:concurrency],
|
data/lib/shoryuken/manager.rb
CHANGED
@@ -6,7 +6,8 @@ module Shoryuken
|
|
6
6
|
# See https://github.com/phstc/shoryuken/issues/348#issuecomment-292847028
|
7
7
|
MIN_DISPATCH_INTERVAL = 0.1
|
8
8
|
|
9
|
-
def initialize(fetcher, polling_strategy, concurrency, executor)
|
9
|
+
def initialize(group, fetcher, polling_strategy, concurrency, executor)
|
10
|
+
@group = group
|
10
11
|
@fetcher = fetcher
|
11
12
|
@polling_strategy = polling_strategy
|
12
13
|
@max_processors = concurrency
|
@@ -16,6 +17,7 @@ module Shoryuken
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def start
|
20
|
+
fire_utilization_update_event
|
19
21
|
dispatch_loop
|
20
22
|
end
|
21
23
|
|
@@ -57,8 +59,15 @@ module Shoryuken
|
|
57
59
|
@max_processors - busy
|
58
60
|
end
|
59
61
|
|
60
|
-
def processor_done
|
62
|
+
def processor_done(queue)
|
61
63
|
@busy_processors.decrement
|
64
|
+
fire_utilization_update_event
|
65
|
+
|
66
|
+
client_queue = Shoryuken::Client.queues(queue)
|
67
|
+
return unless client_queue.fifo?
|
68
|
+
return unless @polling_strategy.respond_to?(:message_processed)
|
69
|
+
|
70
|
+
@polling_strategy.message_processed(queue)
|
62
71
|
end
|
63
72
|
|
64
73
|
def assign(queue_name, sqs_msg)
|
@@ -67,10 +76,12 @@ module Shoryuken
|
|
67
76
|
logger.debug { "Assigning #{sqs_msg.message_id}" }
|
68
77
|
|
69
78
|
@busy_processors.increment
|
79
|
+
fire_utilization_update_event
|
70
80
|
|
71
|
-
Concurrent::Promise
|
72
|
-
executor: @executor
|
73
|
-
|
81
|
+
Concurrent::Promise
|
82
|
+
.execute(executor: @executor) { Processor.process(queue_name, sqs_msg) }
|
83
|
+
.then { processor_done(queue_name) }
|
84
|
+
.rescue { processor_done(queue_name) }
|
74
85
|
end
|
75
86
|
|
76
87
|
def dispatch_batch(queue)
|
@@ -108,5 +119,13 @@ module Shoryuken
|
|
108
119
|
|
109
120
|
@running.make_false
|
110
121
|
end
|
122
|
+
|
123
|
+
def fire_utilization_update_event
|
124
|
+
fire_event :utilization_update, false, {
|
125
|
+
group: @group,
|
126
|
+
max_processors: @max_processors,
|
127
|
+
busy_processors: busy
|
128
|
+
}
|
129
|
+
end
|
111
130
|
end
|
112
131
|
end
|
data/lib/shoryuken/options.rb
CHANGED
@@ -38,6 +38,11 @@ module Shoryuken
|
|
38
38
|
.reverse
|
39
39
|
end
|
40
40
|
|
41
|
+
def message_processed(queue)
|
42
|
+
logger.debug "Unpausing #{queue}"
|
43
|
+
@paused_until[queue] = Time.now
|
44
|
+
end
|
45
|
+
|
41
46
|
private
|
42
47
|
|
43
48
|
def next_active_queue
|
@@ -70,6 +75,7 @@ module Shoryuken
|
|
70
75
|
|
71
76
|
def pause(queue)
|
72
77
|
return unless delay > 0
|
78
|
+
|
73
79
|
@paused_until[queue] = Time.now + delay
|
74
80
|
logger.debug "Paused #{queue}"
|
75
81
|
end
|
@@ -35,10 +35,20 @@ module Shoryuken
|
|
35
35
|
unparse_queues(@queues)
|
36
36
|
end
|
37
37
|
|
38
|
+
def message_processed(queue)
|
39
|
+
return if @paused_queues.empty?
|
40
|
+
|
41
|
+
logger.debug "Unpausing #{queue}"
|
42
|
+
@paused_queues.reject! { |_time, name| name == queue }
|
43
|
+
@queues << queue
|
44
|
+
@queues.uniq!
|
45
|
+
end
|
46
|
+
|
38
47
|
private
|
39
48
|
|
40
49
|
def pause(queue)
|
41
50
|
return unless @queues.delete(queue)
|
51
|
+
|
42
52
|
@paused_queues << [Time.now + delay, queue]
|
43
53
|
logger.debug "Paused #{queue}"
|
44
54
|
end
|
@@ -46,6 +56,7 @@ module Shoryuken
|
|
46
56
|
def unpause_queues
|
47
57
|
return if @paused_queues.empty?
|
48
58
|
return if Time.now < @paused_queues.first[0]
|
59
|
+
|
49
60
|
pause = @paused_queues.shift
|
50
61
|
@queues << pause[1]
|
51
62
|
logger.debug "Unpaused #{pause[1]}"
|
data/lib/shoryuken/queue.rb
CHANGED
@@ -8,9 +8,9 @@ module Shoryuken
|
|
8
8
|
|
9
9
|
attr_accessor :name, :client, :url
|
10
10
|
|
11
|
-
def initialize(client,
|
11
|
+
def initialize(client, name_or_url_or_arn)
|
12
12
|
self.client = client
|
13
|
-
set_name_and_url(
|
13
|
+
set_name_and_url(name_or_url_or_arn)
|
14
14
|
end
|
15
15
|
|
16
16
|
def visibility_timeout
|
@@ -50,32 +50,60 @@ module Shoryuken
|
|
50
50
|
# Make sure the memoization work with boolean to avoid multiple calls to SQS
|
51
51
|
# see https://github.com/phstc/shoryuken/pull/529
|
52
52
|
return @_fifo if defined?(@_fifo)
|
53
|
+
|
53
54
|
@_fifo = queue_attributes.attributes[FIFO_ATTR] == 'true'
|
55
|
+
@_fifo
|
54
56
|
end
|
55
57
|
|
56
58
|
private
|
57
59
|
|
58
|
-
def
|
60
|
+
def initialize_fifo_attribute
|
61
|
+
# calling fifo? will also initialize it
|
62
|
+
fifo?
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_by_name(name) # rubocop:disable Naming/AccessorMethodName
|
59
66
|
self.name = name
|
60
67
|
self.url = client.get_queue_url(queue_name: name).queue_url
|
61
68
|
end
|
62
69
|
|
63
|
-
def set_by_url(url)
|
70
|
+
def set_by_url(url) # rubocop:disable Naming/AccessorMethodName
|
64
71
|
self.name = url.split('/').last
|
65
72
|
self.url = url
|
66
73
|
end
|
67
74
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
75
|
+
def arn_to_url(arn_str)
|
76
|
+
*, region, account_id, resource = arn_str.split(':')
|
77
|
+
|
78
|
+
required = [region, account_id, resource].map(&:to_s)
|
79
|
+
valid = required.none?(&:empty?)
|
80
|
+
|
81
|
+
abort "Invalid ARN: #{arn_str}. A valid ARN must include: region, account_id and resource." unless valid
|
82
|
+
|
83
|
+
"https://sqs.#{region}.amazonaws.com/#{account_id}/#{resource}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_name_and_url(name_or_url_or_arn) # rubocop:disable Naming/AccessorMethodName
|
87
|
+
if name_or_url_or_arn.include?('://')
|
88
|
+
set_by_url(name_or_url_or_arn)
|
89
|
+
|
90
|
+
# anticipate the fifo? checker for validating the queue URL
|
91
|
+
initialize_fifo_attribute
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
if name_or_url_or_arn.start_with?('arn:')
|
96
|
+
url = arn_to_url(name_or_url_or_arn)
|
97
|
+
set_by_url(url)
|
71
98
|
|
72
99
|
# anticipate the fifo? checker for validating the queue URL
|
73
|
-
|
100
|
+
initialize_fifo_attribute
|
101
|
+
return
|
74
102
|
end
|
75
103
|
|
76
|
-
set_by_name(
|
77
|
-
rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue =>
|
78
|
-
raise
|
104
|
+
set_by_name(name_or_url_or_arn)
|
105
|
+
rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue => e
|
106
|
+
raise e, "The specified queue #{name_or_url_or_arn} does not exist."
|
79
107
|
end
|
80
108
|
|
81
109
|
def queue_attributes
|
data/lib/shoryuken/version.rb
CHANGED
data/lib/shoryuken.rb
CHANGED
data/shoryuken.gemspec
CHANGED
@@ -4,10 +4,37 @@ require 'shoryuken/launcher'
|
|
4
4
|
require 'securerandom'
|
5
5
|
|
6
6
|
RSpec.describe Shoryuken::Launcher do
|
7
|
-
|
7
|
+
let(:sqs_client) do
|
8
|
+
Aws::SQS::Client.new(
|
9
|
+
region: 'us-east-1',
|
10
|
+
endpoint: 'http://localhost:5000',
|
11
|
+
access_key_id: 'fake',
|
12
|
+
secret_access_key: 'fake'
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:executor) do
|
17
|
+
# We can't use Concurrent.global_io_executor in these tests since once you
|
18
|
+
# shut down a thread pool, you can't start it back up. Instead, we create
|
19
|
+
# one new thread pool executor for each spec. We use a new
|
20
|
+
# CachedThreadPool, since that most closely resembles
|
21
|
+
# Concurrent.global_io_executor
|
22
|
+
Concurrent::CachedThreadPool.new auto_terminate: true
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'Consuming messages' do
|
8
26
|
before do
|
9
27
|
Aws.config[:stub_responses] = false
|
10
|
-
|
28
|
+
|
29
|
+
allow(Shoryuken).to receive(:launcher_executor).and_return(executor)
|
30
|
+
|
31
|
+
Shoryuken.configure_client do |config|
|
32
|
+
config.sqs_client = sqs_client
|
33
|
+
end
|
34
|
+
|
35
|
+
Shoryuken.configure_server do |config|
|
36
|
+
config.sqs_client = sqs_client
|
37
|
+
end
|
11
38
|
|
12
39
|
StandardWorker.received_messages = 0
|
13
40
|
|