Retriable
Retriable is an simple DSL to retry a code block if an exception should be raised. This is especially useful when interacting external api/services or file system calls.
Installation
via command line:
gem install retriable
In your ruby script:
require 'retriable'
In your Gemfile:
gem 'retriable'
Usage
Code in a retriable block will be retried if an exception is raised. By default, Retriable will rescue any exception inherited from StandardError
(and Timeout::Error
, which does not inherit from StandardError
in ruby 1.8) and make 3 retry attempts before raising the last exception.
require 'retriable'
class Api
# Use it in methods that interact with unreliable services
def get
Retriable.retriable do
# code here...
end
end
end
Options
Here are the available options:
tries
(default: 3) - Number of attempts to make at running your code block
interval
(default: 0) - Number of seconds to sleep between attempts
timeout
(default: 0) - Number of seconds to allow the code block to run before raising a Timeout::Error
on
(default: [StandardError, Timeout::Error]) - StandardError
and Timeout::Error
or array of exceptions to rescue for each attempt
on_retry
- (default: nil) - Proc to call after each attempt is rescued
You can pass options via an options Hash
. This example will only retry on a Timeout::Error
, retry 3 times and sleep for a full second before each attempt.
Retriable.retriable :on => Timeout::Error, :tries => 3, :interval => 1 do
# code here...
end
You can also specify multiple errors to retry on by passing an array of exceptions.
Retriable.retriable :on => [Timeout::Error, Errno::ECONNRESET] do
# code here...
end
You can also specify a timeout if you want the code block to only make an attempt for X amount of seconds. This timeout is per attempt.
Retriable.retriable :timeout => 1 do
# code here...
end
If you need millisecond units of time for the sleep or the timeout:
Retriable.retriable :interval => (200/1000.0), :timeout => (500/1000.0) do
# code here...
end
Exponential Backoff
If you'd like exponential backoff, interval can take a Proc
# with exponential back-off - sleep 4, 16, 64, 256, give up
Retriable.retryable :times => 4, :interval => lambda {|attempts| 4 ** attempts} do
# code here...
end
Callbacks
Retriable also provides a callback called :on_retry
that will run after an exception is rescued. This callback provides the number of tries
, and the exception
that was raised in the current attempt. As these are specified in a Proc
, unnecessary variables can be left out of the parameter list.
do_this_on_each_retry = Proc.new do |exception, tries|
log "#{exception.class}: '#{exception.message}' - #{tries} attempts."}
end
Retriable.retriable :on_retry => do_this_on_each_retry do
# code here...
end
Ensure/Else
What if I want to execute a code block at the end, whether or not an exception was rescued (ensure)? Or, what if I want to execute a code block if no exception is raised (else)? Instead of providing more callbacks, I recommend you just wrap retriable in a begin/retry/else/ensure block:
begin
Retriable.retriable do
# some code
end
rescue => e
# run this if retriable ends up re-rasing the exception
else
# run this if retriable doesn't raise any exceptions
ensure
# run this no matter what, exception or no exception
end
Kernel Extension
If you want to call Retriable.retriable
without the Retriable
module prefix and you don't mind extending Kernel
,
there is a kernel extension available for this.
In your ruby script:
require 'retriable/core_ext/kernel'
or in your Gemfile:
gem 'retriable', require: 'retriable/core_ext/kernel'
and then you can call retriable
in any context like this:
retriable do
# code here...
end
Credits
Retriable was originally forked from the retryable-rb gem by Robert Sosinski, which in turn originally inspired by code written by Michael Celona and later assisted by David Malin. The attempt gem by Daniel J. Berger was also an inspiration.