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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a3e5a73cc16eed57ed53952d2989b5d9dc18e47
4
- data.tar.gz: ba629ecd84e6b69f8477746bbb57dd360c2cc06b
3
+ metadata.gz: f8282f82b86f3812de85b874ad7f000ff38cc382
4
+ data.tar.gz: eac3f28db8e8f77678d48e621d446bc986615216
5
5
  SHA512:
6
- metadata.gz: c81a40929c55500b25f811f62f1620c8b5df1202d6cba891896ec3225f8d6f5bcdfe557b55b0375cf71c9c5fda2a901fde613690f4a61b5ab1567d43530ebe5f
7
- data.tar.gz: d483161d7bc28c32ab53e34ca349b5f80d955ae869907877ebe15be10d7539317ce7865b0cc0c18ceaf8f7e81fe39d15d50fd1132031429b618f37921a1638a7
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::DeadLetter` and `SneakersHandlers::RetryHandler`
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
@@ -7,4 +7,4 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList['test/**/*_test.rb']
8
8
  end
9
9
 
10
- task :default => :spec
10
+ task :default => :test
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")
@@ -1,7 +1,8 @@
1
1
  require "sneakers_handlers/version"
2
- require 'sneakers'
3
- require 'sneakers_handlers/dead_letter'
4
- require 'sneakers_handlers/retry_handler'
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
@@ -1,5 +1,5 @@
1
1
  module SneakersHandlers
2
- class DeadLetter < ::Sneakers::Handlers::Oneshot
2
+ class DeadLetterHandler < ::Sneakers::Handlers::Oneshot
3
3
  def initialize(channel, queue, options)
4
4
  super
5
5
 
@@ -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
@@ -1,3 +1,3 @@
1
1
  module SneakersHandlers
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -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 "dotenv"
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.1
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-04-12 00:00:00.000000000 Z
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: dotenv
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/dead_letter.rb
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: