shoryuken 2.0.11 → 3.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/.codeclimate.yml +20 -0
- data/.rubocop.yml +8 -2
- data/.travis.yml +7 -5
- data/CHANGELOG.md +92 -10
- data/Gemfile +1 -0
- data/README.md +20 -57
- data/Rakefile +0 -1
- data/bin/cli/base.rb +42 -0
- data/bin/cli/sqs.rb +188 -0
- data/bin/shoryuken +47 -9
- data/examples/default_worker.rb +1 -12
- data/lib/shoryuken/client.rb +3 -25
- data/lib/shoryuken/default_worker_registry.rb +9 -5
- data/lib/shoryuken/environment_loader.rb +29 -67
- data/lib/shoryuken/fetcher.rb +22 -53
- data/lib/shoryuken/launcher.rb +5 -29
- data/lib/shoryuken/manager.rb +72 -184
- data/lib/shoryuken/message.rb +4 -13
- data/lib/shoryuken/middleware/chain.rb +1 -18
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +21 -18
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +26 -19
- data/lib/shoryuken/polling.rb +204 -0
- data/lib/shoryuken/processor.rb +6 -14
- data/lib/shoryuken/queue.rb +36 -38
- data/lib/shoryuken/runner.rb +143 -0
- data/lib/shoryuken/util.rb +3 -9
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker.rb +1 -1
- data/lib/shoryuken.rb +78 -39
- data/shoryuken.gemspec +6 -6
- data/spec/integration/launcher_spec.rb +4 -3
- data/spec/shoryuken/client_spec.rb +2 -43
- data/spec/shoryuken/default_worker_registry_spec.rb +12 -10
- data/spec/shoryuken/environment_loader_spec.rb +34 -0
- data/spec/shoryuken/fetcher_spec.rb +18 -52
- data/spec/shoryuken/manager_spec.rb +56 -97
- 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/polling_spec.rb +239 -0
- data/spec/shoryuken/processor_spec.rb +5 -5
- data/spec/shoryuken/queue_spec.rb +110 -63
- data/spec/shoryuken/{cli_spec.rb → runner_spec.rb} +10 -24
- data/spec/shoryuken_spec.rb +13 -1
- data/spec/spec_helper.rb +8 -20
- data/test_workers/endless_interruptive_worker.rb +41 -0
- data/test_workers/endless_uninterruptive_worker.rb +44 -0
- metadata +34 -35
- data/.hound.yml +0 -6
- data/lib/shoryuken/cli.rb +0 -210
- 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/{LICENSE.txt → LICENSE} +0 -0
data/lib/shoryuken/manager.rb
CHANGED
|
@@ -1,245 +1,133 @@
|
|
|
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
|
-
|
|
5
|
+
BATCH_LIMIT = 10
|
|
6
|
+
HEARTBEAT_INTERVAL = 0.1
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
def initialize(fetcher, polling_strategy)
|
|
9
|
+
@count = Shoryuken.options.fetch(:concurrency, 25)
|
|
12
10
|
|
|
13
|
-
def initialize(condvar)
|
|
14
|
-
@count = Shoryuken.options[:concurrency] || 25
|
|
15
11
|
raise(ArgumentError, "Concurrency value #{@count} is invalid, it needs to be a positive number") unless @count > 0
|
|
12
|
+
|
|
16
13
|
@queues = Shoryuken.queues.dup.uniq
|
|
17
|
-
@finished = condvar
|
|
18
14
|
|
|
19
|
-
@done = false
|
|
15
|
+
@done = Concurrent::AtomicBoolean.new(false)
|
|
16
|
+
@dispatching = Concurrent::AtomicBoolean.new(false)
|
|
17
|
+
|
|
18
|
+
@fetcher = fetcher
|
|
19
|
+
@polling_strategy = polling_strategy
|
|
20
|
+
|
|
21
|
+
@heartbeat = Concurrent::TimerTask.new(run_now: true,
|
|
22
|
+
execution_interval: HEARTBEAT_INTERVAL,
|
|
23
|
+
timeout_interval: 60) { dispatch }
|
|
20
24
|
|
|
21
|
-
@
|
|
22
|
-
@ready = @count.times.map { build_processor }
|
|
23
|
-
@threads = {}
|
|
25
|
+
@pool = Concurrent::FixedThreadPool.new(@count, max_queue: @count)
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def start
|
|
27
29
|
logger.info { 'Starting' }
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
@heartbeat.execute
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def stop(options = {})
|
|
33
|
-
|
|
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
|
|
35
|
+
@done.make_true
|
|
61
36
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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}" unless reason.to_s.empty? }
|
|
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
|
|
37
|
+
if (callback = Shoryuken.stop_callback)
|
|
38
|
+
logger.info { 'Calling Shoryuken.on_stop block' }
|
|
39
|
+
callback.call
|
|
90
40
|
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def stopped?
|
|
94
|
-
@done
|
|
95
|
-
end
|
|
96
41
|
|
|
97
|
-
|
|
98
|
-
watchdog('Manager#assign died') do
|
|
99
|
-
logger.debug { "Assigning #{sqs_msg.message_id}" }
|
|
100
|
-
|
|
101
|
-
processor = @ready.pop
|
|
102
|
-
@busy << processor
|
|
42
|
+
fire_event(:shutdown, true)
|
|
103
43
|
|
|
104
|
-
|
|
105
|
-
end
|
|
106
|
-
end
|
|
44
|
+
logger.info { 'Shutting down workers' }
|
|
107
45
|
|
|
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}" }
|
|
46
|
+
@heartbeat.kill
|
|
112
47
|
|
|
113
|
-
|
|
114
|
-
|
|
48
|
+
if options[:shutdown]
|
|
49
|
+
hard_shutdown_in(options[:timeout])
|
|
50
|
+
else
|
|
51
|
+
soft_shutdown
|
|
115
52
|
end
|
|
116
53
|
end
|
|
117
54
|
|
|
118
|
-
def
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because it's empty" }
|
|
122
|
-
|
|
123
|
-
@queues.delete(queue)
|
|
124
|
-
|
|
125
|
-
after(Shoryuken.options[:delay].to_f) { async.restart_queue!(queue) }
|
|
55
|
+
def processor_done(queue)
|
|
56
|
+
logger.debug { "Process done for '#{queue}'" }
|
|
126
57
|
end
|
|
127
58
|
|
|
59
|
+
private
|
|
128
60
|
|
|
129
61
|
def dispatch
|
|
130
|
-
return if
|
|
131
|
-
|
|
132
|
-
logger.debug { "Ready: #{@ready.size}, Busy: #{@busy.size}, Active Queues: #{unparse_queues(@queues)}" }
|
|
133
|
-
|
|
134
|
-
if @ready.empty?
|
|
135
|
-
logger.debug { 'Pausing fetcher, because all processors are busy' }
|
|
62
|
+
return if @done.true?
|
|
63
|
+
return unless @dispatching.make_true
|
|
136
64
|
|
|
137
|
-
|
|
65
|
+
return if ready.zero?
|
|
66
|
+
return unless (queue = @polling_strategy.next_queue)
|
|
138
67
|
|
|
139
|
-
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
if (queue = next_queue)
|
|
143
|
-
@fetcher.async.fetch(queue, @ready.size)
|
|
144
|
-
else
|
|
145
|
-
logger.debug { 'Pausing fetcher, because all queues are paused' }
|
|
68
|
+
logger.debug { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{@polling_strategy.active_queues}" }
|
|
146
69
|
|
|
147
|
-
|
|
148
|
-
|
|
70
|
+
batched_queue?(queue) ? dispatch_batch(queue) : dispatch_single_messages(queue)
|
|
71
|
+
ensure
|
|
72
|
+
@dispatching.make_false
|
|
149
73
|
end
|
|
150
74
|
|
|
151
|
-
def
|
|
152
|
-
@
|
|
75
|
+
def busy
|
|
76
|
+
@count - ready
|
|
153
77
|
end
|
|
154
78
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def build_processor
|
|
158
|
-
processor = Processor.new_link(current_actor)
|
|
159
|
-
processor.proxy_id = processor.object_id
|
|
160
|
-
processor
|
|
79
|
+
def ready
|
|
80
|
+
@pool.remaining_capacity
|
|
161
81
|
end
|
|
162
82
|
|
|
163
|
-
def
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
unless @queues.include? queue
|
|
167
|
-
logger.debug { "Restarting '#{queue}'" }
|
|
168
|
-
|
|
169
|
-
@queues << queue
|
|
170
|
-
|
|
171
|
-
if @fetcher_paused
|
|
172
|
-
logger.debug { 'Restarting fetcher' }
|
|
173
|
-
|
|
174
|
-
@fetcher_paused = false
|
|
83
|
+
def assign(queue, sqs_msg)
|
|
84
|
+
logger.debug { "Assigning #{sqs_msg.message_id}" }
|
|
175
85
|
|
|
176
|
-
|
|
177
|
-
end
|
|
178
|
-
end
|
|
86
|
+
@pool.post { Processor.new(self).process(queue, sqs_msg) }
|
|
179
87
|
end
|
|
180
88
|
|
|
181
|
-
def
|
|
182
|
-
|
|
89
|
+
def dispatch_batch(queue)
|
|
90
|
+
batch = @fetcher.fetch(queue, BATCH_LIMIT)
|
|
91
|
+
@polling_strategy.messages_found(queue.name, batch.size)
|
|
92
|
+
assign(queue.name, patch_batch!(batch))
|
|
183
93
|
end
|
|
184
94
|
|
|
185
|
-
def
|
|
186
|
-
|
|
95
|
+
def dispatch_single_messages(queue)
|
|
96
|
+
messages = @fetcher.fetch(queue, ready)
|
|
97
|
+
@polling_strategy.messages_found(queue.name, messages.size)
|
|
98
|
+
messages.each { |message| assign(queue.name, message) }
|
|
187
99
|
end
|
|
188
100
|
|
|
189
|
-
def
|
|
190
|
-
|
|
101
|
+
def batched_queue?(queue)
|
|
102
|
+
Shoryuken.worker_registry.batch_receive_messages?(queue.name)
|
|
191
103
|
end
|
|
192
104
|
|
|
193
|
-
def
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
queue = @queues.shift
|
|
198
|
-
|
|
199
|
-
unless defined?(::ActiveJob) || !Shoryuken.worker_registry.workers(queue).empty?
|
|
200
|
-
# when no worker registered pause the queue to avoid endless recursion
|
|
201
|
-
logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because no workers registered" }
|
|
202
|
-
|
|
203
|
-
after(Shoryuken.options[:delay].to_f) { async.restart_queue!(queue) }
|
|
105
|
+
def soft_shutdown
|
|
106
|
+
@pool.shutdown
|
|
107
|
+
@pool.wait_for_termination
|
|
108
|
+
end
|
|
204
109
|
|
|
205
|
-
|
|
110
|
+
def hard_shutdown_in(delay)
|
|
111
|
+
if busy > 0
|
|
112
|
+
logger.info { "Pausing up to #{delay} seconds to allow workers to finish..." }
|
|
206
113
|
end
|
|
207
114
|
|
|
208
|
-
|
|
209
|
-
@queues << queue
|
|
210
|
-
|
|
211
|
-
queue
|
|
212
|
-
end
|
|
115
|
+
@pool.shutdown
|
|
213
116
|
|
|
214
|
-
|
|
215
|
-
logger.info { "Waiting for #{@busy.size} busy workers" }
|
|
117
|
+
return if @pool.wait_for_termination(delay)
|
|
216
118
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
else
|
|
220
|
-
@finished.signal
|
|
221
|
-
end
|
|
119
|
+
logger.info { "Hard shutting down #{busy} busy workers" }
|
|
120
|
+
@pool.kill
|
|
222
121
|
end
|
|
223
122
|
|
|
224
|
-
def
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
after(delay) do
|
|
229
|
-
watchdog('Manager#hard_shutdown_in died') do
|
|
230
|
-
if @busy.size > 0
|
|
231
|
-
logger.info { "Hard shutting down #{@busy.size} busy workers" }
|
|
232
|
-
|
|
233
|
-
@busy.each do |processor|
|
|
234
|
-
if processor.alive? && t = @threads.delete(processor.object_id)
|
|
235
|
-
t.raise Shutdown
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
@finished.signal
|
|
123
|
+
def patch_batch!(sqs_msgs)
|
|
124
|
+
sqs_msgs.instance_eval do
|
|
125
|
+
def message_id
|
|
126
|
+
"batch-with-#{size}-messages"
|
|
241
127
|
end
|
|
242
128
|
end
|
|
129
|
+
|
|
130
|
+
sqs_msgs
|
|
243
131
|
end
|
|
244
132
|
end
|
|
245
133
|
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
|
|
@@ -1,51 +1,54 @@
|
|
|
1
|
-
require 'celluloid' 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
|
-
yield
|
|
13
|
-
ensure
|
|
14
|
-
timer.cancel if timer
|
|
10
|
+
if sqs_msg.is_a?(Array)
|
|
11
|
+
logger.warn { "Auto extend visibility isn't supported for batch workers" }
|
|
12
|
+
return yield
|
|
15
13
|
end
|
|
14
|
+
|
|
15
|
+
timer = auto_visibility_timer(worker, queue, sqs_msg, body)
|
|
16
|
+
yield
|
|
17
|
+
ensure
|
|
18
|
+
timer.kill if timer
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
private
|
|
19
22
|
|
|
20
23
|
class MessageVisibilityExtender
|
|
21
|
-
include Celluloid
|
|
22
24
|
include Util
|
|
23
25
|
|
|
24
|
-
def auto_extend(queue, sqs_msg,
|
|
26
|
+
def auto_extend(worker, queue, sqs_msg, body)
|
|
25
27
|
queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
Concurrent::TimerTask.new(execution_interval: queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
|
|
28
30
|
begin
|
|
29
31
|
logger.debug do
|
|
30
|
-
"Extending message #{worker_name(
|
|
31
|
-
|
|
32
|
+
"Extending message #{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
|
|
33
|
+
"visibility timeout by #{queue_visibility_timeout}s."
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
|
|
35
37
|
rescue => e
|
|
36
38
|
logger.error do
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
'Could not auto extend the message ' \
|
|
40
|
+
"#{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
|
|
41
|
+
"visibility timeout. Error: #{e.message}"
|
|
39
42
|
end
|
|
40
43
|
end
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
|
|
45
|
-
def auto_visibility_timer(queue, sqs_msg,
|
|
46
|
-
return unless
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
def auto_visibility_timer(worker, queue, sqs_msg, body)
|
|
49
|
+
return unless worker.class.auto_visibility_timeout?
|
|
50
|
+
|
|
51
|
+
MessageVisibilityExtender.new.auto_extend(worker, queue, sqs_msg, body).tap(&:execute)
|
|
49
52
|
end
|
|
50
53
|
end
|
|
51
54
|
end
|
|
@@ -5,12 +5,17 @@ module Shoryuken
|
|
|
5
5
|
include Util
|
|
6
6
|
|
|
7
7
|
def call(worker, queue, sqs_msg, body)
|
|
8
|
+
if sqs_msg.is_a?(Array)
|
|
9
|
+
logger.warn { "Exponential backoff isn't supported for batch workers" }
|
|
10
|
+
return yield
|
|
11
|
+
end
|
|
12
|
+
|
|
8
13
|
started_at = Time.now
|
|
9
14
|
yield
|
|
10
15
|
rescue
|
|
11
|
-
retry_intervals =
|
|
16
|
+
retry_intervals = worker.class.get_shoryuken_options['retry_intervals']
|
|
12
17
|
|
|
13
|
-
if retry_intervals.
|
|
18
|
+
if retry_intervals.nil? || !handle_failure(sqs_msg, started_at, retry_intervals)
|
|
14
19
|
# Re-raise the exception if the job is not going to be exponential backoff retried.
|
|
15
20
|
# This allows custom middleware (like exception notifiers) to be aware of the unhandled failure.
|
|
16
21
|
raise
|
|
@@ -19,30 +24,32 @@ module Shoryuken
|
|
|
19
24
|
|
|
20
25
|
private
|
|
21
26
|
|
|
22
|
-
def
|
|
23
|
-
attempts
|
|
27
|
+
def get_interval(retry_intervals, attempts)
|
|
28
|
+
return retry_intervals.call(attempts) if retry_intervals.respond_to?(:call)
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
if attempts <= (retry_intervals = Array(retry_intervals)).size
|
|
31
|
+
retry_intervals[attempts - 1]
|
|
32
|
+
else
|
|
33
|
+
retry_intervals.last
|
|
34
|
+
end
|
|
35
|
+
end
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
def next_visibility_timeout(interval, started_at)
|
|
38
|
+
max_timeout = 43_200 - (Time.now - started_at).ceil - 1
|
|
39
|
+
interval = max_timeout if interval > max_timeout
|
|
40
|
+
interval.to_i
|
|
41
|
+
end
|
|
28
42
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
else
|
|
32
|
-
retry_intervals.last
|
|
33
|
-
end
|
|
43
|
+
def handle_failure(sqs_msg, started_at, retry_intervals)
|
|
44
|
+
receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
# We calculate the maximum timeout by subtracting the amount of time since the receipt of the message.
|
|
37
|
-
#
|
|
38
|
-
# From the docs: "Amazon SQS restarts the timeout period using the new value."
|
|
39
|
-
# http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html#AboutVT-extending-message-visibility-timeout
|
|
40
|
-
max_timeout = 43200 - (Time.now - started_at).ceil - 1
|
|
41
|
-
interval = max_timeout if interval > max_timeout
|
|
46
|
+
return false unless (interval = get_interval(retry_intervals, receive_count))
|
|
42
47
|
|
|
43
|
-
sqs_msg.change_visibility(visibility_timeout: interval.to_i)
|
|
48
|
+
sqs_msg.change_visibility(visibility_timeout: next_visibility_timeout(interval.to_i, started_at))
|
|
44
49
|
|
|
45
50
|
logger.info { "Message #{sqs_msg.message_id} failed, will be retried in #{interval} seconds." }
|
|
51
|
+
|
|
52
|
+
true
|
|
46
53
|
end
|
|
47
54
|
end
|
|
48
55
|
end
|