shoryuken 2.1.1 → 3.1.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/.codeclimate.yml +5 -8
- data/.rubocop.yml +8 -2
- data/.travis.yml +6 -0
- data/CHANGELOG.md +167 -0
- data/Gemfile +2 -0
- data/README.md +16 -155
- data/Rakefile +0 -1
- data/bin/cli/base.rb +40 -0
- data/bin/cli/sqs.rb +204 -0
- data/bin/shoryuken +55 -9
- data/examples/default_worker.rb +1 -1
- data/lib/shoryuken/client.rb +3 -15
- data/lib/shoryuken/default_worker_registry.rb +10 -6
- data/lib/shoryuken/environment_loader.rb +60 -57
- data/lib/shoryuken/fetcher.rb +21 -51
- data/lib/shoryuken/launcher.rb +77 -23
- data/lib/shoryuken/logging.rb +0 -5
- data/lib/shoryuken/manager.rb +54 -205
- data/lib/shoryuken/message.rb +4 -13
- data/lib/shoryuken/middleware/chain.rb +1 -18
- data/lib/shoryuken/middleware/server/auto_delete.rb +3 -8
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +16 -17
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +37 -22
- data/lib/shoryuken/middleware/server/timing.rb +2 -2
- data/lib/shoryuken/options.rb +196 -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 +27 -25
- data/lib/shoryuken/queue.rb +28 -9
- data/lib/shoryuken/runner.rb +131 -0
- data/lib/shoryuken/util.rb +1 -9
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker.rb +9 -1
- data/lib/shoryuken.rb +47 -157
- data/shoryuken.gemspec +7 -7
- data/spec/integration/launcher_spec.rb +15 -8
- data/spec/shoryuken/client_spec.rb +2 -45
- data/spec/shoryuken/default_worker_registry_spec.rb +12 -10
- data/spec/shoryuken/environment_loader_spec.rb +54 -0
- data/spec/shoryuken/fetcher_spec.rb +27 -46
- data/spec/shoryuken/manager_spec.rb +80 -93
- data/spec/shoryuken/middleware/chain_spec.rb +0 -24
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +2 -2
- data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +7 -3
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +56 -33
- data/spec/shoryuken/middleware/server/timing_spec.rb +5 -3
- data/spec/shoryuken/options_spec.rb +100 -0
- data/spec/shoryuken/polling/strict_priority_spec.rb +140 -0
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +99 -0
- data/spec/shoryuken/processor_spec.rb +20 -37
- data/spec/shoryuken/queue_spec.rb +72 -26
- data/spec/shoryuken/{cli_spec.rb → runner_spec.rb} +11 -26
- data/spec/shoryuken_spec.rb +1 -48
- data/spec/spec_helper.rb +14 -20
- data/test_workers/endless_interruptive_worker.rb +41 -0
- data/test_workers/endless_uninterruptive_worker.rb +44 -0
- metadata +39 -34
- data/lib/shoryuken/aws_config.rb +0 -64
- data/lib/shoryuken/cli.rb +0 -215
- data/lib/shoryuken/sns_arn.rb +0 -27
- data/lib/shoryuken/topic.rb +0 -17
- data/spec/shoryuken/sns_arn_spec.rb +0 -42
- data/spec/shoryuken/topic_spec.rb +0 -32
- data/spec/shoryuken_endpoint.yml +0 -6
data/lib/shoryuken/launcher.rb
CHANGED
|
@@ -1,43 +1,97 @@
|
|
|
1
1
|
module Shoryuken
|
|
2
2
|
class Launcher
|
|
3
|
-
include Celluloid
|
|
4
3
|
include Util
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
def initialize
|
|
6
|
+
@managers = create_managers
|
|
7
|
+
@shutdowning = Concurrent::AtomicBoolean.new(false)
|
|
8
|
+
end
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
def start
|
|
11
|
+
logger.info { 'Starting' }
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
start_callback
|
|
14
|
+
start_managers
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def stop!
|
|
18
|
+
initiate_stop
|
|
19
|
+
|
|
20
|
+
executor.shutdown
|
|
21
|
+
|
|
22
|
+
return if executor.wait_for_termination(Shoryuken.options[:timeout])
|
|
23
|
+
|
|
24
|
+
executor.kill
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def stop
|
|
28
|
+
fire_event(:quiet, true)
|
|
29
|
+
|
|
30
|
+
initiate_stop
|
|
31
|
+
|
|
32
|
+
executor.shutdown
|
|
33
|
+
executor.wait_for_termination
|
|
34
|
+
end
|
|
14
35
|
|
|
15
|
-
|
|
36
|
+
private
|
|
16
37
|
|
|
17
|
-
|
|
38
|
+
def executor
|
|
39
|
+
Concurrent.global_io_executor
|
|
18
40
|
end
|
|
19
41
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
def start_managers
|
|
43
|
+
@managers.each do |manager|
|
|
44
|
+
Concurrent::Promise.execute { manager.start }.rescue do |ex|
|
|
45
|
+
log_manager_failure(ex)
|
|
46
|
+
start_soft_shutdown
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def start_soft_shutdown
|
|
52
|
+
Process.kill('USR1', Process.pid) if @shutdowning.make_true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def log_manager_failure(ex)
|
|
56
|
+
return unless ex
|
|
57
|
+
|
|
58
|
+
logger.error { "Manager failed: #{ex.message}" }
|
|
59
|
+
logger.error { ex.backtrace.join("\n") } unless ex.backtrace.nil?
|
|
60
|
+
end
|
|
24
61
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
62
|
+
def initiate_stop
|
|
63
|
+
logger.info { 'Shutting down' }
|
|
64
|
+
|
|
65
|
+
stop_callback
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def start_callback
|
|
69
|
+
if (callback = Shoryuken.start_callback)
|
|
70
|
+
logger.debug { 'Calling start_callback' }
|
|
71
|
+
callback.call
|
|
28
72
|
end
|
|
73
|
+
|
|
74
|
+
fire_event(:startup)
|
|
29
75
|
end
|
|
30
76
|
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
|
|
77
|
+
def stop_callback
|
|
78
|
+
if (callback = Shoryuken.stop_callback)
|
|
79
|
+
logger.debug { 'Calling stop_callback' }
|
|
80
|
+
callback.call
|
|
34
81
|
end
|
|
82
|
+
|
|
83
|
+
fire_event(:shutdown, true)
|
|
35
84
|
end
|
|
36
85
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
86
|
+
def create_managers
|
|
87
|
+
Shoryuken.groups.map do |group, options|
|
|
88
|
+
Shoryuken::Manager.new(
|
|
89
|
+
Shoryuken::Fetcher.new(group),
|
|
90
|
+
Shoryuken.polling_strategy(group).new(options[:queues]),
|
|
91
|
+
options[:concurrency],
|
|
92
|
+
executor
|
|
93
|
+
)
|
|
94
|
+
end
|
|
41
95
|
end
|
|
42
96
|
end
|
|
43
97
|
end
|
data/lib/shoryuken/logging.rb
CHANGED
|
@@ -3,7 +3,6 @@ require 'logger'
|
|
|
3
3
|
|
|
4
4
|
module Shoryuken
|
|
5
5
|
module Logging
|
|
6
|
-
|
|
7
6
|
class Pretty < Logger::Formatter
|
|
8
7
|
# Provide a call() method that returns the formatted message.
|
|
9
8
|
def call(severity, time, program_name, message)
|
|
@@ -37,9 +36,5 @@ module Shoryuken
|
|
|
37
36
|
def self.logger=(log)
|
|
38
37
|
@logger = (log ? log : Logger.new('/dev/null'))
|
|
39
38
|
end
|
|
40
|
-
|
|
41
|
-
def logger
|
|
42
|
-
shoryuken::Logging.logger
|
|
43
|
-
end
|
|
44
39
|
end
|
|
45
40
|
end
|
data/lib/shoryuken/manager.rb
CHANGED
|
@@ -1,251 +1,100 @@
|
|
|
1
|
-
require 'shoryuken/processor'
|
|
2
|
-
require 'shoryuken/fetcher'
|
|
3
|
-
|
|
4
1
|
module Shoryuken
|
|
5
2
|
class Manager
|
|
6
|
-
include Celluloid
|
|
7
3
|
include Util
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def initialize(condvar)
|
|
14
|
-
@count = Shoryuken.options[:concurrency] || 25
|
|
15
|
-
raise(ArgumentError, "Concurrency value #{@count} is invalid, it needs to be a positive number") unless @count > 0
|
|
16
|
-
@queues = Shoryuken.queues.dup.uniq
|
|
17
|
-
@finished = condvar
|
|
5
|
+
BATCH_LIMIT = 10
|
|
6
|
+
# See https://github.com/phstc/shoryuken/issues/348#issuecomment-292847028
|
|
7
|
+
MIN_DISPATCH_INTERVAL = 0.1
|
|
18
8
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@
|
|
22
|
-
@
|
|
23
|
-
@
|
|
9
|
+
def initialize(fetcher, polling_strategy, concurrency, executor)
|
|
10
|
+
@fetcher = fetcher
|
|
11
|
+
@polling_strategy = polling_strategy
|
|
12
|
+
@max_processors = concurrency
|
|
13
|
+
@busy_processors = Concurrent::AtomicFixnum.new(0)
|
|
14
|
+
@executor = executor
|
|
24
15
|
end
|
|
25
16
|
|
|
26
17
|
def start
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
dispatch
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def stop(options = {})
|
|
33
|
-
watchdog('Manager#stop died') do
|
|
34
|
-
@done = true
|
|
35
|
-
|
|
36
|
-
if (callback = Shoryuken.stop_callback)
|
|
37
|
-
logger.info { 'Calling Shoryuken.on_stop block' }
|
|
38
|
-
callback.call
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
fire_event(:shutdown, true)
|
|
42
|
-
|
|
43
|
-
@fetcher.terminate if @fetcher.alive?
|
|
44
|
-
|
|
45
|
-
logger.info { "Shutting down #{@ready.size} quiet workers" }
|
|
46
|
-
|
|
47
|
-
@ready.each do |processor|
|
|
48
|
-
processor.terminate if processor.alive?
|
|
49
|
-
end
|
|
50
|
-
@ready.clear
|
|
51
|
-
|
|
52
|
-
return after(0) { @finished.signal } if @busy.empty?
|
|
53
|
-
|
|
54
|
-
if options[:shutdown]
|
|
55
|
-
hard_shutdown_in(options[:timeout])
|
|
56
|
-
else
|
|
57
|
-
soft_shutdown(options[:timeout])
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def processor_done(queue, processor)
|
|
63
|
-
watchdog('Manager#processor_done died') do
|
|
64
|
-
logger.debug { "Process done for '#{queue}'" }
|
|
65
|
-
|
|
66
|
-
@threads.delete(processor.object_id)
|
|
67
|
-
@busy.delete processor
|
|
68
|
-
|
|
69
|
-
if stopped?
|
|
70
|
-
processor.terminate if processor.alive?
|
|
71
|
-
return after(0) { @finished.signal } if @busy.empty?
|
|
72
|
-
else
|
|
73
|
-
@ready << processor
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def processor_died(processor, reason)
|
|
79
|
-
watchdog("Manager#processor_died died") do
|
|
80
|
-
logger.error { "Process died, reason: #{reason}" }
|
|
81
|
-
|
|
82
|
-
@threads.delete(processor.object_id)
|
|
83
|
-
@busy.delete processor
|
|
84
|
-
|
|
85
|
-
if stopped?
|
|
86
|
-
return after(0) { @finished.signal } if @busy.empty?
|
|
87
|
-
else
|
|
88
|
-
@ready << build_processor
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def stopped?
|
|
94
|
-
@done
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def assign(queue, sqs_msg)
|
|
98
|
-
watchdog('Manager#assign died') do
|
|
99
|
-
logger.debug { "Assigning #{sqs_msg.message_id}" }
|
|
100
|
-
|
|
101
|
-
processor = @ready.pop
|
|
102
|
-
@busy << processor
|
|
103
|
-
|
|
104
|
-
processor.async.process(queue, sqs_msg)
|
|
105
|
-
end
|
|
18
|
+
dispatch_loop
|
|
106
19
|
end
|
|
107
20
|
|
|
108
|
-
|
|
109
|
-
watchdog('Manager#rebalance_queue_weight! died') do
|
|
110
|
-
if (original = original_queue_weight(queue)) > (current = current_queue_weight(queue))
|
|
111
|
-
logger.info { "Increasing '#{queue}' weight to #{current + 1}, max: #{original}" }
|
|
21
|
+
private
|
|
112
22
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
end
|
|
23
|
+
def running?
|
|
24
|
+
@executor.running?
|
|
116
25
|
end
|
|
117
26
|
|
|
118
|
-
def
|
|
119
|
-
return
|
|
27
|
+
def dispatch_loop
|
|
28
|
+
return unless running?
|
|
120
29
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
after(Shoryuken.options[:delay].to_f) { async.restart_queue!(queue) }
|
|
30
|
+
Concurrent::Promise.execute(
|
|
31
|
+
executor: @executor
|
|
32
|
+
) { dispatch }.then { dispatch_loop }.rescue { |ex| raise ex }
|
|
126
33
|
end
|
|
127
34
|
|
|
128
|
-
|
|
129
35
|
def dispatch
|
|
130
|
-
return
|
|
131
|
-
|
|
132
|
-
logger.debug { "Ready: #{@ready.size}, Busy: #{@busy.size}, Active Queues: #{unparse_queues(@queues)}" }
|
|
36
|
+
return unless running?
|
|
133
37
|
|
|
134
|
-
if @
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
dispatch_later
|
|
138
|
-
return
|
|
38
|
+
if ready <= 0 || (queue = @polling_strategy.next_queue).nil?
|
|
39
|
+
return sleep(MIN_DISPATCH_INTERVAL)
|
|
139
40
|
end
|
|
140
41
|
|
|
141
|
-
|
|
142
|
-
@fetcher.async.fetch(queue, @ready.size)
|
|
143
|
-
else
|
|
144
|
-
logger.debug { 'Pausing fetcher, because all queues are paused' }
|
|
42
|
+
fire_event(:dispatch)
|
|
145
43
|
|
|
146
|
-
|
|
147
|
-
end
|
|
148
|
-
end
|
|
44
|
+
logger.debug { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{@polling_strategy.active_queues}" }
|
|
149
45
|
|
|
150
|
-
|
|
151
|
-
@threads[proxy_id] = thr
|
|
46
|
+
batched_queue?(queue) ? dispatch_batch(queue) : dispatch_single_messages(queue)
|
|
152
47
|
end
|
|
153
48
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def dispatch_later
|
|
157
|
-
@_dispatch_timer ||= after(1) do
|
|
158
|
-
@_dispatch_timer = nil
|
|
159
|
-
dispatch
|
|
160
|
-
end
|
|
49
|
+
def busy
|
|
50
|
+
@busy_processors.value
|
|
161
51
|
end
|
|
162
52
|
|
|
163
|
-
def
|
|
164
|
-
|
|
165
|
-
processor.proxy_id = processor.object_id
|
|
166
|
-
processor
|
|
53
|
+
def ready
|
|
54
|
+
@max_processors - busy
|
|
167
55
|
end
|
|
168
56
|
|
|
169
|
-
def
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
unless @queues.include? queue
|
|
173
|
-
logger.debug { "Restarting '#{queue}'" }
|
|
174
|
-
|
|
175
|
-
@queues << queue
|
|
176
|
-
|
|
177
|
-
if @fetcher_paused
|
|
178
|
-
logger.debug { 'Restarting fetcher' }
|
|
57
|
+
def processor_done
|
|
58
|
+
@busy_processors.decrement
|
|
59
|
+
end
|
|
179
60
|
|
|
180
|
-
|
|
61
|
+
def assign(queue_name, sqs_msg)
|
|
62
|
+
return unless running?
|
|
181
63
|
|
|
182
|
-
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|
|
64
|
+
logger.debug { "Assigning #{sqs_msg.message_id}" }
|
|
186
65
|
|
|
187
|
-
|
|
188
|
-
queue_weight(@queues, queue)
|
|
189
|
-
end
|
|
66
|
+
@busy_processors.increment
|
|
190
67
|
|
|
191
|
-
|
|
192
|
-
|
|
68
|
+
Concurrent::Promise.execute(
|
|
69
|
+
executor: @executor
|
|
70
|
+
) { Processor.process(queue_name, sqs_msg) }.then { processor_done }.rescue { processor_done }
|
|
193
71
|
end
|
|
194
72
|
|
|
195
|
-
def
|
|
196
|
-
|
|
73
|
+
def dispatch_batch(queue)
|
|
74
|
+
return if (batch = @fetcher.fetch(queue, BATCH_LIMIT)).none?
|
|
75
|
+
@polling_strategy.messages_found(queue.name, batch.size)
|
|
76
|
+
assign(queue.name, patch_batch!(batch))
|
|
197
77
|
end
|
|
198
78
|
|
|
199
|
-
def
|
|
200
|
-
|
|
79
|
+
def dispatch_single_messages(queue)
|
|
80
|
+
messages = @fetcher.fetch(queue, ready)
|
|
201
81
|
|
|
202
|
-
|
|
203
|
-
queue
|
|
204
|
-
|
|
205
|
-
unless defined?(::ActiveJob) || !Shoryuken.worker_registry.workers(queue).empty?
|
|
206
|
-
# when no worker registered pause the queue to avoid endless recursion
|
|
207
|
-
logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because no workers registered" }
|
|
208
|
-
|
|
209
|
-
after(Shoryuken.options[:delay].to_f) { async.restart_queue!(queue) }
|
|
210
|
-
|
|
211
|
-
return next_queue
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# add queue back to the end of the list
|
|
215
|
-
@queues << queue
|
|
216
|
-
|
|
217
|
-
queue
|
|
82
|
+
@polling_strategy.messages_found(queue.name, messages.size)
|
|
83
|
+
messages.each { |message| assign(queue.name, message) }
|
|
218
84
|
end
|
|
219
85
|
|
|
220
|
-
def
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if @busy.size > 0
|
|
224
|
-
after(delay) { soft_shutdown(delay) }
|
|
225
|
-
else
|
|
226
|
-
@finished.signal
|
|
227
|
-
end
|
|
86
|
+
def batched_queue?(queue)
|
|
87
|
+
Shoryuken.worker_registry.batch_receive_messages?(queue.name)
|
|
228
88
|
end
|
|
229
89
|
|
|
230
|
-
def
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
after(delay) do
|
|
235
|
-
watchdog('Manager#hard_shutdown_in died') do
|
|
236
|
-
if @busy.size > 0
|
|
237
|
-
logger.info { "Hard shutting down #{@busy.size} busy workers" }
|
|
238
|
-
|
|
239
|
-
@busy.each do |processor|
|
|
240
|
-
if processor.alive? && t = @threads.delete(processor.object_id)
|
|
241
|
-
t.raise Shutdown
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
@finished.signal
|
|
90
|
+
def patch_batch!(sqs_msgs)
|
|
91
|
+
sqs_msgs.instance_eval do
|
|
92
|
+
def message_id
|
|
93
|
+
"batch-with-#{size}-messages"
|
|
247
94
|
end
|
|
248
95
|
end
|
|
96
|
+
|
|
97
|
+
sqs_msgs
|
|
249
98
|
end
|
|
250
99
|
end
|
|
251
100
|
end
|
data/lib/shoryuken/message.rb
CHANGED
|
@@ -3,19 +3,10 @@ module Shoryuken
|
|
|
3
3
|
attr_accessor :client, :queue_url, :queue_name, :data
|
|
4
4
|
|
|
5
5
|
def initialize(client, queue, data)
|
|
6
|
-
self.client
|
|
7
|
-
self.data
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
self.queue_url = queue.url
|
|
11
|
-
self.queue_name = queue.name
|
|
12
|
-
else
|
|
13
|
-
# TODO: Remove next major release
|
|
14
|
-
Shoryuken.logger.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
|
|
6
|
+
self.client = client
|
|
7
|
+
self.data = data
|
|
8
|
+
self.queue_url = queue.url
|
|
9
|
+
self.queue_name = queue.name
|
|
19
10
|
end
|
|
20
11
|
|
|
21
12
|
def delete
|
|
@@ -102,32 +102,15 @@ module Shoryuken
|
|
|
102
102
|
|
|
103
103
|
class Entry
|
|
104
104
|
attr_reader :klass
|
|
105
|
+
|
|
105
106
|
def initialize(klass, *args)
|
|
106
107
|
@klass = klass
|
|
107
108
|
@args = args
|
|
108
|
-
|
|
109
|
-
patch_deprecated_middleware!(klass)
|
|
110
109
|
end
|
|
111
110
|
|
|
112
111
|
def make_new
|
|
113
112
|
@klass.new(*@args)
|
|
114
113
|
end
|
|
115
|
-
|
|
116
|
-
private
|
|
117
|
-
|
|
118
|
-
def patch_deprecated_middleware!(klass)
|
|
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)" }
|
|
121
|
-
|
|
122
|
-
klass.class_eval do
|
|
123
|
-
alias_method :deprecated_call, :call
|
|
124
|
-
|
|
125
|
-
def call(worker_instance, queue, sqs_msg, body = nil, &block)
|
|
126
|
-
deprecated_call(worker_instance, queue, sqs_msg, &block)
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
114
|
end
|
|
132
115
|
end
|
|
133
116
|
end
|
|
@@ -5,18 +5,13 @@ module Shoryuken
|
|
|
5
5
|
def call(worker, queue, sqs_msg, body)
|
|
6
6
|
yield
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
return unless worker.class.auto_delete?
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
entries = [sqs_msg].flatten.map.with_index do |message, i|
|
|
12
|
-
{ id: i.to_s, receipt_handle: message.receipt_handle }
|
|
13
|
-
end
|
|
10
|
+
entries = [sqs_msg].flatten.map.with_index { |message, i| { id: i.to_s, receipt_handle: message.receipt_handle } }
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
end
|
|
12
|
+
Shoryuken::Client.queues(queue).delete_messages(entries: entries)
|
|
17
13
|
end
|
|
18
14
|
end
|
|
19
15
|
end
|
|
20
16
|
end
|
|
21
17
|
end
|
|
22
|
-
|
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
require 'celluloid/current' unless defined?(Celluloid)
|
|
2
|
-
|
|
3
1
|
module Shoryuken
|
|
4
2
|
module Middleware
|
|
5
3
|
module Server
|
|
6
4
|
class AutoExtendVisibility
|
|
5
|
+
include Util
|
|
6
|
+
|
|
7
7
|
EXTEND_UPFRONT_SECONDS = 5
|
|
8
8
|
|
|
9
9
|
def call(worker, queue, sqs_msg, body)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
timer.cancel
|
|
16
|
-
@visibility_extender.terminate
|
|
17
|
-
end
|
|
10
|
+
return yield unless worker.class.auto_visibility_timeout?
|
|
11
|
+
|
|
12
|
+
if sqs_msg.is_a?(Array)
|
|
13
|
+
logger.warn { "Auto extend visibility isn't supported for batch workers" }
|
|
14
|
+
return yield
|
|
18
15
|
end
|
|
16
|
+
|
|
17
|
+
timer = auto_visibility_timer(worker, queue, sqs_msg, body)
|
|
18
|
+
yield
|
|
19
|
+
ensure
|
|
20
|
+
timer.kill if timer
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
private
|
|
22
24
|
|
|
23
25
|
class MessageVisibilityExtender
|
|
24
|
-
include Celluloid
|
|
25
26
|
include Util
|
|
26
27
|
|
|
27
28
|
def auto_extend(worker, queue, sqs_msg, body)
|
|
28
29
|
queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
Concurrent::TimerTask.new(execution_interval: queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
|
|
31
32
|
begin
|
|
32
33
|
logger.debug do
|
|
33
34
|
"Extending message #{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
|
|
@@ -35,11 +36,11 @@ module Shoryuken
|
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
|
|
38
|
-
rescue =>
|
|
39
|
+
rescue => ex
|
|
39
40
|
logger.error do
|
|
40
41
|
'Could not auto extend the message ' \
|
|
41
42
|
"#{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
|
|
42
|
-
"visibility timeout. Error: #{
|
|
43
|
+
"visibility timeout. Error: #{ex.message}"
|
|
43
44
|
end
|
|
44
45
|
end
|
|
45
46
|
end
|
|
@@ -47,9 +48,7 @@ module Shoryuken
|
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
def auto_visibility_timer(worker, queue, sqs_msg, body)
|
|
50
|
-
|
|
51
|
-
@visibility_extender = MessageVisibilityExtender.new_link
|
|
52
|
-
@visibility_extender.auto_extend(worker, queue, sqs_msg, body)
|
|
51
|
+
MessageVisibilityExtender.new.auto_extend(worker, queue, sqs_msg, body).tap(&:execute)
|
|
53
52
|
end
|
|
54
53
|
end
|
|
55
54
|
end
|
|
@@ -5,42 +5,57 @@ module Shoryuken
|
|
|
5
5
|
include Util
|
|
6
6
|
|
|
7
7
|
def call(worker, queue, sqs_msg, body)
|
|
8
|
+
return yield unless worker.class.exponential_backoff?
|
|
9
|
+
|
|
10
|
+
if sqs_msg.is_a?(Array)
|
|
11
|
+
logger.warn { "Exponential backoff isn't supported for batch workers" }
|
|
12
|
+
return yield
|
|
13
|
+
end
|
|
14
|
+
|
|
8
15
|
started_at = Time.now
|
|
9
16
|
yield
|
|
10
|
-
rescue
|
|
11
|
-
retry_intervals =
|
|
17
|
+
rescue => ex
|
|
18
|
+
retry_intervals = worker.class.get_shoryuken_options['retry_intervals']
|
|
12
19
|
|
|
13
|
-
if retry_intervals.
|
|
20
|
+
if retry_intervals.nil? || !handle_failure(sqs_msg, started_at, retry_intervals)
|
|
14
21
|
# Re-raise the exception if the job is not going to be exponential backoff retried.
|
|
15
22
|
# This allows custom middleware (like exception notifiers) to be aware of the unhandled failure.
|
|
16
23
|
raise
|
|
17
24
|
end
|
|
25
|
+
|
|
26
|
+
logger.warn { "Message #{sqs_msg.message_id} will attempt retry due to error: #{ex.message}" }
|
|
27
|
+
# since we didn't raise, lets log the backtrace for debugging purposes.
|
|
28
|
+
logger.debug { ex.backtrace.join("\n") } unless ex.backtrace.nil?
|
|
18
29
|
end
|
|
19
30
|
|
|
20
31
|
private
|
|
21
32
|
|
|
22
|
-
def
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
attempts =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# We calculate the maximum timeout by subtracting the amount of time since the receipt of the message.
|
|
35
|
-
#
|
|
36
|
-
# From the docs: "Amazon SQS restarts the timeout period using the new value."
|
|
37
|
-
# http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html#AboutVT-extending-message-visibility-timeout
|
|
38
|
-
max_timeout = 43200 - (Time.now - started_at).ceil - 1
|
|
33
|
+
def get_interval(retry_intervals, attempts)
|
|
34
|
+
return retry_intervals.call(attempts) if retry_intervals.respond_to?(:call)
|
|
35
|
+
|
|
36
|
+
if attempts <= (retry_intervals = Array(retry_intervals)).size
|
|
37
|
+
retry_intervals[attempts - 1]
|
|
38
|
+
else
|
|
39
|
+
retry_intervals.last
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def next_visibility_timeout(interval, started_at)
|
|
44
|
+
max_timeout = 43_200 - (Time.now - started_at).ceil - 1
|
|
39
45
|
interval = max_timeout if interval > max_timeout
|
|
46
|
+
interval.to_i
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def handle_failure(sqs_msg, started_at, retry_intervals)
|
|
50
|
+
receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
|
|
51
|
+
|
|
52
|
+
return false unless (interval = get_interval(retry_intervals, receive_count))
|
|
53
|
+
|
|
54
|
+
sqs_msg.change_visibility(visibility_timeout: next_visibility_timeout(interval.to_i, started_at))
|
|
40
55
|
|
|
41
|
-
sqs_msg.
|
|
56
|
+
logger.info { "Message #{sqs_msg.message_id} failed, will be retried in #{interval} seconds" }
|
|
42
57
|
|
|
43
|
-
|
|
58
|
+
true
|
|
44
59
|
end
|
|
45
60
|
end
|
|
46
61
|
end
|