retryable-2.0.1/0000755000175000017500000000000012542002766013057 5ustar globusglobusretryable-2.0.1/lib/0000755000175000017500000000000012542002766013625 5ustar globusglobusretryable-2.0.1/lib/retryable.rb0000644000175000017500000000501312542002766016142 0ustar globusglobusrequire '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 # 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 } check_for_invalid_options(options, opts) opts.merge!(options) return if opts[:tries] == 0 on_exception, tries = [ opts[:on] ].flatten, opts[:tries] retries = 0 retry_exception = nil begin return yield retries, retry_exception rescue *on_exception => exception raise unless configuration.enabled? raise unless exception.message =~ opts[:matching] raise if retries+1 >= tries # Interrupt Exception could be raised while sleeping begin Kernel.sleep opts[:sleep].respond_to?(:call) ? opts[:sleep].call(retries) : opts[:sleep] 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.1/lib/retryable/0000755000175000017500000000000012542002766015616 5ustar globusglobusretryable-2.0.1/lib/retryable/configuration.rb0000644000175000017500000000251412542002766021014 0ustar globusglobusmodule Retryable # Used to set up and modify settings for the retryable. class Configuration OPTIONS = [ :ensure, :exception_cb, :matching, :on, :sleep, :tries ].freeze attr_accessor :ensure attr_accessor :exception_cb attr_accessor :matching attr_accessor :on attr_accessor :sleep attr_accessor :tries attr_accessor :enabled alias_method :enabled?, :enabled def initialize @ensure = Proc.new {} @exception_cb = Proc.new {} @matching = /.*/ @on = StandardError @sleep = 1 @tries = 2 @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.1/lib/retryable/version.rb0000644000175000017500000000052212542002766017627 0ustar globusglobusmodule Retryable class Version MAJOR = 2 unless defined? Retryable::Version::MAJOR MINOR = 0 unless defined? Retryable::Version::MINOR PATCH = 1 unless defined? Retryable::Version::PATCH class << self # @return [String] def to_s [MAJOR, MINOR, PATCH].compact.join('.') end end end end retryable-2.0.1/Rakefile0000644000175000017500000000031312542002766014521 0ustar globusglobusrequire '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.1/retryable.gemspec0000644000175000017500000000142512542002766016417 0ustar globusglobus# -*- 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{loci.master@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.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.1/spec/0000755000175000017500000000000012542002766014011 5ustar globusglobusretryable-2.0.1/spec/lib/0000755000175000017500000000000012542002766014557 5ustar globusglobusretryable-2.0.1/spec/lib/retryable_spec.rb0000644000175000017500000000711712542002766020115 0ustar globusglobusrequire 'spec_helper' 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 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 '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 end retryable-2.0.1/spec/lib/configuration_spec.rb0000644000175000017500000000154512542002766020772 0ustar globusglobusrequire '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.1/spec/spec_helper.rb0000644000175000017500000000063112542002766016627 0ustar globusglobusrequire File.dirname(__FILE__) + '/../lib/retryable' require 'rspec' 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.1/LICENSE.md0000644000175000017500000000213212542002766014461 0ustar globusglobus(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.1/CHANGELOG.md0000644000175000017500000000267012542002766014675 0ustar globusglobus## 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.1/README.md0000644000175000017500000001025312542002766014337 0ustar globusglobusretryable gem ===== [![Build Status](https://travis-ci.org/nfedyashev/retryable.png?branch=master)](https://travis-ci.org/nfedyashev/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 four optional parameters `:tries`, `:on`, `:sleep`, `:matching`, `:ensure`, `:exception_cb` 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 ``` 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 { } 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 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 ``` 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 retryable-2.0.1/metadata.yml0000644000175000017500000000324212542002766015363 0ustar globusglobus--- !ruby/object:Gem::Specification name: retryable version: !ruby/object:Gem::Version version: 2.0.1 platform: ruby authors: - Nikita Fedyashev - Carlo Zottmann - Chu Yeow autorequire: bindir: bin cert_chain: [] date: 2015-01-30 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: bundler requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' description: Retryable#retryable, allow for retrying of code blocks. email: loci.master@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - CHANGELOG.md - LICENSE.md - README.md - Rakefile - lib/retryable.rb - lib/retryable/configuration.rb - lib/retryable/version.rb - retryable.gemspec - spec/lib/configuration_spec.rb - spec/lib/retryable_spec.rb - spec/spec_helper.rb homepage: http://github.com/nfedyashev/retryable licenses: [] metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.3.6 requirements: [] rubyforge_project: rubygems_version: 2.4.5 signing_key: specification_version: 4 summary: Retryable#retryable, allow for retrying of code blocks. test_files: - spec/lib/configuration_spec.rb - spec/lib/retryable_spec.rb - spec/spec_helper.rb has_rdoc: