sneakers_handlers 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: