sneakers_handlers 0.0.1 → 0.0.2
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/README.md +29 -2
- data/Rakefile +1 -1
- data/bin/rake +16 -0
- data/lib/sneakers_handlers.rb +5 -4
- data/lib/sneakers_handlers/{dead_letter.rb → dead_letter_handler.rb} +1 -1
- data/lib/sneakers_handlers/exponential_backoff_handler.rb +146 -0
- data/lib/sneakers_handlers/version.rb +1 -1
- data/sneakers_handlers.gemspec +2 -1
- metadata +20 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8282f82b86f3812de85b874ad7f000ff38cc382
|
4
|
+
data.tar.gz: eac3f28db8e8f77678d48e621d446bc986615216
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8618303e16ee9775d7c8e60e71928e55cfad85a7840f53bd6d912aaa8ca5f148bf011a2148ed22412ee3458a21b8cf8b89e6f96b8590f2e613d977f8e90ef88c
|
7
|
+
data.tar.gz: 08408381176a5f5e420aa2b3115ba6ab6559e89361c84c27423e2c322699c9a29dfc535affc9a5f67ccfffb6f9047b60b2d2c063961f91f68471641cc2665af6
|
data/README.md
CHANGED
@@ -22,7 +22,7 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
The gem introduces two handlers you can use as part of your sneaker workers: `SneakersHandlers::
|
25
|
+
The gem introduces two handlers you can use as part of your sneaker workers: `SneakersHandlers::DeadLetterHandler` and `SneakersHandlers::RetryHandler`
|
26
26
|
|
27
27
|
## Using the `SneakersHandlers::RetryHandler` handler
|
28
28
|
|
@@ -49,7 +49,34 @@ class MyWorker
|
|
49
49
|
def work(payload)
|
50
50
|
...
|
51
51
|
end
|
52
|
-
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
## Using the `SneakersHandlers::ExponentialBackoffHandler` handler
|
56
|
+
|
57
|
+
Exponential Backoff isn't really the right phrase for this. It's more
|
58
|
+
static configurable backoff. Plan on updating the name in the future.
|
59
|
+
|
60
|
+
When defining your worker, you have the following extra options:
|
61
|
+
|
62
|
+
`delay` [required] : An array containing the number of seconds to pause between retries
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class MyWorker
|
66
|
+
include Sneakers::Worker
|
67
|
+
from_queue "my-app.resource_processor",
|
68
|
+
durable: true,
|
69
|
+
ack: true,
|
70
|
+
exchange: "domain_events",
|
71
|
+
exchange_type: :topic,
|
72
|
+
routing_key: "resources.lifecycle.*",
|
73
|
+
handler: SneakersHandlers::ExponentialBackoffHandler,
|
74
|
+
delay: [1.second, 10.seconds, 1.minute, 10.minutes]
|
75
|
+
|
76
|
+
def work(payload)
|
77
|
+
...
|
78
|
+
end
|
79
|
+
end
|
53
80
|
```
|
54
81
|
|
55
82
|
## Development
|
data/Rakefile
CHANGED
data/bin/rake
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rake' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require "pathname"
|
10
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require "rubygems"
|
14
|
+
require "bundler/setup"
|
15
|
+
|
16
|
+
load Gem.bin_path("rake", "rake")
|
data/lib/sneakers_handlers.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require "sneakers_handlers/version"
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
2
|
+
require "sneakers"
|
3
|
+
require "sneakers_handlers/dead_letter_handler"
|
4
|
+
require "sneakers_handlers/retry_handler"
|
5
|
+
require "sneakers_handlers/exponential_backoff_handler"
|
6
|
+
|
5
7
|
module SneakersHandlers
|
6
|
-
# Your code goes here...
|
7
8
|
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# Using this handler, failed messages will be retried with an exponential
|
2
|
+
# backoff delay, for a certain number of times, until they are dead-lettered.
|
3
|
+
#
|
4
|
+
# To use it you need to defined this handler in your worker:
|
5
|
+
#
|
6
|
+
# from_queue "my-app.queue_name",
|
7
|
+
# exchange: "my_exchange_name",
|
8
|
+
# routing_key: "my_routing_key",
|
9
|
+
# handler: SneakersHandlers::ExponentialBackoffHandler,
|
10
|
+
# arguments: { "x-dead-letter-exchange" => "my_exchange_name.dlx",
|
11
|
+
# "x-dead-letter-routing-key" => "my-app.queue_name" }}
|
12
|
+
#
|
13
|
+
# By default it will retry 25 times before dead-lettering a message, but you can
|
14
|
+
# also customize that with the `max_retries` option:
|
15
|
+
#
|
16
|
+
# from_queue "my-app.queue_name",
|
17
|
+
# exchange: "my_exchange_name",
|
18
|
+
# routing_key: "my_routing_key",
|
19
|
+
# max_retries: 10,
|
20
|
+
# handler: SneakersHandlers::ExponentialBackoffHandler,
|
21
|
+
# arguments: { "x-dead-letter-exchange" => "my_exchange_name.dlx",
|
22
|
+
# "x-dead-letter-routing-key" => "my-app.queue_name" }}
|
23
|
+
|
24
|
+
module SneakersHandlers
|
25
|
+
class ExponentialBackoffHandler
|
26
|
+
attr_reader :queue, :channel, :options, :max_retries
|
27
|
+
|
28
|
+
DEFAULT_MAX_RETRY_ATTEMPTS = 25
|
29
|
+
|
30
|
+
def initialize(channel, queue, options)
|
31
|
+
@queue = queue
|
32
|
+
@channel = channel
|
33
|
+
@options = options
|
34
|
+
@max_retries = options[:max_retries] || DEFAULT_MAX_RETRY_ATTEMPTS
|
35
|
+
|
36
|
+
create_error_exchange!
|
37
|
+
|
38
|
+
queue.bind(primary_exchange, routing_key: queue.name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def acknowledge(delivery_info, _, _)
|
42
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
43
|
+
end
|
44
|
+
|
45
|
+
def reject(delivery_info, properties, message, _requeue = true)
|
46
|
+
retry_message(delivery_info, properties, message, :reject)
|
47
|
+
end
|
48
|
+
|
49
|
+
def error(delivery_info, properties, message, err)
|
50
|
+
retry_message(delivery_info, properties, message, err)
|
51
|
+
end
|
52
|
+
|
53
|
+
def timeout(delivery_info, properties, message)
|
54
|
+
retry_message(delivery_info, properties, message, :timeout)
|
55
|
+
end
|
56
|
+
|
57
|
+
def noop(_delivery_info, _properties, _message)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def retry_message(delivery_info, properties, message, reason)
|
63
|
+
attempt_number = death_count(properties[:headers])
|
64
|
+
|
65
|
+
if attempt_number < max_retries
|
66
|
+
delay = seconds_to_delay(attempt_number)
|
67
|
+
|
68
|
+
log("msg=retrying, delay=#{delay}, count=#{attempt_number}, properties=#{properties}, reason=#{reason}")
|
69
|
+
|
70
|
+
routing_key = "#{queue.name}.#{delay}"
|
71
|
+
|
72
|
+
retry_queue = create_retry_queue!(delay)
|
73
|
+
retry_queue.bind(primary_exchange, routing_key: routing_key)
|
74
|
+
|
75
|
+
primary_exchange.publish(message, routing_key: routing_key, headers: properties[:headers])
|
76
|
+
acknowledge(delivery_info, properties, message)
|
77
|
+
else
|
78
|
+
log("msg=erroring, count=#{attempt_number}, properties=#{properties}")
|
79
|
+
channel.reject(delivery_info.delivery_tag)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def death_count(headers)
|
84
|
+
return 0 if headers.nil? || headers["x-death"].nil?
|
85
|
+
|
86
|
+
headers["x-death"].inject(0) do |sum, x_death|
|
87
|
+
sum + x_death["count"] if x_death["queue"] =~ /^#{queue.name}/
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def log(message)
|
92
|
+
Sneakers.logger.info do
|
93
|
+
"[#{self.class}] #{message}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_exchange(name)
|
98
|
+
log("creating exchange=#{name}")
|
99
|
+
|
100
|
+
channel.exchange(name, type: "topic", durable: options[:exchange_options][:durable])
|
101
|
+
end
|
102
|
+
|
103
|
+
def primary_exchange
|
104
|
+
@primary_exchange ||= create_exchange("#{options[:exchange]}")
|
105
|
+
end
|
106
|
+
|
107
|
+
def create_error_exchange!
|
108
|
+
arguments = options[:queue_options][:arguments]
|
109
|
+
|
110
|
+
dlx_exchange_name = arguments.fetch("x-dead-letter-exchange")
|
111
|
+
dlx_routing_key = arguments.fetch("x-dead-letter-routing-key")
|
112
|
+
|
113
|
+
@error_exchange ||= create_exchange(dlx_exchange_name).tap do |exchange|
|
114
|
+
queue = channel.queue("#{@queue.name}.error", durable: options[:queue_options][:durable])
|
115
|
+
queue.bind(exchange, routing_key: dlx_routing_key)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_retry_queue!(delay)
|
120
|
+
clear_queues_cache
|
121
|
+
channel.queue( "#{queue.name}.retry.#{delay}",
|
122
|
+
durable: options[:queue_options][:durable],
|
123
|
+
arguments: {
|
124
|
+
:"x-dead-letter-exchange" => options[:exchange],
|
125
|
+
:"x-dead-letter-routing-key" => queue.name,
|
126
|
+
:"x-message-ttl" => delay * 1_000,
|
127
|
+
:"x-expires" => delay * 1_000 * 2
|
128
|
+
}
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
def seconds_to_delay(count)
|
133
|
+
(count + 1) ** 2
|
134
|
+
end
|
135
|
+
|
136
|
+
# When we create a new queue, `Bunny` stores its name in an internal cache.
|
137
|
+
# The problem is that as we are creating ephemeral queues that can expire shortly
|
138
|
+
# after they are created, this cached queue may not exist anymore when we try to
|
139
|
+
# publish a second message to it.
|
140
|
+
# Removing queues from the cache guarantees that `Bunny` will always try
|
141
|
+
# to check if they exist, and when they don't, it will create them for us.
|
142
|
+
def clear_queues_cache
|
143
|
+
channel.queues.clear
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/sneakers_handlers.gemspec
CHANGED
@@ -24,5 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "bundler"
|
25
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
26
26
|
spec.add_development_dependency "minitest", "~> 5.0"
|
27
|
-
spec.add_development_dependency "
|
27
|
+
spec.add_development_dependency "pry-byebug"
|
28
|
+
spec.add_development_dependency "minitest-reporters"
|
28
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sneakers_handlers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Bohn, Abe Petrillo, Brian Storti
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sneakers
|
@@ -67,7 +67,21 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '5.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest-reporters
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - ">="
|
@@ -93,10 +107,12 @@ files:
|
|
93
107
|
- README.md
|
94
108
|
- Rakefile
|
95
109
|
- bin/console
|
110
|
+
- bin/rake
|
96
111
|
- bin/setup
|
97
112
|
- circle.yml
|
98
113
|
- lib/sneakers_handlers.rb
|
99
|
-
- lib/sneakers_handlers/
|
114
|
+
- lib/sneakers_handlers/dead_letter_handler.rb
|
115
|
+
- lib/sneakers_handlers/exponential_backoff_handler.rb
|
100
116
|
- lib/sneakers_handlers/retry_handler.rb
|
101
117
|
- lib/sneakers_handlers/version.rb
|
102
118
|
- sneakers_handlers.gemspec
|
@@ -125,4 +141,3 @@ signing_key:
|
|
125
141
|
specification_version: 4
|
126
142
|
summary: Adds Handlers to use with Sneakers
|
127
143
|
test_files: []
|
128
|
-
has_rdoc:
|