Aman King

RetryThis moduleControl PanelChange LogBrowse PagesSearch?

RetryThis module

Recently I needed to execute a block of code in Ruby which could possibly throw a few types of errors. For a certain subset of errors, I wanted the block to be retried for a given number of times before giving up, while for other errors, I wanted the block to bomb immediately.

This is what I came up with (available on Bitbucket and as a gem):

class Gateway
    include RetryThis
    ...
    def make_request(url)
      connection = create_connection
      retry_this(:times => 3, :error_types => [SocketError, Timeout::Error]) do
        # this is retried at least 3 times if a SocketError or Timeout::Error occurs
        connection.start do |http_connection|              
          http_connection.request(Net::HTTP::Get.new(url))
        end
      end
    rescue SocketError, Timeout::Error => e
      raise Gateway::ConnectionError,  "Connection failed: #{e.message}"
    rescue StandardError => error
      raise Gateway::UnexpectedError, "Unexpected error: #{error.message}"
    end
    ...
end

The following is the code for RetryThis:

retry_this.rb

module RetryThis
  def retry_this(options = {})
    number_of_retries = options[:times] || 1
    error_types = options[:error_types] || [StandardError]
    (1 + number_of_retries).times do |attempt_number|
      begin
        return yield if block_given?
      rescue *error_types => e
        raise e unless attempt_number < number_of_retries
      end
    end
  end
end
 
if __FILE__ == $0
  require 'test/unit'
 
  class RetryThisTest < Test::Unit::TestCase
    include RetryThis
 
    def test_should_retry_block_for_given_number_of_times_for_given_error_types_and_raise_error_if_all_attempts_fail
      attempts = 0
      retry_this(:times => 3, :error_types => [TypeError, RuntimeError]) do
        attempts += 1
        if attempts % 2 == 0
          raise 'some runtime error'
        else
          'hi' + 1 # TypeError
        end
      end
    rescue => e
      assert e.kind_of?(TypeError) || e.kind_of?(RuntimeError)
      assert_equal(1 + 3, attempts)
    end
 
    def test_should_retry_block_for_default_1_time_for_default_standard_error_and_raise_error_if_all_attempts_fail
      attempts = 0
      retry_this do
        attempts += 1
        raise StandardError, 'some error'
      end
    rescue => e
      assert_equal('some error', e.message)
      assert_equal(1 + 1, attempts)
    end
 
    def test_should_pass_on_an_error_that_is_not_among_given_errors
      attempts = 0
      retry_this(:error_types => [TypeError]) do
        attempts += 1
        raise StandardError, 'some error'
      end
    rescue => e
      assert_equal('some error', e.message)
      assert_equal(1, attempts)
    end
 
    def test_should_return_value_of_block_if_any_attempt_leads_to_no_error
        attempts = 0
        value = retry_this do
          attempts += 1
          raise 'some error' if attempts == 1
          'hi'
        end
        assert_equal('hi', value)
        assert_equal(1 + 1, attempts)
    end
  end
end

Comments

Talk
Tags: technology:ruby, technology:coding Last modified 10:13 Sat, 26 Jun 2010 by AmanKing. Accessed 608 times Children What Links Here share Share Except where expressly noted, this work is licensed under a Creative Commons License.