Last active
October 18, 2018 07:37
-
-
Save phoet/92767354f7d78a9f8e2985bc29df88c4 to your computer and use it in GitHub Desktop.
Generic Retry Helper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Gem::Specification.new do |s| | |
s.name = 'retry' | |
s.version = '0.0.1' | |
s.platform = Gem::Platform::RUBY | |
s.author = 'Peter Schröder' | |
s.email = 'phoetmail@googlemail.com' | |
s.license = 'MIT' | |
s.homepage = 'https://gist.github.com/phoet/92767354f7d78a9f8e2985bc29df88c4' | |
s.description = s.summary = 'retry helper as a gist' | |
s.files = ['retry.rb'] | |
s.require_path = '.' | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Retry | |
def with_retry(max_retry_count: 3, retry_timeout: 0.5, backoff: false) | |
Thread.current[:retry_count] ||= 0 | |
yield(Thread.current[:retry_count]) | |
rescue | |
raise if Thread.current[:retry_count] >= max_retry_count - 1 | |
sleep backoff ? exponential_timeout(retry_timeout) : retry_timeout | |
Thread.current[:retry_count] += 1 | |
retry | |
ensure | |
Thread.current[:retry_count] = nil | |
end | |
private | |
def exponential_timeout(retry_timeout, iteration = Thread.current[:retry_count].to_i) | |
retry_timeout = 2 if retry_timeout < 2 | |
retry_timeout ** (iteration + 1) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require_relative 'retry' | |
require 'minitest/autorun' | |
class RetryTest < Minitest::Test | |
def setup | |
@mock = MiniTest::Mock.new | |
@retryer = Retryer.new(@mock) | |
Thread.current[:retry_count] = nil | |
end | |
def test_exponential_has_minimum_of_two_seconds | |
assert_equal 2, @retryer.send(:exponential_timeout, 0.5) | |
assert_equal 2, @retryer.send(:exponential_timeout, 1) | |
assert_equal 2, @retryer.send(:exponential_timeout, 2) | |
end | |
def test_exponential_uses_passed_timeout | |
assert_equal 3, @retryer.send(:exponential_timeout, 3) | |
end | |
def test_exponential_increases_exponentially | |
Thread.current[:retry_count] = 1 | |
assert_equal 4, @retryer.send(:exponential_timeout, 2) | |
Thread.current[:retry_count] = 2 | |
assert_equal 8, @retryer.send(:exponential_timeout, 2) | |
Thread.current[:retry_count] = 3 | |
assert_equal 16, @retryer.send(:exponential_timeout, 2) | |
end | |
def test_retrying_doesnt_do_anything_on_success | |
@mock.expect(:doit, true, [0]) | |
assert @retryer.retry | |
assert_nil Thread.current[:retry_count] | |
end | |
def test_retrying_works_up_to_max_retry_count | |
def @mock.doit(iteration) | |
raise "error that should be retried, #{iteration}" | |
end | |
begin | |
@retryer.retry | |
fail 'expected an error to be raised' | |
rescue | |
assert_equal "error that should be retried, 2", $!.message | |
end | |
end | |
def test_retrying_works_up_to_custom_max_retry_count | |
def @mock.doit(iteration) | |
raise "error that should be retried, #{iteration}" | |
end | |
begin | |
@retryer.retry(max_retry_count: 2) | |
fail 'expected an error to be raised' | |
rescue | |
assert_equal "error that should be retried, 1", $!.message | |
end | |
end | |
class Retryer | |
include Retry | |
def initialize(mock) | |
@mock = mock | |
end | |
def retry(**args) | |
with_retry(**args) do |iteration| | |
@mock.doit(iteration) | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment