shoryuken 3.0.6 → 4.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/.rubocop.yml +90 -24
- data/.travis.yml +17 -5
- data/CHANGELOG.md +265 -62
- data/Gemfile +9 -1
- data/Gemfile.aws-sdk-core-v2 +13 -0
- data/README.md +19 -113
- data/Rakefile +1 -1
- data/bin/cli/base.rb +0 -3
- data/bin/cli/sqs.rb +42 -16
- data/bin/shoryuken +4 -9
- data/examples/bootstrap_queues.rb +3 -3
- data/examples/default_worker.rb +2 -2
- data/lib/shoryuken/body_parser.rb +27 -0
- data/lib/shoryuken/client.rb +6 -2
- data/lib/shoryuken/core_ext.rb +1 -1
- data/lib/shoryuken/default_worker_registry.rb +2 -2
- data/lib/shoryuken/environment_loader.rb +60 -24
- data/lib/shoryuken/extensions/active_job_adapter.rb +21 -11
- data/lib/shoryuken/fetcher.rb +58 -19
- data/lib/shoryuken/launcher.rb +70 -7
- data/lib/shoryuken/logging.rb +1 -6
- data/lib/shoryuken/manager.rb +50 -80
- data/lib/shoryuken/middleware/chain.rb +4 -0
- data/lib/shoryuken/middleware/server/active_record.rb +1 -1
- data/lib/shoryuken/middleware/server/auto_delete.rb +4 -9
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +6 -9
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +9 -3
- data/lib/shoryuken/middleware/server/timing.rb +12 -16
- data/lib/shoryuken/options.rb +225 -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 +30 -39
- data/lib/shoryuken/queue.rb +41 -10
- data/lib/shoryuken/runner.rb +13 -17
- data/lib/shoryuken/util.rb +3 -3
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker/default_executor.rb +33 -0
- data/lib/shoryuken/worker/inline_executor.rb +37 -0
- data/lib/shoryuken/worker.rb +76 -31
- data/lib/shoryuken/worker_registry.rb +4 -4
- data/lib/shoryuken.rb +54 -173
- data/shoryuken.gemspec +6 -6
- data/spec/integration/launcher_spec.rb +14 -8
- data/spec/shoryuken/body_parser_spec.rb +89 -0
- data/spec/shoryuken/client_spec.rb +1 -1
- data/spec/shoryuken/core_ext_spec.rb +6 -6
- data/spec/shoryuken/default_worker_registry_spec.rb +2 -4
- data/spec/shoryuken/environment_loader_spec.rb +32 -12
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +64 -0
- data/spec/shoryuken/fetcher_spec.rb +101 -18
- data/spec/shoryuken/manager_spec.rb +54 -26
- data/spec/shoryuken/middleware/chain_spec.rb +17 -5
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +9 -7
- data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +4 -4
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +6 -4
- data/spec/shoryuken/middleware/server/timing_spec.rb +5 -3
- data/spec/shoryuken/options_spec.rb +180 -0
- data/spec/shoryuken/{polling_spec.rb → polling/strict_priority_spec.rb} +2 -101
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +99 -0
- data/spec/shoryuken/processor_spec.rb +26 -127
- data/spec/shoryuken/queue_spec.rb +115 -41
- data/spec/shoryuken/runner_spec.rb +3 -4
- data/spec/shoryuken/util_spec.rb +24 -0
- data/spec/shoryuken/worker/default_executor_spec.rb +105 -0
- data/spec/shoryuken/worker/inline_executor_spec.rb +49 -0
- data/spec/shoryuken/worker_spec.rb +35 -96
- data/spec/shoryuken_spec.rb +0 -59
- data/spec/spec_helper.rb +14 -3
- data/test_workers/endless_interruptive_worker.rb +2 -2
- data/test_workers/endless_uninterruptive_worker.rb +4 -4
- metadata +31 -12
- data/lib/shoryuken/polling.rb +0 -204
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
module Shoryuken
|
|
2
|
+
class Options
|
|
3
|
+
DEFAULTS = {
|
|
4
|
+
concurrency: 25,
|
|
5
|
+
queues: [],
|
|
6
|
+
aws: {},
|
|
7
|
+
delay: 0,
|
|
8
|
+
timeout: 8,
|
|
9
|
+
lifecycle_events: {
|
|
10
|
+
startup: [],
|
|
11
|
+
dispatch: [],
|
|
12
|
+
quiet: [],
|
|
13
|
+
shutdown: []
|
|
14
|
+
}
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
@@groups = {}
|
|
18
|
+
@@worker_registry = DefaultWorkerRegistry.new
|
|
19
|
+
@@active_job_queue_name_prefixing = false
|
|
20
|
+
@@sqs_client = nil
|
|
21
|
+
@@sqs_client_receive_message_opts = {}
|
|
22
|
+
@@start_callback = nil
|
|
23
|
+
@@stop_callback = nil
|
|
24
|
+
@@worker_executor = Worker::DefaultExecutor
|
|
25
|
+
@@launcher_executor = nil
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def active_job?
|
|
29
|
+
defined?(::ActiveJob)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def add_group(group, concurrency)
|
|
33
|
+
groups[group] ||= {
|
|
34
|
+
concurrency: concurrency,
|
|
35
|
+
queues: []
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def groups
|
|
40
|
+
@@groups
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def add_queue(queue, weight, group)
|
|
44
|
+
weight.times do
|
|
45
|
+
groups[group][:queues] << queue
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def ungrouped_queues
|
|
50
|
+
groups.values.flat_map { |options| options[:queues] }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def worker_registry
|
|
54
|
+
@@worker_registry
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def worker_registry=(worker_registry)
|
|
58
|
+
@@worker_registry = worker_registry
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def worker_executor
|
|
62
|
+
@@worker_executor
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def worker_executor=(worker_executor)
|
|
66
|
+
@@worker_executor = worker_executor
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def launcher_executor
|
|
70
|
+
@@launcher_executor
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def launcher_executor=(launcher_executor)
|
|
74
|
+
@@launcher_executor = launcher_executor
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def polling_strategy(group)
|
|
78
|
+
strategy = (group == 'default' ? options : options[:groups].to_h[group]).to_h[:polling_strategy]
|
|
79
|
+
|
|
80
|
+
case strategy
|
|
81
|
+
when 'WeightedRoundRobin', nil # Default case
|
|
82
|
+
Polling::WeightedRoundRobin
|
|
83
|
+
when 'StrictPriority'
|
|
84
|
+
Polling::StrictPriority
|
|
85
|
+
when Class
|
|
86
|
+
strategy
|
|
87
|
+
else
|
|
88
|
+
raise ArgumentError, "#{strategy} is not a valid polling_strategy"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def start_callback
|
|
93
|
+
@@start_callback
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def start_callback=(start_callback)
|
|
97
|
+
@@start_callback = start_callback
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def stop_callback
|
|
101
|
+
@@stop_callback
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def stop_callback=(stop_callback)
|
|
105
|
+
@@stop_callback = stop_callback
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def active_job_queue_name_prefixing
|
|
109
|
+
@@active_job_queue_name_prefixing
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def active_job_queue_name_prefixing=(active_job_queue_name_prefixing)
|
|
113
|
+
@@active_job_queue_name_prefixing = active_job_queue_name_prefixing
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def sqs_client
|
|
117
|
+
@@sqs_client ||= Aws::SQS::Client.new
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def sqs_client=(sqs_client)
|
|
121
|
+
@@sqs_client = sqs_client
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def sqs_client_receive_message_opts
|
|
125
|
+
@@sqs_client_receive_message_opts
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def sqs_client_receive_message_opts=(sqs_client_receive_message_opts)
|
|
129
|
+
@@sqs_client_receive_message_opts['default'] = sqs_client_receive_message_opts
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def options
|
|
133
|
+
@@options ||= DEFAULTS.dup
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def logger
|
|
137
|
+
Shoryuken::Logging.logger
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def register_worker(*args)
|
|
141
|
+
@@worker_registry.register_worker(*args)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def configure_server
|
|
145
|
+
yield self if server?
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def server_middleware
|
|
149
|
+
@@server_chain ||= default_server_middleware
|
|
150
|
+
yield @@server_chain if block_given?
|
|
151
|
+
@@server_chain
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def configure_client
|
|
155
|
+
yield self unless server?
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def client_middleware
|
|
159
|
+
@@client_chain ||= default_client_middleware
|
|
160
|
+
yield @@client_chain if block_given?
|
|
161
|
+
@@client_chain
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def default_worker_options
|
|
165
|
+
@@default_worker_options ||= {
|
|
166
|
+
'queue' => 'default',
|
|
167
|
+
'delete' => false,
|
|
168
|
+
'auto_delete' => false,
|
|
169
|
+
'auto_visibility_timeout' => false,
|
|
170
|
+
'retry_intervals' => nil,
|
|
171
|
+
'batch' => false
|
|
172
|
+
}
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def default_worker_options=(default_worker_options)
|
|
176
|
+
@@default_worker_options = default_worker_options
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def on_start(&block)
|
|
180
|
+
@@start_callback = block
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def on_stop(&block)
|
|
184
|
+
@@stop_callback = block
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Register a block to run at a point in the Shoryuken lifecycle.
|
|
188
|
+
# :startup, :quiet or :shutdown are valid events.
|
|
189
|
+
#
|
|
190
|
+
# Shoryuken.configure_server do |config|
|
|
191
|
+
# config.on(:shutdown) do
|
|
192
|
+
# puts "Goodbye cruel world!"
|
|
193
|
+
# end
|
|
194
|
+
# end
|
|
195
|
+
def on(event, &block)
|
|
196
|
+
fail ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
|
|
197
|
+
fail ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
|
|
198
|
+
options[:lifecycle_events][event] << block
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def default_server_middleware
|
|
204
|
+
Middleware::Chain.new do |m|
|
|
205
|
+
m.add Middleware::Server::Timing
|
|
206
|
+
m.add Middleware::Server::ExponentialBackoffRetry
|
|
207
|
+
m.add Middleware::Server::AutoDelete
|
|
208
|
+
m.add Middleware::Server::AutoExtendVisibility
|
|
209
|
+
if defined?(::ActiveRecord::Base)
|
|
210
|
+
require 'shoryuken/middleware/server/active_record'
|
|
211
|
+
m.add Middleware::Server::ActiveRecord
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def default_client_middleware
|
|
217
|
+
Middleware::Chain.new
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def server?
|
|
221
|
+
defined?(Shoryuken::CLI)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Shoryuken
|
|
2
|
+
module Polling
|
|
3
|
+
QueueConfiguration = Struct.new(:name, :options) do
|
|
4
|
+
def hash
|
|
5
|
+
name.hash
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def ==(other)
|
|
9
|
+
case other
|
|
10
|
+
when String
|
|
11
|
+
if options.empty?
|
|
12
|
+
name == other
|
|
13
|
+
else
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
alias_method :eql?, :==
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
if options.empty?
|
|
25
|
+
name
|
|
26
|
+
else
|
|
27
|
+
"#<QueueConfiguration #{name} options=#{options.inspect}>"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class BaseStrategy
|
|
33
|
+
include Util
|
|
34
|
+
|
|
35
|
+
def next_queue
|
|
36
|
+
fail NotImplementedError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def messages_found(_queue, _messages_found)
|
|
40
|
+
fail NotImplementedError
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def active_queues
|
|
44
|
+
fail NotImplementedError
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def ==(other)
|
|
48
|
+
case other
|
|
49
|
+
when Array
|
|
50
|
+
@queues == other
|
|
51
|
+
else
|
|
52
|
+
if other.respond_to?(:active_queues)
|
|
53
|
+
active_queues == other.active_queues
|
|
54
|
+
else
|
|
55
|
+
false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def delay
|
|
63
|
+
Shoryuken.options[:delay].to_f
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Shoryuken
|
|
2
|
+
module Polling
|
|
3
|
+
class StrictPriority < BaseStrategy
|
|
4
|
+
def initialize(queues)
|
|
5
|
+
# Priority ordering of the queues, highest priority first
|
|
6
|
+
@queues = queues
|
|
7
|
+
.group_by { |q| q }
|
|
8
|
+
.sort_by { |_, qs| -qs.count }
|
|
9
|
+
.map(&:first)
|
|
10
|
+
|
|
11
|
+
# Pause status of the queues, default to past time (unpaused)
|
|
12
|
+
@paused_until = queues
|
|
13
|
+
.each_with_object({}) { |queue, h| h[queue] = Time.at(0) }
|
|
14
|
+
|
|
15
|
+
# Start queues at 0
|
|
16
|
+
reset_next_queue
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def next_queue
|
|
20
|
+
next_queue = next_active_queue
|
|
21
|
+
next_queue.nil? ? nil : QueueConfiguration.new(next_queue, {})
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def messages_found(queue, messages_found)
|
|
25
|
+
if messages_found == 0
|
|
26
|
+
pause(queue)
|
|
27
|
+
else
|
|
28
|
+
reset_next_queue
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def active_queues
|
|
33
|
+
@queues
|
|
34
|
+
.reverse
|
|
35
|
+
.map.with_index(1)
|
|
36
|
+
.reject { |q, _| queue_paused?(q) }
|
|
37
|
+
.reverse
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def next_active_queue
|
|
43
|
+
reset_next_queue if queues_unpaused_since?
|
|
44
|
+
|
|
45
|
+
size = @queues.length
|
|
46
|
+
size.times do
|
|
47
|
+
queue = @queues[@next_queue_index]
|
|
48
|
+
@next_queue_index = (@next_queue_index + 1) % size
|
|
49
|
+
return queue unless queue_paused?(queue)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def queues_unpaused_since?
|
|
56
|
+
last = @last_unpause_check
|
|
57
|
+
now = @last_unpause_check = Time.now
|
|
58
|
+
|
|
59
|
+
last && @paused_until.values.any? { |t| t > last && t <= now }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def reset_next_queue
|
|
63
|
+
@next_queue_index = 0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def queue_paused?(queue)
|
|
67
|
+
@paused_until[queue] > Time.now
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def pause(queue)
|
|
71
|
+
return unless delay > 0
|
|
72
|
+
@paused_until[queue] = Time.now + delay
|
|
73
|
+
logger.debug "Paused #{queue}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Shoryuken
|
|
2
|
+
module Polling
|
|
3
|
+
class WeightedRoundRobin < BaseStrategy
|
|
4
|
+
def initialize(queues)
|
|
5
|
+
@initial_queues = queues
|
|
6
|
+
@queues = queues.dup.uniq
|
|
7
|
+
@paused_queues = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def next_queue
|
|
11
|
+
unpause_queues
|
|
12
|
+
queue = @queues.shift
|
|
13
|
+
return nil if queue.nil?
|
|
14
|
+
|
|
15
|
+
@queues << queue
|
|
16
|
+
QueueConfiguration.new(queue, {})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def messages_found(queue, messages_found)
|
|
20
|
+
if messages_found == 0
|
|
21
|
+
pause(queue)
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
maximum_weight = maximum_queue_weight(queue)
|
|
26
|
+
current_weight = current_queue_weight(queue)
|
|
27
|
+
if maximum_weight > current_weight
|
|
28
|
+
logger.info { "Increasing #{queue} weight to #{current_weight + 1}, max: #{maximum_weight}" }
|
|
29
|
+
@queues << queue
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def active_queues
|
|
34
|
+
unparse_queues(@queues)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def pause(queue)
|
|
40
|
+
return unless @queues.delete(queue)
|
|
41
|
+
@paused_queues << [Time.now + delay, queue]
|
|
42
|
+
logger.debug "Paused #{queue}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def unpause_queues
|
|
46
|
+
return if @paused_queues.empty?
|
|
47
|
+
return if Time.now < @paused_queues.first[0]
|
|
48
|
+
pause = @paused_queues.shift
|
|
49
|
+
@queues << pause[1]
|
|
50
|
+
logger.debug "Unpaused #{pause[1]}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def current_queue_weight(queue)
|
|
54
|
+
queue_weight(@queues, queue)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def maximum_queue_weight(queue)
|
|
58
|
+
queue_weight(@initial_queues, queue)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def queue_weight(queues, queue)
|
|
62
|
+
queues.count { |q| q == queue }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/shoryuken/processor.rb
CHANGED
|
@@ -2,57 +2,48 @@ module Shoryuken
|
|
|
2
2
|
class Processor
|
|
3
3
|
include Util
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
attr_reader :queue, :sqs_msg
|
|
6
|
+
|
|
7
|
+
def self.process(queue, sqs_msg)
|
|
8
|
+
new(queue, sqs_msg).process
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(queue, sqs_msg)
|
|
12
|
+
@queue = queue
|
|
13
|
+
@sqs_msg = sqs_msg
|
|
7
14
|
end
|
|
8
15
|
|
|
9
|
-
def process
|
|
10
|
-
worker
|
|
11
|
-
body = get_body(worker.class, sqs_msg)
|
|
16
|
+
def process
|
|
17
|
+
return logger.error { "No worker found for #{queue}" } unless worker
|
|
12
18
|
|
|
13
|
-
worker.class
|
|
14
|
-
worker.
|
|
19
|
+
Shoryuken::Logging.with_context("#{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id}") do
|
|
20
|
+
worker.class.server_middleware.invoke(worker, queue, sqs_msg, body) do
|
|
21
|
+
worker.perform(sqs_msg, body)
|
|
22
|
+
end
|
|
15
23
|
end
|
|
16
24
|
rescue Exception => ex
|
|
17
|
-
|
|
25
|
+
logger.error { "Processor failed: #{ex.message}" }
|
|
26
|
+
logger.error { ex.backtrace.join("\n") } unless ex.backtrace.nil?
|
|
27
|
+
|
|
18
28
|
raise
|
|
19
|
-
ensure
|
|
20
|
-
@manager.processor_done(queue)
|
|
21
29
|
end
|
|
22
30
|
|
|
23
31
|
private
|
|
24
32
|
|
|
25
|
-
def
|
|
26
|
-
|
|
27
|
-
sqs_msg.map { |m| parse_body(worker_class, m) }
|
|
28
|
-
else
|
|
29
|
-
parse_body(worker_class, sqs_msg)
|
|
30
|
-
end
|
|
33
|
+
def worker
|
|
34
|
+
@_worker ||= Shoryuken.worker_registry.fetch_worker(queue, sqs_msg)
|
|
31
35
|
end
|
|
32
36
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
else
|
|
44
|
-
if body_parser.respond_to?(:parse)
|
|
45
|
-
# JSON.parse
|
|
46
|
-
body_parser.parse(sqs_msg.body)
|
|
47
|
-
elsif body_parser.respond_to?(:load)
|
|
48
|
-
# see https://github.com/phstc/shoryuken/pull/91
|
|
49
|
-
# JSON.load
|
|
50
|
-
body_parser.load(sqs_msg.body)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
rescue => e
|
|
54
|
-
logger.error { "Error parsing the message body: #{e.message}\nbody_parser: #{body_parser}\nsqs_msg.body: #{sqs_msg.body}" }
|
|
55
|
-
raise
|
|
37
|
+
def worker_class
|
|
38
|
+
worker.class
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def body
|
|
42
|
+
@_body ||= sqs_msg.is_a?(Array) ? sqs_msg.map(&method(:parse_body)) : parse_body(sqs_msg)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse_body(sqs_msg)
|
|
46
|
+
BodyParser.parse(worker_class, sqs_msg)
|
|
56
47
|
end
|
|
57
48
|
end
|
|
58
49
|
end
|
data/lib/shoryuken/queue.rb
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
module Shoryuken
|
|
2
2
|
class Queue
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
include Util
|
|
4
|
+
|
|
5
|
+
FIFO_ATTR = 'FifoQueue'.freeze
|
|
6
|
+
MESSAGE_GROUP_ID = 'ShoryukenMessage'.freeze
|
|
7
|
+
VISIBILITY_TIMEOUT_ATTR = 'VisibilityTimeout'.freeze
|
|
6
8
|
|
|
7
9
|
attr_accessor :name, :client, :url
|
|
8
10
|
|
|
9
|
-
def initialize(client,
|
|
10
|
-
self.name = name
|
|
11
|
+
def initialize(client, name_or_url)
|
|
11
12
|
self.client = client
|
|
12
|
-
|
|
13
|
-
rescue Aws::SQS::Errors::NonExistentQueue => e
|
|
14
|
-
raise e, "The specified queue '#{name}' does not exist."
|
|
13
|
+
set_name_and_url(name_or_url)
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def visibility_timeout
|
|
@@ -19,7 +18,13 @@ module Shoryuken
|
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def delete_messages(options)
|
|
22
|
-
client.delete_message_batch(
|
|
21
|
+
client.delete_message_batch(
|
|
22
|
+
options.merge(queue_url: url)
|
|
23
|
+
).failed.any? do |failure|
|
|
24
|
+
logger.error do
|
|
25
|
+
"Could not delete #{failure.id}, code: '#{failure.code}', message: '#{failure.message}', sender_fault: #{failure.sender_fault}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
def send_message(options)
|
|
@@ -39,11 +44,37 @@ module Shoryuken
|
|
|
39
44
|
end
|
|
40
45
|
|
|
41
46
|
def fifo?
|
|
42
|
-
|
|
47
|
+
# Make sure the memoization work with boolean to avoid multiple calls to SQS
|
|
48
|
+
# see https://github.com/phstc/shoryuken/pull/529
|
|
49
|
+
return @_fifo if defined?(@_fifo)
|
|
50
|
+
@_fifo = queue_attributes.attributes[FIFO_ATTR] == 'true'
|
|
43
51
|
end
|
|
44
52
|
|
|
45
53
|
private
|
|
46
54
|
|
|
55
|
+
def set_by_name(name)
|
|
56
|
+
self.name = name
|
|
57
|
+
self.url = client.get_queue_url(queue_name: name).queue_url
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def set_by_url(url)
|
|
61
|
+
self.name = url.split('/').last
|
|
62
|
+
self.url = url
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def set_name_and_url(name_or_url)
|
|
66
|
+
if name_or_url.start_with?('https://sqs.')
|
|
67
|
+
set_by_url(name_or_url)
|
|
68
|
+
|
|
69
|
+
# anticipate the fifo? checker for validating the queue URL
|
|
70
|
+
return fifo?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
set_by_name(name_or_url)
|
|
74
|
+
rescue Aws::Errors::NoSuchEndpointError, Aws::SQS::Errors::NonExistentQueue => ex
|
|
75
|
+
raise ex, "The specified queue #{name_or_url} does not exist."
|
|
76
|
+
end
|
|
77
|
+
|
|
47
78
|
def queue_attributes
|
|
48
79
|
# Note: Retrieving all queue attributes as requesting `FifoQueue` on non-FIFO queue raises error.
|
|
49
80
|
# See issue: https://github.com/aws/aws-sdk-ruby/issues/1350
|
data/lib/shoryuken/runner.rb
CHANGED
|
@@ -8,7 +8,6 @@ require 'shoryuken'
|
|
|
8
8
|
|
|
9
9
|
module Shoryuken
|
|
10
10
|
# rubocop:disable Lint/InheritException
|
|
11
|
-
# rubocop:disable Metrics/AbcSize
|
|
12
11
|
# See: https://github.com/mperham/sidekiq/blob/33f5d6b2b6c0dfaab11e5d39688cab7ebadc83ae/lib/sidekiq/cli.rb#L20
|
|
13
12
|
class Shutdown < Interrupt; end
|
|
14
13
|
|
|
@@ -19,7 +18,7 @@ module Shoryuken
|
|
|
19
18
|
def run(options)
|
|
20
19
|
self_read, self_write = IO.pipe
|
|
21
20
|
|
|
22
|
-
%w
|
|
21
|
+
%w[INT TERM USR1 TSTP TTIN].each do |sig|
|
|
23
22
|
begin
|
|
24
23
|
trap sig do
|
|
25
24
|
self_write.puts(sig)
|
|
@@ -43,22 +42,15 @@ module Shoryuken
|
|
|
43
42
|
|
|
44
43
|
@launcher = Shoryuken::Launcher.new
|
|
45
44
|
|
|
46
|
-
if (callback = Shoryuken.start_callback)
|
|
47
|
-
logger.info { 'Calling Shoryuken.on_start block' }
|
|
48
|
-
callback.call
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
fire_event(:startup)
|
|
52
|
-
|
|
53
45
|
begin
|
|
54
|
-
@launcher.
|
|
46
|
+
@launcher.start
|
|
55
47
|
|
|
56
48
|
while (readable_io = IO.select([self_read]))
|
|
57
49
|
signal = readable_io.first[0].gets.strip
|
|
58
50
|
handle_signal(signal)
|
|
59
51
|
end
|
|
60
52
|
rescue Interrupt
|
|
61
|
-
@launcher.stop
|
|
53
|
+
@launcher.stop!
|
|
62
54
|
exit 0
|
|
63
55
|
end
|
|
64
56
|
end
|
|
@@ -110,10 +102,15 @@ module Shoryuken
|
|
|
110
102
|
logger.info { 'Received USR1, will soft shutdown down' }
|
|
111
103
|
|
|
112
104
|
@launcher.stop
|
|
113
|
-
fire_event(:quiet, true)
|
|
114
105
|
exit 0
|
|
115
106
|
end
|
|
116
107
|
|
|
108
|
+
def execute_terminal_stop
|
|
109
|
+
logger.info { 'Received TSTP, will stop accepting new work' }
|
|
110
|
+
|
|
111
|
+
@launcher.stop
|
|
112
|
+
end
|
|
113
|
+
|
|
117
114
|
def print_threads_backtrace
|
|
118
115
|
Thread.list.each do |thread|
|
|
119
116
|
logger.info { "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}" }
|
|
@@ -126,15 +123,14 @@ module Shoryuken
|
|
|
126
123
|
end
|
|
127
124
|
|
|
128
125
|
def handle_signal(sig)
|
|
129
|
-
logger.
|
|
126
|
+
logger.debug "Got #{sig} signal"
|
|
130
127
|
|
|
131
128
|
case sig
|
|
132
129
|
when 'USR1' then execute_soft_shutdown
|
|
133
130
|
when 'TTIN' then print_threads_backtrace
|
|
134
|
-
when '
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
logger.info { "Received #{sig}, will shutdown down" }
|
|
131
|
+
when 'TSTP' then execute_terminal_stop
|
|
132
|
+
when 'TERM', 'INT'
|
|
133
|
+
logger.info { "Received #{sig}, will shutdown" }
|
|
138
134
|
|
|
139
135
|
raise Interrupt
|
|
140
136
|
end
|
data/lib/shoryuken/util.rb
CHANGED
|
@@ -4,13 +4,13 @@ module Shoryuken
|
|
|
4
4
|
Shoryuken.logger
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
def fire_event(event, reverse = false)
|
|
7
|
+
def fire_event(event, reverse = false, event_options = {})
|
|
8
8
|
logger.debug { "Firing '#{event}' lifecycle event" }
|
|
9
9
|
arr = Shoryuken.options[:lifecycle_events][event]
|
|
10
10
|
arr.reverse! if reverse
|
|
11
11
|
arr.each do |block|
|
|
12
12
|
begin
|
|
13
|
-
block.call
|
|
13
|
+
block.call(event_options)
|
|
14
14
|
rescue => ex
|
|
15
15
|
logger.warn(event: event)
|
|
16
16
|
logger.warn "#{ex.class.name}: #{ex.message}"
|
|
@@ -30,7 +30,7 @@ module Shoryuken
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def worker_name(worker_class, sqs_msg, body = nil)
|
|
33
|
-
if
|
|
33
|
+
if Shoryuken.active_job? \
|
|
34
34
|
&& !sqs_msg.is_a?(Array) \
|
|
35
35
|
&& sqs_msg.message_attributes \
|
|
36
36
|
&& sqs_msg.message_attributes['shoryuken_class'] \
|