shoryuken 2.0.11 → 3.0.6
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 +127 -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 +187 -0
- data/bin/shoryuken +55 -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 +77 -180
- 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 +9 -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 +7 -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,142 @@
|
|
|
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
|
+
# See https://github.com/phstc/shoryuken/issues/348#issuecomment-292847028
|
|
7
|
+
MIN_DISPATCH_INTERVAL = 0.1
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
def initialize(fetcher, polling_strategy)
|
|
10
|
+
@count = Shoryuken.options.fetch(:concurrency, 25)
|
|
12
11
|
|
|
13
|
-
def initialize(condvar)
|
|
14
|
-
@count = Shoryuken.options[:concurrency] || 25
|
|
15
12
|
raise(ArgumentError, "Concurrency value #{@count} is invalid, it needs to be a positive number") unless @count > 0
|
|
13
|
+
|
|
16
14
|
@queues = Shoryuken.queues.dup.uniq
|
|
17
|
-
@finished = condvar
|
|
18
15
|
|
|
19
|
-
@done = false
|
|
16
|
+
@done = Concurrent::AtomicBoolean.new(false)
|
|
20
17
|
|
|
21
|
-
@
|
|
22
|
-
@
|
|
23
|
-
|
|
18
|
+
@fetcher = fetcher
|
|
19
|
+
@polling_strategy = polling_strategy
|
|
20
|
+
|
|
21
|
+
@pool = Concurrent::FixedThreadPool.new(@count, max_queue: @count)
|
|
22
|
+
@dispatcher_executor = Concurrent::SingleThreadExecutor.new
|
|
24
23
|
end
|
|
25
24
|
|
|
26
25
|
def start
|
|
27
26
|
logger.info { 'Starting' }
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
dispatch_async
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
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" }
|
|
32
|
+
@done.make_true
|
|
46
33
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
34
|
+
if (callback = Shoryuken.stop_callback)
|
|
35
|
+
logger.info { 'Calling Shoryuken.on_stop block' }
|
|
36
|
+
callback.call
|
|
59
37
|
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
38
|
|
|
69
|
-
|
|
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
|
|
39
|
+
fire_event(:shutdown, true)
|
|
77
40
|
|
|
78
|
-
|
|
79
|
-
watchdog("Manager#processor_died died") do
|
|
80
|
-
logger.error { "Process died, reason: #{reason}" unless reason.to_s.empty? }
|
|
41
|
+
logger.info { 'Shutting down workers' }
|
|
81
42
|
|
|
82
|
-
|
|
83
|
-
@busy.delete processor
|
|
43
|
+
@dispatcher_executor.kill
|
|
84
44
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
end
|
|
45
|
+
if options[:shutdown]
|
|
46
|
+
hard_shutdown_in(options[:timeout])
|
|
47
|
+
else
|
|
48
|
+
soft_shutdown
|
|
90
49
|
end
|
|
91
50
|
end
|
|
92
51
|
|
|
93
|
-
def
|
|
94
|
-
|
|
52
|
+
def processor_failed(ex)
|
|
53
|
+
logger.error ex
|
|
54
|
+
logger.error ex.backtrace.join("\n") unless ex.backtrace.nil?
|
|
95
55
|
end
|
|
96
56
|
|
|
97
|
-
def
|
|
98
|
-
|
|
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
|
|
57
|
+
def processor_done(queue)
|
|
58
|
+
logger.debug { "Process done for '#{queue}'" }
|
|
106
59
|
end
|
|
107
60
|
|
|
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}" }
|
|
112
|
-
|
|
113
|
-
@queues << queue
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def pause_queue!(queue)
|
|
119
|
-
return if !@queues.include?(queue) || Shoryuken.options[:delay].to_f <= 0
|
|
120
|
-
|
|
121
|
-
logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because it's empty" }
|
|
122
|
-
|
|
123
|
-
@queues.delete(queue)
|
|
61
|
+
private
|
|
124
62
|
|
|
125
|
-
|
|
63
|
+
def dispatch_async
|
|
64
|
+
@dispatcher_executor.post(&method(:dispatch_now))
|
|
126
65
|
end
|
|
127
66
|
|
|
67
|
+
def dispatch_now
|
|
68
|
+
return if @done.true?
|
|
128
69
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if @ready.empty?
|
|
135
|
-
logger.debug { 'Pausing fetcher, because all processors are busy' }
|
|
70
|
+
begin
|
|
71
|
+
if ready.zero? || (queue = @polling_strategy.next_queue).nil?
|
|
72
|
+
sleep MIN_DISPATCH_INTERVAL
|
|
73
|
+
return
|
|
74
|
+
end
|
|
136
75
|
|
|
137
|
-
|
|
76
|
+
logger.debug { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{@polling_strategy.active_queues}" }
|
|
138
77
|
|
|
139
|
-
|
|
140
|
-
|
|
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' }
|
|
146
|
-
|
|
147
|
-
@fetcher_paused = true
|
|
78
|
+
batched_queue?(queue) ? dispatch_batch(queue) : dispatch_single_messages(queue)
|
|
79
|
+
ensure
|
|
80
|
+
dispatch_async
|
|
148
81
|
end
|
|
149
82
|
end
|
|
150
83
|
|
|
151
|
-
def
|
|
152
|
-
@
|
|
84
|
+
def busy
|
|
85
|
+
@count - ready
|
|
153
86
|
end
|
|
154
87
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def build_processor
|
|
158
|
-
processor = Processor.new_link(current_actor)
|
|
159
|
-
processor.proxy_id = processor.object_id
|
|
160
|
-
processor
|
|
88
|
+
def ready
|
|
89
|
+
@pool.remaining_capacity
|
|
161
90
|
end
|
|
162
91
|
|
|
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
|
|
92
|
+
def assign(queue, sqs_msg)
|
|
93
|
+
logger.debug { "Assigning #{sqs_msg.message_id}" }
|
|
175
94
|
|
|
176
|
-
|
|
177
|
-
end
|
|
178
|
-
end
|
|
95
|
+
@pool.post { Processor.new(self).process(queue, sqs_msg) }
|
|
179
96
|
end
|
|
180
97
|
|
|
181
|
-
def
|
|
182
|
-
|
|
98
|
+
def dispatch_batch(queue)
|
|
99
|
+
batch = @fetcher.fetch(queue, BATCH_LIMIT)
|
|
100
|
+
@polling_strategy.messages_found(queue.name, batch.size)
|
|
101
|
+
assign(queue.name, patch_batch!(batch))
|
|
183
102
|
end
|
|
184
103
|
|
|
185
|
-
def
|
|
186
|
-
|
|
104
|
+
def dispatch_single_messages(queue)
|
|
105
|
+
messages = @fetcher.fetch(queue, ready)
|
|
106
|
+
@polling_strategy.messages_found(queue.name, messages.size)
|
|
107
|
+
messages.each { |message| assign(queue.name, message) }
|
|
187
108
|
end
|
|
188
109
|
|
|
189
|
-
def
|
|
190
|
-
|
|
110
|
+
def batched_queue?(queue)
|
|
111
|
+
Shoryuken.worker_registry.batch_receive_messages?(queue.name)
|
|
191
112
|
end
|
|
192
113
|
|
|
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) }
|
|
114
|
+
def soft_shutdown
|
|
115
|
+
@pool.shutdown
|
|
116
|
+
@pool.wait_for_termination
|
|
117
|
+
end
|
|
204
118
|
|
|
205
|
-
|
|
119
|
+
def hard_shutdown_in(delay)
|
|
120
|
+
if busy > 0
|
|
121
|
+
logger.info { "Pausing up to #{delay} seconds to allow workers to finish..." }
|
|
206
122
|
end
|
|
207
123
|
|
|
208
|
-
|
|
209
|
-
@queues << queue
|
|
210
|
-
|
|
211
|
-
queue
|
|
212
|
-
end
|
|
124
|
+
@pool.shutdown
|
|
213
125
|
|
|
214
|
-
|
|
215
|
-
logger.info { "Waiting for #{@busy.size} busy workers" }
|
|
126
|
+
return if @pool.wait_for_termination(delay)
|
|
216
127
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
else
|
|
220
|
-
@finished.signal
|
|
221
|
-
end
|
|
128
|
+
logger.info { "Hard shutting down #{busy} busy workers" }
|
|
129
|
+
@pool.kill
|
|
222
130
|
end
|
|
223
131
|
|
|
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
|
|
132
|
+
def patch_batch!(sqs_msgs)
|
|
133
|
+
sqs_msgs.instance_eval do
|
|
134
|
+
def message_id
|
|
135
|
+
"batch-with-#{size}-messages"
|
|
241
136
|
end
|
|
242
137
|
end
|
|
138
|
+
|
|
139
|
+
sqs_msgs
|
|
243
140
|
end
|
|
244
141
|
end
|
|
245
142
|
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
|