shoryuken 5.0.4 → 5.2.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/.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
|
|