Skip to content

Instantly share code, notes, and snippets.

@phoet
Last active October 18, 2018 07:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phoet/92767354f7d78a9f8e2985bc29df88c4 to your computer and use it in GitHub Desktop.
Save phoet/92767354f7d78a9f8e2985bc29df88c4 to your computer and use it in GitHub Desktop.
Generic Retry Helper
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
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
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