sneakers-retry 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/sneakers-retry.rb +1 -0
- data/lib/sneakers-retry/handlers/maxretry2.rb +206 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a88e547379620874a8f3dff5b12858419003a17d
|
4
|
+
data.tar.gz: 6cf05f66a5c12b1c7d232192c0f6fd8e16d85236
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 12c77a3b7fc742398803b349064d2db53017b2b98a3389c971dab1a388330ad50a8410f6ccc83c193442d5641637d6f41463d86a72877aff4352eb15e051bd47
|
7
|
+
data.tar.gz: 1e05ab1a2eb137560f3ed43ea81be33132ec2be704dd4c9ead94abb6d6c3dc09297e63942f17d944271f76e1ccf79bc49658c5221c70e8c16b453aa3f9c0c4f3
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'sneakers-retry/handlers/maxretry2.rb'
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module SneakersRetry
|
4
|
+
module Handlers
|
5
|
+
#
|
6
|
+
# Maxretry uses dead letter policies on Rabbitmq to requeue and retry
|
7
|
+
# messages after failure (rejections, errors and timeouts). When the maximum
|
8
|
+
# number of retries is reached it will put the message on an error queue.
|
9
|
+
# This handler will only retry at the queue level. To accomplish that, the
|
10
|
+
# setup is a bit complex.
|
11
|
+
#
|
12
|
+
# Input:
|
13
|
+
# worker_exchange (eXchange)
|
14
|
+
# worker_queue (Queue)
|
15
|
+
# We create:
|
16
|
+
# worker_queue-retry - (X) where we setup the worker queue to dead-letter.
|
17
|
+
# worker_queue-retry - (Q) queue bound to ^ exchange, dead-letters to
|
18
|
+
# worker_queue-retry-requeue.
|
19
|
+
# worker_queue-error - (X) where to send max-retry failures
|
20
|
+
# worker_queue-error - (Q) bound to worker_queue-error.
|
21
|
+
# worker_queue-retry-requeue - (X) exchange to bind worker_queue to for
|
22
|
+
# requeuing directly to the worker_queue.
|
23
|
+
#
|
24
|
+
# This requires that you setup arguments to the worker queue to line up the
|
25
|
+
# dead letter queue. See the example for more information.
|
26
|
+
#
|
27
|
+
# Many of these can be override with options:
|
28
|
+
# - retry_exchange - sets retry exchange & queue
|
29
|
+
# - retry_error_exchange - sets error exchange and queue
|
30
|
+
# - retry_requeue_exchange - sets the exchange created to re-queue things
|
31
|
+
# back to the worker queue.
|
32
|
+
#
|
33
|
+
class Maxretry2
|
34
|
+
|
35
|
+
def initialize(channel, queue, opts)
|
36
|
+
puts "################################"
|
37
|
+
@worker_queue_name = queue.name
|
38
|
+
Sneakers.logger.debug do
|
39
|
+
"#{log_prefix} creating handler, opts=#{opts}"
|
40
|
+
end
|
41
|
+
|
42
|
+
@channel = channel
|
43
|
+
@opts = opts
|
44
|
+
|
45
|
+
# Construct names, defaulting where suitable
|
46
|
+
retry_name = @opts[:retry_exchange] || "#{@worker_queue_name}-retry"
|
47
|
+
error_name = @opts[:retry_error_exchange] || "#{@worker_queue_name}-error"
|
48
|
+
requeue_name = @opts[:retry_requeue_exchange] || "#{@worker_queue_name}-retry-requeue"
|
49
|
+
retry_routing_key = @opts[:retry_routing_key] || "#"
|
50
|
+
|
51
|
+
# Create the exchanges
|
52
|
+
@retry_exchange, @error_exchange, @requeue_exchange = [retry_name, error_name, requeue_name].map do |name|
|
53
|
+
Sneakers.logger.debug { "#{log_prefix} creating exchange=#{name}" }
|
54
|
+
@channel.exchange(name,
|
55
|
+
:type => 'topic',
|
56
|
+
:durable => exchange_durable?)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create the queues and bindings
|
60
|
+
Sneakers.logger.debug do
|
61
|
+
"#{log_prefix} creating queue=#{retry_name} x-dead-letter-exchange=#{requeue_name}"
|
62
|
+
end
|
63
|
+
@retry_queue = @channel.queue(retry_name,
|
64
|
+
:durable => queue_durable?,
|
65
|
+
:arguments => {
|
66
|
+
:'x-dead-letter-exchange' => requeue_name,
|
67
|
+
:'x-message-ttl' => @opts[:retry_timeout] || 60000
|
68
|
+
})
|
69
|
+
@retry_queue.bind(@retry_exchange, :routing_key => '#')
|
70
|
+
|
71
|
+
Sneakers.logger.debug do
|
72
|
+
"#{log_prefix} creating queue=#{error_name}"
|
73
|
+
end
|
74
|
+
@error_queue = @channel.queue(error_name,
|
75
|
+
:durable => queue_durable?)
|
76
|
+
@error_queue.bind(@error_exchange, :routing_key => '#')
|
77
|
+
|
78
|
+
# Finally, bind the worker queue to our requeue exchange
|
79
|
+
queue.bind(@requeue_exchange, :routing_key => retry_routing_key)
|
80
|
+
|
81
|
+
@max_retries = @opts[:retry_max_times] || 5
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
def acknowledge(hdr, props, msg)
|
86
|
+
@channel.acknowledge(hdr.delivery_tag, false)
|
87
|
+
end
|
88
|
+
|
89
|
+
def reject(hdr, props, msg, requeue = false)
|
90
|
+
if requeue
|
91
|
+
# This was explicitly rejected specifying it be requeued so we do not
|
92
|
+
# want it to pass through our retry logic.
|
93
|
+
@channel.reject(hdr.delivery_tag, requeue)
|
94
|
+
else
|
95
|
+
handle_retry(hdr, props, msg, :reject)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def error(hdr, props, msg, err)
|
101
|
+
handle_retry(hdr, props, msg, err)
|
102
|
+
end
|
103
|
+
|
104
|
+
def timeout(hdr, props, msg)
|
105
|
+
handle_retry(hdr, props, msg, :timeout)
|
106
|
+
end
|
107
|
+
|
108
|
+
def noop(hdr, props, msg)
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
# Helper logic for retry handling. This will reject the message if there
|
113
|
+
# are remaining retries left on it, otherwise it will publish it to the
|
114
|
+
# error exchange along with the reason.
|
115
|
+
# @param hdr [Bunny::DeliveryInfo]
|
116
|
+
# @param props [Bunny::MessageProperties]
|
117
|
+
# @param msg [String] The message
|
118
|
+
# @param reason [String, Symbol, Exception] Reason for the retry, included
|
119
|
+
# in the JSON we put on the error exchange.
|
120
|
+
def handle_retry(hdr, props, msg, reason)
|
121
|
+
# +1 for the current attempt
|
122
|
+
num_attempts = failure_count(props[:headers]) + 1
|
123
|
+
if num_attempts <= @max_retries
|
124
|
+
# We call reject which will route the message to the
|
125
|
+
# x-dead-letter-exchange (ie. retry exchange) on the queue
|
126
|
+
Sneakers.logger.info do
|
127
|
+
"#{log_prefix} msg=retrying, count=#{num_attempts}, headers=#{props[:headers]}"
|
128
|
+
end
|
129
|
+
@channel.reject(hdr.delivery_tag, false)
|
130
|
+
# TODO: metrics
|
131
|
+
else
|
132
|
+
# Retried more than the max times
|
133
|
+
# Publish the original message with the routing_key to the error exchange
|
134
|
+
Sneakers.logger.info do
|
135
|
+
"#{log_prefix} msg=failing, retry_count=#{num_attempts}, reason=#{reason}"
|
136
|
+
end
|
137
|
+
data = {
|
138
|
+
error: reason.to_s,
|
139
|
+
num_attempts: num_attempts,
|
140
|
+
failed_at: Time.now.iso8601,
|
141
|
+
properties: props.to_hash
|
142
|
+
}.tap do |hash|
|
143
|
+
if reason.is_a?(Exception)
|
144
|
+
hash[:error_class] = reason.class.to_s
|
145
|
+
hash[:error_message] = "#{reason}"
|
146
|
+
if reason.backtrace
|
147
|
+
hash[:backtrace] = reason.backtrace.take(10)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end.to_json
|
151
|
+
@error_exchange.publish(msg, {
|
152
|
+
routing_key: hdr.routing_key,
|
153
|
+
headers: {
|
154
|
+
retry_info: data
|
155
|
+
}
|
156
|
+
})
|
157
|
+
@channel.acknowledge(hdr.delivery_tag, false)
|
158
|
+
# TODO: metrics
|
159
|
+
end
|
160
|
+
end
|
161
|
+
private :handle_retry
|
162
|
+
|
163
|
+
# Uses the x-death header to determine the number of failures this job has
|
164
|
+
# seen in the past. This does not count the current failure. So for
|
165
|
+
# instance, the first time the job fails, this will return 0, the second
|
166
|
+
# time, 1, etc.
|
167
|
+
# @param headers [Hash] Hash of headers that Rabbit delivers as part of
|
168
|
+
# the message
|
169
|
+
# @return [Integer] Count of number of failures.
|
170
|
+
def failure_count(headers)
|
171
|
+
if headers.nil? || headers['x-death'].nil?
|
172
|
+
0
|
173
|
+
else
|
174
|
+
x_death_array = headers['x-death'].select do |x_death|
|
175
|
+
x_death['queue'] == @worker_queue_name
|
176
|
+
end
|
177
|
+
if x_death_array.count > 0 && x_death_array.first['count']
|
178
|
+
# Newer versions of RabbitMQ return headers with a count key
|
179
|
+
x_death_array.inject(0) {|sum, x_death| sum + x_death['count']}
|
180
|
+
else
|
181
|
+
# Older versions return a separate x-death header for each failure
|
182
|
+
x_death_array.count
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
private :failure_count
|
187
|
+
|
188
|
+
# Prefix all of our log messages so they are easier to find. We don't have
|
189
|
+
# the worker, so the next best thing is the queue name.
|
190
|
+
def log_prefix
|
191
|
+
"Maxretry handler [queue=#{@worker_queue_name}]"
|
192
|
+
end
|
193
|
+
private :log_prefix
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def queue_durable?
|
198
|
+
@opts.fetch(:queue_options, {}).fetch(:durable, false)
|
199
|
+
end
|
200
|
+
|
201
|
+
def exchange_durable?
|
202
|
+
queue_durable?
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sneakers-retry
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eetay Natan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-01 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A simple hello world gem
|
14
|
+
email: eetay2@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/sneakers-retry.rb
|
20
|
+
- lib/sneakers-retry/handlers/maxretry2.rb
|
21
|
+
homepage: https://github.com/eetay/sneakers-retry
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata: {}
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 2.6.11
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: Retry handler for sneakers
|
45
|
+
test_files: []
|