retryable-2.0.4/0000755000004100000410000000000012750322315013536 5ustar www-datawww-dataretryable-2.0.4/Rakefile0000644000004100000410000000031312750322315015200 0ustar www-datawww-datarequire 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) task :test => :spec task :default => :spec require 'yard' YARD::Rake::YardocTask.new retryable-2.0.4/spec/0000755000004100000410000000000012750322315014470 5ustar www-datawww-dataretryable-2.0.4/spec/spec_helper.rb0000644000004100000410000000064712750322315017315 0ustar www-datawww-datarequire File.dirname(__FILE__) + '/../lib/retryable' require 'rspec' require 'pry' RSpec.configure do |config| config.disable_monkey_patching! config.before(:each) do reset_config end def count_retryable(*opts) @try_count = 0 return Retryable.retryable(*opts) do |*args| @try_count += 1 yield *args end end private def reset_config Retryable.configuration = nil end end retryable-2.0.4/spec/lib/0000755000004100000410000000000012750322315015236 5ustar www-datawww-dataretryable-2.0.4/spec/lib/configuration_spec.rb0000644000004100000410000000154512750322315021451 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe Retryable do it 'is enabled by default' do expect(Retryable).to be_enabled end it 'could be disabled' do Retryable.disable expect(Retryable).not_to be_enabled end context 'when disabled' do before do Retryable.disable end it 'could be re-enabled' do Retryable.enable expect(Retryable).to be_enabled end end context 'when configured globally with custom sleep parameter' do it 'passes retry count and exception on retry' do expect(Kernel).to receive(:sleep).once.with(3) Retryable.configure do |config| config.sleep = 3 end count_retryable(:tries => 2) do |tries, ex| expect(ex.class).to eq(StandardError) if tries > 0 raise StandardError if tries < 1 end expect(@try_count).to eq(2) end end end retryable-2.0.4/spec/lib/retryable_spec.rb0000644000004100000410000001117312750322315020571 0ustar www-datawww-datarequire 'spec_helper' require 'timeout' RSpec.describe 'Retryable.retryable' do before(:each) do Retryable.enable @attempt = 0 end it 'catch StandardError only by default' do expect do count_retryable(:tries => 2) { |tries, ex| raise Exception if tries < 1 } end.to raise_error Exception expect(@try_count).to eq(1) end it 'retries on default exception' do expect(Kernel).to receive(:sleep).once.with(1) count_retryable(:tries => 2) { |tries, ex| raise StandardError if tries < 1 } expect(@try_count).to eq(2) end it 'does not retry if disabled' do Retryable.disable expect do count_retryable(:tries => 2) { raise } end.to raise_error RuntimeError expect(@try_count).to eq(1) end it 'executes *ensure* clause' do ensure_cb = Proc.new do |retries| expect(retries).to eq(0) end Retryable.retryable(:ensure => ensure_cb) { } end it 'passes retry count and exception on retry' do expect(Kernel).to receive(:sleep).once.with(1) count_retryable(:tries => 2) do |tries, ex| expect(ex.class).to eq(StandardError) if tries > 0 raise StandardError if tries < 1 end expect(@try_count).to eq(2) end it 'makes another try if exception is covered by :on' do allow(Kernel).to receive(:sleep) count_retryable(:on => [StandardError, ArgumentError, RuntimeError] ) { |tries, ex| raise ArgumentError if tries < 1 } expect(@try_count).to eq(2) end it 'does not try on unexpected exception' do allow(Kernel).to receive(:sleep) expect do count_retryable(:on => RuntimeError ) { |tries, ex| raise StandardError if tries < 1 } end.to raise_error StandardError expect(@try_count).to eq(1) end it 'retries three times' do allow(Kernel).to receive(:sleep) count_retryable(:tries => 3) { |tries, ex| raise StandardError if tries < 2 } expect(@try_count).to eq(3) end it 'retries infinitely' do expect do Timeout::timeout(3) do count_retryable(:tries => :infinite, :sleep => 0.1) { |tries, ex| raise StandardError } end end.to raise_error Timeout::Error expect(@try_count).to be > 10 end it 'retries on default exception' do expect(Kernel).to receive(:sleep).once.with(1) count_retryable(:tries => 2) { |tries, ex| raise StandardError if tries < 1 } expect(@try_count).to eq(2) end it 'executes exponential backoff scheme for :sleep option' do [1, 4, 16, 64].each { |i| expect(Kernel).to receive(:sleep).once.ordered.with(i) } expect do Retryable.retryable(:tries => 5, :sleep => lambda { |n| 4**n }) { raise RangeError } end.to raise_error RangeError end it 'calls :sleep_method option' do sleep_method = double expect(sleep_method).to receive(:call).twice expect do Retryable.retryable(:tries => 3, :sleep_method => sleep_method) { |tries, ex| raise RangeError if tries < 9} end.to raise_error RangeError end it 'does not retry any exception if :on is empty list' do expect do count_retryable(:on => []) { raise } end.to raise_error RuntimeError expect(@try_count).to eq(1) end it 'catches an exception that matches the regex' do expect(Kernel).to receive(:sleep).once.with(1) count_retryable(:matching => /IO timeout/) { |c,e| raise "yo, IO timeout!" if c == 0 } expect(@try_count).to eq(2) end it 'does not catch an exception that does not match the regex' do expect(Kernel).not_to receive(:sleep) expect do count_retryable(:matching => /TimeError/) { raise "yo, IO timeout!" } end.to raise_error RuntimeError expect(@try_count).to eq(1) end it 'does not allow invalid options' do expect do Retryable.retryable(:bad_option => 2) { raise "this is bad" } end.to raise_error ArgumentError, '[Retryable] Invalid options: bad_option' end it 'accepts a callback to run after an exception is rescued' do expect do Retryable.retryable(:sleep => 0, :exception_cb => Proc.new {|e| @raised = e.to_s }) {|tries, ex| raise StandardError.new("this is fun!") if tries < 1 } end.not_to raise_error expect(@raised).to eq("this is fun!") end it 'does not retry on :not exception' do expect do count_retryable(:not => RuntimeError ) { |tries, ex| raise RuntimeError if tries < 1 } end.to raise_error RuntimeError expect(@try_count).to eq(1) end it 'gives precidence for :not over :on' do expect do count_retryable(:sleep => 0, :tries => 3, :on => StandardError, :not => IndexError ) { |tries, ex| raise tries >= 1 ? IndexError : StandardError } end.to raise_error IndexError expect(@try_count).to eq(2) end end retryable-2.0.4/LICENSE.md0000644000004100000410000000213212750322315015140 0ustar www-datawww-data(The MIT License) Copyright (c) 2011 Carlo Zottmann Copyright (c) 2012 Nikita Fedyashev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. retryable-2.0.4/lib/0000755000004100000410000000000012750322315014304 5ustar www-datawww-dataretryable-2.0.4/lib/retryable.rb0000644000004100000410000000554212750322315016630 0ustar www-datawww-datarequire 'retryable/version' require 'retryable/configuration' module Retryable class << self # A Retryable configuration object. Must act like a hash and return sensible # values for all Retryable configuration options. See Retryable::Configuration. attr_writer :configuration # Call this method to modify defaults in your initializers. # # @example # Retryable.configure do |config| # config.ensure = Proc.new {} # config.exception_cb = Proc.new {} # config.matching = /.*/ # config.on = StandardError # config.sleep = 1 # config.tries = 2 # config.not = [] # end def configure yield(configuration) end # The configuration object. # @see Retryable.configure def configuration @configuration ||= Configuration.new end def enabled? configuration.enabled? end def enable configuration.enable end def disable configuration.disable end def retryable(options = {}, &block) opts = { :tries => self.configuration.tries, :sleep => self.configuration.sleep, :on => self.configuration.on, :matching => self.configuration.matching, :ensure => self.configuration.ensure, :exception_cb => self.configuration.exception_cb, :not => self.configuration.not, :sleep_method => self.configuration.sleep_method } check_for_invalid_options(options, opts) opts.merge!(options) return if opts[:tries] == 0 on_exception = [ opts[:on] ].flatten not_exception = [ opts[:not] ].flatten tries = opts[:tries] retries = 0 retry_exception = nil begin return yield retries, retry_exception rescue *not_exception raise rescue *on_exception => exception raise unless configuration.enabled? raise unless exception.message =~ opts[:matching] raise if tries != :infinite && retries+1 >= tries # Interrupt Exception could be raised while sleeping begin seconds = opts[:sleep].respond_to?(:call) ? opts[:sleep].call(retries) : opts[:sleep] opts[:sleep_method].call(seconds) rescue *not_exception raise rescue *on_exception end retries += 1 retry_exception = exception opts[:exception_cb].call(retry_exception) retry ensure opts[:ensure].call(retries) end end private def check_for_invalid_options(custom_options, default_options) invalid_options = default_options.merge(custom_options).keys - default_options.keys raise ArgumentError.new("[Retryable] Invalid options: #{invalid_options.join(", ")}") unless invalid_options.empty? end end end retryable-2.0.4/lib/retryable/0000755000004100000410000000000012750322315016275 5ustar www-datawww-dataretryable-2.0.4/lib/retryable/configuration.rb0000644000004100000410000000300012750322315021462 0ustar www-datawww-datamodule Retryable # Used to set up and modify settings for the retryable. class Configuration OPTIONS = [ :ensure, :exception_cb, :matching, :on, :sleep, :tries, :not, :sleep_method ].freeze attr_accessor :ensure attr_accessor :exception_cb attr_accessor :matching attr_accessor :on attr_accessor :sleep attr_accessor :tries attr_accessor :not attr_accessor :sleep_method attr_accessor :enabled alias_method :enabled?, :enabled def initialize @ensure = Proc.new {} @exception_cb = Proc.new {} @matching = /.*/ @on = StandardError @sleep = 1 @tries = 2 @not = [] @sleep_method = lambda do |seconds| Kernel.sleep(seconds) end @enabled = true end def enable @enabled = true end def disable @enabled = false end # Allows config options to be read like a hash # # @param [Symbol] option Key for a given attribute def [](option) send(option) end # Returns a hash of all configurable options def to_hash OPTIONS.inject({}) do |hash, option| hash[option.to_sym] = self.send(option) hash end end # Returns a hash of all configurable options merged with +hash+ # # @param [Hash] hash A set of configuration options that will take precedence over the defaults def merge(hash) to_hash.merge(hash) end end end retryable-2.0.4/lib/retryable/version.rb0000644000004100000410000000052212750322315020306 0ustar www-datawww-datamodule Retryable class Version MAJOR = 2 unless defined? Retryable::Version::MAJOR MINOR = 0 unless defined? Retryable::Version::MINOR PATCH = 4 unless defined? Retryable::Version::PATCH class << self # @return [String] def to_s [MAJOR, MINOR, PATCH].compact.join('.') end end end end retryable-2.0.4/CHANGELOG.md0000644000004100000410000000403612750322315015352 0ustar www-datawww-data## Retryable 2.0.4 ## * :infinite value is now available as :tries paramater. Use it for retrying your blocks infinitely until it stops failing. * :sleep_method parameter has been added. This can be very useful when you are working with Celluloid which implements its own version of the method sleep. Use `:sleep_method => Celluloid.method(:sleep)` in such cases. ## Retryable 2.0.3 ## * gemspec contains explicit licence option from now on(MIT) ## Retryable 2.0.2 ## * :not configuration option has been added for specifying exceptions when a retry should not be performed. Thanks @drunkel ## Retryable 2.0.1 ## * Retryable can now be configured globally via Retryable.configure block. ## Retryable 2.0.0 ## * Retryable can now be used without monkey patching Kernel module(use `Retryable.retryable` instead). Thanks @oppegard ## Retryable 1.3.6 ## * Fixed warning: assigned but unused variable - tries. Thanks @amatsuda ## Retryable 1.3.5 ## * New callback option(:exception_cb) to run after an rescued exception is introduced. Thanks @jondruse ## Retryable 1.3.4 ## * Namespace issue has been fixed. Thanks @darkhelmet ## Retryable 1.3.3 ## * Retryable::Version constant typo has been fixed ## Retryable 1.3.2 ## * Retryable.disable method has been added * Retryable.enabled method has been added ## Retryable 1.3.1 ## * :ensure retryable option add added * ArgumentError is raised instead of InvalidRetryableOptions in case of invalid option param for retryable block ## Retryable 1.3.0 ## * StandardError is now default exception for rescuing. ## Retryable 1.2.5 ## * became friendly to any rubygems version installed ## Retryable 1.2.4 ## * added :matching option + better options validation ## Retryable 1.2.3 ## * fixed dependencies ## Retryable 1.2.2 ## * added :sleep option ## Retryable 1.2.1 ## * stability -- Thoroughly unit-tested ## Retryable 1.2.0 ## * FIX -- block would run twice when `:tries` was set to `0`. (Thanks for the heads-up to [Tuker](http://github.com/tuker).) retryable-2.0.4/retryable.gemspec0000644000004100000410000000145212750322315017076 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.expand_path('../lib/retryable/version', __FILE__) Gem::Specification.new do |gem| gem.add_development_dependency 'bundler', '~> 1.0' gem.authors = ["Nikita Fedyashev", "Carlo Zottmann", "Chu Yeow"] gem.description = %q{Retryable#retryable, allow for retrying of code blocks.} gem.email = %q{nfedyashev@gmail.com} gem.files = %w(CHANGELOG.md LICENSE.md README.md Rakefile retryable.gemspec) gem.files += Dir.glob("lib/**/*.rb") gem.files += Dir.glob("spec/**/*") gem.homepage = %q{http://github.com/nfedyashev/retryable} gem.name = 'retryable' gem.license = 'MIT' gem.require_paths = ["lib"] gem.required_rubygems_version = '>= 1.3.6' gem.summary = gem.description gem.test_files = Dir.glob("spec/**/*") gem.version = Retryable::Version end retryable-2.0.4/README.md0000644000004100000410000001222312750322315015015 0ustar www-datawww-dataretryable gem ===== [![Build Status](https://travis-ci.org/nfedyashev/retryable.png?branch=master)](https://travis-ci.org/nfedyashev/retryable) [![Dependency Status](https://www.versioneye.com/ruby/retryable/badge.svg)](https://www.versioneye.com/ruby/retryable) Description -------- Runs a code block, and retries it when an exception occurs. It's great when working with flakey webservices (for example). It's configured using several optional parameters `:tries`, `:on`, `:sleep`, `:matching`, `:ensure`, `:exception_cb`, `:not`, `:sleep_method` and runs the passed block. Should an exception occur, it'll retry for (n-1) times. Should the number of retries be reached without success, the last exception will be raised. Examples -------- Open an URL, retry up to two times when an `OpenURI::HTTPError` occurs. ``` ruby require "open-uri" Retryable.retryable(:tries => 3, :on => OpenURI::HTTPError) do xml = open("http://example.com/test.xml").read end ``` Try the block forever. ```ruby Retryable.retryable(:tries => :infinite) do # some code end ``` Do _something_, retry up to four times for either `ArgumentError` or `TimeoutError` exceptions. ``` ruby Retryable.retryable(:tries => 5, :on => [ArgumentError, TimeoutError]) do # some crazy code end ``` Ensure that block of code is executed, regardless of whether an exception was raised. It doesn't matter if the block exits normally, if it retries to execute block of code, or if it is terminated by an uncaught exception -- the ensure block will get run. ``` ruby f = File.open("testfile") ensure_cb = Proc.new do |retries| puts "total retry attempts: #{retries}" f.close end Retryable.retryable(:ensure => ensure_cb) do # process file end ``` ## Defaults :tries => 2, :on => StandardError, :sleep => 1, :matching => /.*/, :ensure => Proc.new { }, :exception_cb => Proc.new { }, :not => [], :sleep_method => lambda { |n| Kernel.sleep(n) } Retryable also could be configured globally to change those defaults: ``` Retryable.configure do |config| config.ensure = Proc.new {} config.exception_cb = Proc.new {} config.matching = /.*/ config.on = StandardError config.sleep = 1 config.tries = 2 config.not = [] config.sleep_method = Celluloid.method(:sleep) end ``` Sleeping -------- By default Retryable waits for one second between retries. You can change this and even provide your own exponential backoff scheme. ``` Retryable.retryable(:sleep => 0) { } # don't pause at all between retries Retryable.retryable(:sleep => 10) { } # sleep ten seconds between retries Retryable.retryable(:sleep => lambda { |n| 4**n }) { } # sleep 1, 4, 16, etc. each try ``` Matching error messages -------- You can also retry based on the exception message: ``` Retryable.retryable(:matching => /IO timeout/) do |retries, exception| raise "yo, IO timeout!" if retries == 0 end ``` Block Parameters -------- Your block is called with two optional parameters: the number of tries until now, and the most recent exception. ``` Retryable.retryable do |retries, exception| puts "try #{retries} failed with exception: #{exception}" if retries > 0 pick_up_soap end ``` Callback to run after an exception is rescued -------- ``` exception_cb = Proc.new do |exception| # http://smartinez87.github.io/exception_notification ExceptionNotifier.notify_exception(exception, :data => {:message => "it failed"}) end Retryable.retryable(:exception_cb => exception_cb) do # perform risky operation end ``` You can temporary disable retryable blocks -------- ``` Retryable.enabled? => true Retryable.disable Retryable.enabled? => false ``` Specify exceptions where a retry should NOT be performed -------- No more tries will be made if an exception listed in `:not` is raised. Takes precedence over `:on`. ``` class MyError < StandardError; end Retryable.retryable(:tries => 5, :on => [StandardError], :not => [MyError]) do raise MyError "No retries!" end ``` Specify the sleep method to use -------- This can be very useful when you are working with [Celluloid](https://github.com/celluloid/celluloid) which implements its own version of the method sleep. ``` Retryable.retryable(:sleep_method => Celluloid.method(:sleep)) do retrieve_url end ``` Supported Ruby Versions ------- This library aims to support and is [tested against][travis] the following Ruby versions: * Ruby 1.8.7 * Ruby 1.9.2 * Ruby 1.9.3 * Ruby 2.0.0 * Ruby 2.1.2 * Ruby 2.2.0 If something doesn't work on one of these versions, it's a bug. This library may inadvertently work (or seem to work) on other Ruby versions, however support will only be provided for the versions listed above. If you would like this library to support another Ruby version or implementation, you may volunteer to be a maintainer. Installation ------- Install the gem: ``` bash $ gem install retryable ``` Add it to your Gemfile: ``` ruby gem 'retryable' ``` ## Thanks [Chu Yeow for this nifty piece of code](http://blog.codefront.net/2008/01/14/retrying-code-blocks-in-ruby-on-exceptions-whatever/) [Scott Bronson](https://github.com/bronson/retryable) [travis]: http://travis-ci.org/nfedyashev/retryable