hutch-retry 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f1b47c78fb252a30d64de64be86b59c036554ddceb2a45b9b89d5e0fc76d8a58
4
+ data.tar.gz: ab438ad77125d26465b2d6d04026c03fd6ccc045a5156337923b8bc7e18089ad
5
+ SHA512:
6
+ metadata.gz: 917bd4a35967e52376e254775ab4365e4a056e53eb7c8562f8ff8611cd5cf6326d18280ff472d7946657fee932c89c99bb531d1d0456ed69107b38e641aceea4
7
+ data.tar.gz: fd568586e828a2d3931e26397cf40e82cabd54977fed88a88c7c8947118c8da7de558a49ff5af5e1c955459514bd03e18815316e13ef3fb8562868dd9a4c03ac
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-11-03
4
+
5
+ - Initial release
6
+
7
+ ---
8
+
9
+ ## [0.2.0] - 2023-02-07
10
+
11
+ ### Changed
12
+
13
+ - Set Hutch version to 1.2.0
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in hutch-retry.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ group :test do
11
+ gem "rspec", "~> 3.0"
12
+ gem "timecop"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ hutch-retry (0.2.0)
5
+ hutch (= 1.2.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (7.0.4.2)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (>= 1.6, < 2)
13
+ minitest (>= 5.1)
14
+ tzinfo (~> 2.0)
15
+ amq-protocol (2.3.2)
16
+ bunny (2.20.3)
17
+ amq-protocol (~> 2.3, >= 2.3.1)
18
+ sorted_set (~> 1, >= 1.0.2)
19
+ carrot-top (0.0.7)
20
+ json
21
+ concurrent-ruby (1.2.0)
22
+ diff-lcs (1.5.0)
23
+ hutch (1.2.0)
24
+ activesupport (>= 4.2, < 8)
25
+ bunny (>= 2.19, < 3.0)
26
+ carrot-top (~> 0.0.7)
27
+ multi_json (~> 1.15)
28
+ i18n (1.12.0)
29
+ concurrent-ruby (~> 1.0)
30
+ json (2.6.3)
31
+ minitest (5.17.0)
32
+ multi_json (1.15.0)
33
+ rake (13.0.6)
34
+ rbtree (0.4.6)
35
+ rspec (3.12.0)
36
+ rspec-core (~> 3.12.0)
37
+ rspec-expectations (~> 3.12.0)
38
+ rspec-mocks (~> 3.12.0)
39
+ rspec-core (3.12.1)
40
+ rspec-support (~> 3.12.0)
41
+ rspec-expectations (3.12.2)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.12.0)
44
+ rspec-mocks (3.12.3)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.12.0)
47
+ rspec-support (3.12.0)
48
+ set (1.0.3)
49
+ sorted_set (1.0.3)
50
+ rbtree
51
+ set (~> 1.0)
52
+ timecop (0.9.6)
53
+ tzinfo (2.0.6)
54
+ concurrent-ruby (~> 1.0)
55
+
56
+ PLATFORMS
57
+ arm64-darwin-21
58
+ x86_64-linux
59
+
60
+ DEPENDENCIES
61
+ hutch-retry!
62
+ rake (~> 13.0)
63
+ rspec (~> 3.0)
64
+ timecop
65
+
66
+ BUNDLED WITH
67
+ 2.3.14
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Kahoot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # Hutch retry
2
+
3
+ Hutch-retry is an extension for [hutch](https://github.com/ruby-amqp/hutch) framework. It allows consumers to reprocess failed messages using an exponential backoff algorithm.
4
+
5
+ ## Requirements
6
+
7
+ - `hutch 1.2.0` - this library uses monkey patch to add retry mechanism
8
+
9
+ ## Installation
10
+
11
+ Install the gem and add it to the application's Gemfile by executing:
12
+
13
+ $ bundle add hutch-retry
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ $ gem install hutch-retry
18
+
19
+ ## Usage
20
+
21
+ To use hutch-retry replace `Hutch::Consumer` with `Hutch::Retry::Consumer` in your class.
22
+
23
+ ```ruby
24
+ class ExampleConsumer
25
+ include Hutch::Retry::Consumer
26
+
27
+ consume "hutch.example"
28
+ max_retries 3
29
+ retry_on [TimeoutError]
30
+ retry_exchange_options name: "example.retry",
31
+ durable: false
32
+ end
33
+ ```
34
+
35
+ | Option name | Default value | Type |
36
+ |----------------------------------|--------------------|---------|
37
+ | max_retries | 5 | Integer |
38
+ | retry_on | [StandardError] | Array |
39
+ | retry_exchange_options[:name] | <queue_name>.retry | String |
40
+ | retry_exchange_options[:durable] | true | Boolean |
41
+
42
+ ## Development
43
+
44
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
45
+
46
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
47
+
48
+ ## Contributing
49
+
50
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Motimate/hutch-retry.
51
+
52
+ ## License
53
+
54
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('../lib/hutch/retry/version', __FILE__)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "hutch-retry"
7
+ spec.version = Hutch::Retry::VERSION
8
+ spec.authors = ["Michał Marcinkowski"]
9
+ spec.email = ["michal@marcinkowski.io"]
10
+
11
+ spec.summary = "Reprocess failed messages using an exponential backoff algorithm"
12
+ spec.description = "Reprocess failed messages using an exponential backoff algorithm"
13
+ spec.homepage = "https://www.github.com/Motimate/hutch-retry"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+ spec.files = Dir.chdir(__dir__) do
17
+ `git ls-files -z`.split("\x0").reject do |f|
18
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
19
+ end
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_runtime_dependency "hutch", "1.2.0"
26
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hutch
4
+ module Retry
5
+ module Consumer
6
+ module ClassMethods
7
+ include Hutch::Logging
8
+
9
+ def max_retries(counter)
10
+ @max_retries = counter
11
+ end
12
+
13
+ def retry_on(array)
14
+ @retry_on = array
15
+ end
16
+
17
+ def get_max_retries
18
+ @max_retries || 5
19
+ end
20
+
21
+ def get_retry_on
22
+ @retry_on || [StandardError]
23
+ end
24
+
25
+ def exp_backoff(retry_count)
26
+ ((retry_count + 1)**4) + 30 + (retry_count + 2)
27
+ end
28
+
29
+ def retry_exchange_options(options = {})
30
+ @retry_exchange_options = options
31
+ end
32
+
33
+ def retry_exchange_name
34
+ (@retry_exchange_options || {}).fetch(:name, "#{get_queue_name}.retry")
35
+ end
36
+
37
+ def retry_exchange_durable?
38
+ (@retry_exchange_options || {}).fetch(:durable, true)
39
+ end
40
+ alias retry_query_durable? retry_exchange_durable?
41
+
42
+ def channel
43
+ @broker.channel
44
+ end
45
+
46
+ def retry_exchange
47
+ @retry_exchange ||= Hutch::Adapter.new_exchange(
48
+ channel,
49
+ "headers",
50
+ retry_exchange_name,
51
+ durable: retry_exchange_durable?
52
+ )
53
+ end
54
+
55
+ def create_retry_queues!(broker)
56
+ logger.info "setting up retry queues for #{retry_exchange_name} exchange"
57
+
58
+ @broker = broker
59
+
60
+ (0...get_max_retries)
61
+ .map(&method(:exp_backoff))
62
+ .each(&method(:create_retry_queue!))
63
+ end
64
+
65
+ def create_retry_queue!(delay)
66
+ channel.queue(
67
+ "#{retry_exchange_name}.#{delay}",
68
+ durable: retry_query_durable?,
69
+ arguments: {
70
+ "x-dead-letter-exchange": @broker.exchange.name,
71
+ "x-message-ttl": delay * 1_000
72
+ }
73
+ ).bind(retry_exchange, arguments: { "backoff-delay": delay, "x-match": "all" })
74
+ end
75
+ end
76
+
77
+ def self.included(base)
78
+ base.include(Hutch::Consumer)
79
+ base.extend(ClassMethods)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hutch
4
+ module Retry
5
+ VERSION = "0.2.0"
6
+ end
7
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hutch
4
+ module Retry
5
+ module WorkerExtension
6
+ def setup_queue(consumer)
7
+ logger.info "setting up queue: #{consumer.get_queue_name}"
8
+
9
+ queue = @broker.queue(consumer.get_queue_name, consumer.get_options)
10
+ @broker.bind_queue(queue, consumer.routing_keys)
11
+
12
+ consumer.create_retry_queues!(@broker) if consumer.include?(Hutch::Retry::Consumer)
13
+
14
+ queue.subscribe(consumer_tag: unique_consumer_tag, manual_ack: true) do |*args|
15
+ delivery_info, properties, payload = Hutch::Adapter.decode_message(*args)
16
+ handle_message(consumer, delivery_info, properties, payload)
17
+ end
18
+ end
19
+
20
+ def handle_message(consumer, delivery_info, properties, payload)
21
+ serializer = consumer.get_serializer || Hutch::Config[:serializer]
22
+ logger.debug {
23
+ spec = serializer.binary? ? "#{payload.bytesize} bytes" : "#{payload}"
24
+ "message(#{properties.message_id || '-'}): " +
25
+ "routing key: #{delivery_info.routing_key}, " +
26
+ "consumer: #{consumer}, " +
27
+ "payload: #{spec}"
28
+ }
29
+
30
+ message = Message.new(delivery_info, properties, payload, serializer)
31
+ consumer_instance = consumer.new.tap { |c| c.broker, c.delivery_info = @broker, delivery_info }
32
+ with_tracing(consumer_instance).handle(message)
33
+ @broker.ack(delivery_info.delivery_tag) unless consumer_instance.message_rejected?
34
+ rescue => ex
35
+ if consumer.include?(Hutch::Retry::Consumer)
36
+ handle_retry(consumer, delivery_info, properties, payload, ex)
37
+ else
38
+ acknowledge_error(delivery_info, properties, @broker, ex)
39
+ end
40
+ handle_error(properties, payload, consumer, ex)
41
+ end
42
+
43
+ def handle_retry(consumer, delivery_info, properties, payload, ex)
44
+ case ex
45
+ when *consumer.get_retry_on
46
+ current_retry_count = (properties[:headers] || {}).fetch("backoff-delay-count", 0)
47
+
48
+ if current_retry_count < consumer.get_max_retries
49
+ logger.debug "Retry message_id=#{properties[:message_id]} counter=#{current_retry_count + 1}"
50
+
51
+ @broker.ack(delivery_info.delivery_tag)
52
+ consumer.retry_exchange.publish(
53
+ payload,
54
+ routing_key: delivery_info.routing_key,
55
+ message_id: properties[:message_id],
56
+ timestamp: Time.now.to_i,
57
+ headers: {
58
+ "backoff-delay": consumer.exp_backoff(current_retry_count),
59
+ "backoff-delay-count": current_retry_count + 1
60
+ }
61
+ )
62
+ else
63
+ logger.debug "Max retries exceeded message_id=#{properties[:message_id]}"
64
+
65
+ acknowledge_error(delivery_info, properties, @broker, ex)
66
+ end
67
+ else
68
+ acknowledge_error(delivery_info, properties, @broker, ex)
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ class Worker
75
+ prepend ::Hutch::Retry::WorkerExtension
76
+ end
77
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hutch"
4
+ require_relative "retry/worker_extension"
5
+
6
+ module Hutch
7
+ module Retry
8
+ autoload :Consumer, ::File.expand_path(::File.dirname(__FILE__)) + "/retry/consumer"
9
+ autoload :VERSION, ::File.expand_path(::File.dirname(__FILE__)) + "/retry/version"
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hutch/retry"
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hutch-retry
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Michał Marcinkowski
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-02-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hutch
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.2.0
27
+ description: Reprocess failed messages using an exponential backoff algorithm
28
+ email:
29
+ - michal@marcinkowski.io
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rspec"
35
+ - CHANGELOG.md
36
+ - Gemfile
37
+ - Gemfile.lock
38
+ - LICENSE.txt
39
+ - README.md
40
+ - Rakefile
41
+ - hutch-retry.gemspec
42
+ - lib/hutch-retry.rb
43
+ - lib/hutch/retry.rb
44
+ - lib/hutch/retry/consumer.rb
45
+ - lib/hutch/retry/version.rb
46
+ - lib/hutch/retry/worker_extension.rb
47
+ homepage: https://www.github.com/Motimate/hutch-retry
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.6.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.3.7
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Reprocess failed messages using an exponential backoff algorithm
70
+ test_files: []