Retrying code blocks in Ruby (on exceptions, whatever)

In: Ruby

14 Jan 2008

I am not certain whether the ability to retry a code block when encountering exceptions was a feature available in Ruby, but I certainly couldn’t find anything on that topic (what I did find were mostly about the retry keyword for iterator loops).

Before you ask why I need this, the motivation for this was because I was getting intermittent HTTP errors (503s mostly) trying to connect to a web service. Turns out it’s really easy in Ruby to implement a retryable method that does something like this:

retryable(:tries => 5, :on => OpenURI::HTTPError) do
  open('http://example.com/flaky_api')
  # Code that mashes up stuff for your "social networking" site.
end

Here are the Kernel#retryable specs (pastie).

And the code:

# Options:
# * :tries - Number of retries to perform. Defaults to 1.
# * :on - The Exception on which a retry will be performed. Defaults to Exception, which retries on any Exception.
#
# Example
# =======
#   retryable(:tries => 1, :on => OpenURI::HTTPError) do
#     # your code here
#   end
#
def retryable(options = {}, &block)
  opts = { :tries => 1, :on => Exception }.merge(options)

  retry_exception, retries = opts[:on], opts[:tries]

  begin
    return yield
  rescue retry_exception
    retry if (retries -= 1) > 0
  end

  yield
end

It’s not hard to implement the same for checking return values as well, i.e.

retryable_deluxe(:tries => 5, :on => { :return => nil }) { puts "working..." }

retryable_deluxe(:on => { :exception => StandardError, :return => nil }) do
  # your code here
end

Ruby is nice.

11 Responses to Retrying code blocks in Ruby (on exceptions, whatever)

Avatar

rubylicio.us

January 14th, 2008 at 8pm

Good idea and nice little snippet, I usually tend to skip conditional retries, for no better reason then it feels like it clutters up my code alot … which is a somewhat lame reason now that I think about it :)

Wonder if this could be built into “retry” itself somehow ..

begin
  open('http://example.com/flaky_api')
rescue OpenURI::HTTPError, :retries => 5
  ## blah, errorreporting etc
end

Avatar

rubylicio.us

January 14th, 2008 at 8pm

I ofcourse meant built into “rescue”, not “retry” in the above post.

Avatar

Chu Yeow

January 14th, 2008 at 8pm

Yup exactly – I hated having to put conditionals in there for retries. Thankfully for Ruby blocks, we can abstract that considerably :)

That sounds really nice, if we could monkey patch rescue, but I think it could be hard since rescue‘s a language keyword.

Avatar

macournoyer

January 14th, 2008 at 10pm

very nice, I like your retryable method!

Avatar

Geoff Buesing

January 15th, 2008 at 1am

I like this idea.

I think you could avoid the double yield in your implementation by using Ruby’s “retry” method:


rescue SomeError
tries -= 1
retry if tries > 0

Avatar

Jeremy McAnally

January 15th, 2008 at 2am

The retry keyword works for exceptions also. You could easily write a little helper to handle the retry counter, etc.

Avatar

Chu Yeow

January 15th, 2008 at 9am

You’re right, Geoff and Jeremy! I wasn’t aware the retry keyword worked for exceptions as well. I’ve updated the code :).

Avatar

www.hans-eric.com » Blog Archive » Loop Abstractions in D

January 18th, 2008 at 5am

[...] with Ruby is the natural way in which you can hide looping constructs with descriptive names. Like the retryable example that Cheah Chu Yeow gives on his [...]

Avatar

Loop Abstractions in D revisited

January 31st, 2008 at 11pm

[...] I showed you how we could make loop constructs abstract, in a similar way which is common in Ruby. The example I used as a model was the retryable method from Cheah Chu Yeow. His version is customizable in a way that let you [...]

Avatar

A Ruby ‘tries’ method # Another code blog

October 15th, 2009 at 11am

[...] exercise.   Tj Holowaychuk posted a retry method, which appears itself to be a refactoring of the retryable [...]

Avatar

Trevor Turk — Links for 12-3-10

December 4th, 2010 at 7am

[...] Retrying code blocks in Ruby (on exceptions, whatever) Before you ask why I need this, the motivation for this was because I was getting intermittent HTTP errors (503s mostly) trying to connect to a web service. [...]