retry-handler
A Ruby library that makes it easy to write “retry up to N times on failure” logic. It adds a retry method to Proc, so you just wrap the code you want to retry in a block and can specify the maximum attempts, wait time, and target exceptions.
It is published as a gem and works on Ruby 1.8/1.9.
gem install retry-handler
Usage
When an exception is raised inside the Proc’s block, it automatically retries up to the specified number of times.
Proc.new{
# なんかエラーの起こるコード
}.retry(:max => 3, :accept_exception => StandardError)
With :wait, it sleeps the given number of seconds after each failure before the next attempt. Add :logger if you want to see what’s happening. If writing Proc.new feels clunky, a lambda works too.
lambda{}.retry(:max => 3, :wait => 3)
lambda{}.retry(:max => 3, :wait => 3, :logger => Logger.new(STDERR))
Method objects also gain retry, so you can make an existing method retryable as-is. Here’s an example with File.read.
File.method(:read).retry("./not_found.txt",
:accept_exception => StandardError, :logger => Logger.new(STDERR))
Internals
The whole thing is a single file (lib/retry-handler.rb), and the core is one method, RetryHandler.retry_handler. Proc#retry, Method#retry, and Kernel#retry_handler are all monkey patches that delegate to it.
- Option defaults —
:maxis 3 retries,:waitis 1 second,:timeoutis none (unlimited),:accept_exceptionis the bundledRetryError, and:loggerisLogger.new(nil)(outputs nothing) - Only exceptions specified via
:accept_exceptionare retried. You wouldn’t normally raise the defaultRetryErroryourself, so in practice you pass something like:accept_exception => StandardErroras in the README examples. Other exceptions pass straight through - With
:timeout, the block is wrapped intimeout(), which raises the:accept_exceptionexception when time runs out. Since a timeout turns into “an exception to be caught”, the retry loop can handle it uniformly as just another exception Method#retrypulls a trailingHashoff the argument list as its options, if present, and passes the rest as arguments to the method itself
The retry loop itself (RetryHandler._retry_handler) works like this. It is a straightforward begin/rescue/retry construction that increments a counter and sleeps after each failure before trying again.
- When the retries are exhausted, it raises
RetryOverErrorcarrying the last exception. There are two exception classes:RetryError < StandardErrorandRetryOverError < RetryError :maxis the number of retries, so the total number of executions is the initial attempt + up to:maxretries = up to 4 runs (with the defaults)
My own vrowser uses this gem, applying it to retry fetching the server list from the master server when it fails.