Introduction
Retryable is an easy to use DSL to retry code if an exception is raised. This is especially useful when interacting with unreliable services that fail randomly.
Installation
gem install retryable-rb
# In your ruby application
require 'retryable'
Using Retryable
Code wrapped in a Retryable block will be retried if a failure occurs. As such, code attempted once, will be retried again for another attempt if it fails to run.
# Include Retryable into your class
class Api
include Retryable
# Use it in methods that interact with unreliable services
def get
retryable do
# code here...
end
end
end
By default, Retryable will rescue any exception inherited from Exception
, retry once (for a total of two attempts) and sleep for a random amount time (between 0 to 100 milliseconds, in 10 millisecond increments). You can choose additional options by passing them via an options Hash
.
retryable :on => Timeout::Error, :times => 3, :sleep => 1 do
# code here...
end
This example will only retry on a Timeout::Error
, retry 3 times (for a total of 4 attempts) and sleep for a full second before each retry. You can also specify multiple errors to retry on by passing an array.
retryable :on => [Timeout::Error, Errno::ECONNRESET] do
# code here...
end
You can also have Ruby retry immediately after a failure by passing false
as the sleep option.
retryable :sleep => false do
# code here...
end
Retryable also allows for callbacks to be defined, which is useful to log failures for analytics purposes or cleanup after repeated failures. Retryable has three types of callbacks: then
, finally
, and always
.
then
: Run every time a failure occurs.
finally
: Run when the number of retries runs out.
always
: Run when the code wrapped in a Retryable block passes or when the number of retries runs out.
The then
and finally
callbacks pass the exception raised, which can be used for logging or error control. All three callbacks also have a handler
, which provides an interface to pass data between the code wrapped in the Retryable block and the callbacks defined.
Furthermore, each callback provides the number of attempts
, retries
and times
that the wrapped code should be retried. As these are specified in a Proc
, unnecessary variables can be left out of the parameter list.
then_cb = Proc.new do |exception, handler, attempts, retries, times|
log "#{exception.class}: '#{exception.message}' - #{attempts} attempts, #{retries} out of #{times} retries left."}
end
finally_cb = Proc.new do |exception, handler|
log "#{exception.class} raised too many times. First attempt at #{handler[:start]} and final attempt at #{Time.now}"
end
always_cb = Proc.new do |handler, attempts|
log "total time for #{attempts} attempts: #{Time.now - handler[:start]}"
end
retryable :then => then_cb, :finally => finally_cb, :always => always_cb do |handler|
handler[:start] ||= Time.now
# code here...
end
If you are using Retryable once or outside of a class, you can also use it via its module method as well.
Retryable.retryable do
# code here...
end
Credits
Retryable was inspired by code written by Michael Celona and later assisted by David Malin.