retryable-3.0.5/0000755000004100000410000000000013641337156013551 5ustar www-datawww-dataretryable-3.0.5/README.md0000644000004100000410000001647113641337156015041 0ustar www-datawww-data# Retryable [![Gem Version](https://badge.fury.io/rb/retryable.svg)](https://badge.fury.io/rb/retryable) [![Build Status](https://travis-ci.org/nfedyashev/retryable.png?branch=master)](https://travis-ci.org/nfedyashev/retryable) [![Code Climate](https://codeclimate.com/github/nfedyashev/retryable/badges/gpa.svg)](https://codeclimate.com/github/nfedyashev/retryable) [![Test Coverage](https://codeclimate.com/github/nfedyashev/retryable/badges/coverage.svg)](https://codeclimate.com/github/nfedyashev/retryable/coverage) [![Inline docs](http://inch-ci.org/github/nfedyashev/retryable.svg?branch=master)](http://inch-ci.org/github/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 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. Installation ------- Install the gem: ``` bash $ gem install retryable ``` Add it to your Gemfile: ``` ruby gem 'retryable' ``` 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 # For ruby versions prior to 1.9.2 use :infinite symbol instead Retryable.retryable(tries: Float::INFINITY) do # code here end ``` Do something, retry up to four times for either `ArgumentError` or `Timeout::Error` exceptions. ``` ruby Retryable.retryable(tries: 5, on: [ArgumentError, Timeout::Error]) do # code here 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 do |retries| puts "total retry attempts: #{retries}" f.close end Retryable.retryable(ensure: ensure_cb) do # process file end ``` ## Defaults contexts: {}, ensure: proc { }, exception_cb: proc { }, log_method: proc { }, matching : /.*/, not: [], on: StandardError, sleep: 1, sleep_method: lambda { |n| Kernel.sleep(n) }, tries: 2 Retryable also could be configured globally to change those defaults: ```ruby Retryable.configure do |config| config.contexts = {} config.ensure = proc {} config.exception_cb = proc {} config.log_method = proc {} config.matching = /.*/ config.not = [] config.on = StandardError config.sleep = 1 config.sleep_method = Celluloid.method(:sleep) 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. ```ruby 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: ```ruby Retryable.retryable(matching: /IO timeout/) do |retries, exception| raise "oops IO timeout!" if retries == 0 end #matching param supports array format as well: Retryable.retryable(matching: [/IO timeout/, "IO tymeout"]) do |retries, exception| raise "oops 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. ```ruby Retryable.retryable do |retries, exception| puts "try #{retries} failed with exception: #{exception}" if retries > 0 # code here end ``` Callback to run after an exception is rescued -------- ```ruby exception_cb = proc do |exception| # http://smartinez87.github.io/exception_notification ExceptionNotifier.notify_exception(exception, data: {message: "it failed"}) end Retryable.retryable(exception_cb: exception_cb) do # code here end ``` Logging -------- ```ruby # or extract it to global config instead: log_method = lambda do |retries, exception| Logger.new(STDOUT).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}") end Retryable.retryable(log_method: log_method, matching: /IO timeout/) do |retries, exception| raise "oops IO timeout!" if retries == 0 end #D, [2018-09-01T18:19:06.093811 #22535] DEBUG -- : [Attempt #1] Retrying because [RuntimeError - oops IO timeout!]: (irb#1):6:in `block in irb_binding' | /home/nikita/Projects/retryable/lib/retryable.rb:73:in `retryable' | (irb#1):6:in `irb_binding' | /home/nikita/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval' | /home/nikita/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/irb/workspace.rb:85:in `evaluate' ``` If you prefer to use Rails' native logger: ```ruby log_method = lambda do |retries, exception| Rails.logger.debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}") end ``` Contexts -------- Contexts allow you to extract common `Retryable.retryable` calling options for reuse or readability purposes. ```ruby Retryable.configure do |config| config.contexts[:faulty_service] = { :on: [FaultyServiceTimeoutError], :sleep: 10, :tries: 5 } end Retryable.with_context(:faulty_service) { # code here } ``` You may also override options defined in your contexts: ```ruby # :on & sleep defined in the context earlier are still effective Retryable.with_context(:faulty_service, tries: 999) { # code here } ``` You can temporary disable retryable blocks -------- ```ruby 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`. ```ruby 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. ```ruby Retryable.retryable(sleep_method: Celluloid.method(:sleep)) do # code here end ``` Supported Ruby Versions ------- This library aims to support and is [tested against][travis] the following Ruby versions: * Ruby 1.9.3 * Ruby 2.0.0 * Ruby 2.1.10 * Ruby 2.2.10 * Ruby 2.3.8 * Ruby 2.4.5 * Ruby 2.5.3 * Ruby 2.6.1 *NOTE: if you need `retryable` to be running on Ruby 1.8 use gem versions prior to 3.0.0 release* 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. retryable-3.0.5/LICENSE.md0000644000004100000410000000213213641337156015153 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-3.0.5/spec/0000755000004100000410000000000013641337156014503 5ustar www-datawww-dataretryable-3.0.5/spec/retryable_logging_spec.rb0000644000004100000410000000205313641337156021541 0ustar www-datawww-datarequire 'spec_helper' require 'logger' RSpec.describe Retryable do describe '.retryable' do before do described_class.enable expect(Kernel).to receive(:sleep) end let(:retryable) do -> { Retryable.retryable(tries: 2) { |tries| raise StandardError, "because foo" if tries < 1 } } end context 'given default configuration' do it 'does not output anything' do expect { retryable.call }.not_to output.to_stdout_from_any_process end end context 'given custom STDOUT logger config option' do it 'does not output anything' do described_class.configure do |config| config.log_method = lambda do |retries, exception| Logger.new(STDOUT).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}") end end expect { retryable.call }.to output(/\[Attempt #1\] Retrying because \[StandardError - because foo\]/).to_stdout_from_any_process end end end end retryable-3.0.5/spec/retryable_with_context_spec.rb0000644000004100000410000000317113641337156022634 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe Retryable do describe '.with_context' do before do described_class.enable @attempt = 0 end it 'properly checks context configuration' do expect do described_class.with_context(:foo) {} end.to raise_error ArgumentError, 'foo not found in Retryable.configuration.contexts. Available contexts: []' expect do described_class.retryable_with_context(:bar) {} end.to raise_error ArgumentError, 'bar not found in Retryable.configuration.contexts. Available contexts: []' expect do described_class.configure do |config| config.contexts[:faulty_service] = { sleep: 3 } end described_class.retryable_with_context(:baz) {} end.to raise_error ArgumentError, 'baz not found in Retryable.configuration.contexts. Available contexts: [:faulty_service]' end it 'properly fetches context options' do allow(Kernel).to receive(:sleep) described_class.configure do |config| config.contexts[:faulty_service] = { tries: 3 } end c = counter_with_context(:faulty_service) { |tries| raise StandardError if tries < 2 } expect(c.count).to eq(3) end it 'properly overrides context options with local arguments' do allow(Kernel).to receive(:sleep) described_class.configure do |config| config.contexts[:faulty_service] = { tries: 1 } end c = counter_with_context(:faulty_service, tries: 3) { |tries| raise StandardError if tries < 2 } expect(c.count).to eq(3) end end end retryable-3.0.5/spec/retryable_spec.rb0000644000004100000410000001406313641337156020037 0ustar www-datawww-datarequire 'spec_helper' require 'timeout' RSpec.describe Retryable do describe '.retryable' do before do described_class.enable @attempt = 0 end it 'catch StandardError only by default' do expect do counter(tries: 2) { |tries| raise Exception if tries < 1 } end.to raise_error Exception expect(counter.count).to eq(1) end it 'retries on default exception' do expect(Kernel).to receive(:sleep).once.with(1) counter(tries: 2) { |tries| raise StandardError if tries < 1 } expect(counter.count).to eq(2) end it 'does not retry if disabled' do described_class.disable expect do counter(tries: 2) { raise } end.to raise_error RuntimeError expect(counter.count).to eq(1) end it 'executes *ensure* clause' do ensure_cb = proc do |retries| expect(retries).to eq(0) end described_class.retryable(ensure: ensure_cb) {} end it 'passes retry count and exception on retry' do expect(Kernel).to receive(:sleep).once.with(1) counter(tries: 2) do |tries, ex| expect(ex.class).to eq(StandardError) if tries > 0 raise StandardError if tries < 1 end expect(counter.count).to eq(2) end it 'makes another try if exception is covered by :on' do allow(Kernel).to receive(:sleep) counter(on: [StandardError, ArgumentError, RuntimeError]) do |tries| raise ArgumentError if tries < 1 end expect(counter.count).to eq(2) end it 'does not retry on :not exception which is covered by Array' do expect do counter(not: [RuntimeError, IndexError]) { |tries| raise RuntimeError if tries < 1 } end.to raise_error RuntimeError expect(counter.count).to eq(1) end it 'does not try on unexpected exception' do allow(Kernel).to receive(:sleep) expect do counter(on: RuntimeError) { |tries| raise StandardError if tries < 1 } end.to raise_error StandardError expect(counter.count).to eq(1) end it 'retries three times' do allow(Kernel).to receive(:sleep) counter(tries: 3) { |tries| raise StandardError if tries < 2 } expect(counter.count).to eq(3) end context 'infinite retries' do example 'with magic constant' do expect do Timeout.timeout(3) do counter(tries: :infinite, sleep: 0.1) { raise StandardError } end end.to raise_error Timeout::Error expect(counter.count).to be > 10 end example 'with native infinity data type' do expect do require 'bigdecimal' tries = [Float::INFINITY, BigDecimal::INFINITY, BigDecimal("1.0") / BigDecimal("0.0")] Timeout.timeout(3) do counter(tries: tries.sample, sleep: 0.1) { raise StandardError } end end.to raise_error Timeout::Error expect(counter.count).to be > 10 end 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 described_class.retryable(tries: 5, sleep: ->(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 described_class.retryable(tries: 3, sleep_method: sleep_method) { |tries| 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 counter(on: []) { raise } end.to raise_error RuntimeError expect(counter.count).to eq(1) end it 'catches an exception that matches the regex' do expect(Kernel).to receive(:sleep).once.with(1) counter(matching: /IO timeout/) { |c, _e| raise 'yo, IO timeout!' if c == 0 } expect(counter.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 counter(matching: /TimeError/) { raise 'yo, IO timeout!' } end.to raise_error RuntimeError expect(counter.count).to eq(1) end it 'catches an exception in the list of matches' do expect(Kernel).to receive(:sleep).once.with(1) counter(matching: [/IO timeout/, 'IO tymeout']) { |c, _e| raise 'yo, IO timeout!' if c == 0 } expect(counter.count).to eq(2) expect(Kernel).to receive(:sleep).once.with(1) counter(matching: [/IO timeout/, 'IO tymeout']) { |c, _e| raise 'yo, IO tymeout!' if c == 0 } expect(counter.count).to eq(4) end it 'does not allow invalid type of matching option' do expect do described_class.retryable(matching: 1) { raise 'this is invaid type of matching iotion' } end.to raise_error ArgumentError, ':matching must be a string or regex' end it 'does not allow invalid options' do expect do described_class.retryable(bad_option: 2) { raise 'this is bad' } end.to raise_error ArgumentError, '[Retryable] Invalid options: bad_option' end # rubocop:disable Rspec/InstanceVariable it 'accepts a callback to run after an exception is rescued' do expect do described_class.retryable(sleep: 0, exception_cb: proc { |e| @raised = e.to_s }) do |tries| raise StandardError, 'this is fun!' if tries < 1 end end.not_to raise_error expect(@raised).to eq('this is fun!') end # rubocop:enable Rspec/InstanceVariable it 'does not retry on :not exception' do expect do counter(not: RuntimeError) { |tries| raise RuntimeError if tries < 1 } end.to raise_error RuntimeError expect(counter.count).to eq(1) end it 'gives precidence for :not over :on' do expect do counter(sleep: 0, tries: 3, on: StandardError, not: IndexError) do |tries| raise tries >= 1 ? IndexError : StandardError end end.to raise_error IndexError expect(counter.count).to eq(2) end end end retryable-3.0.5/spec/retryable/0000755000004100000410000000000013641337156016474 5ustar www-datawww-dataretryable-3.0.5/spec/retryable/configuration_spec.rb0000644000004100000410000000252013641337156022701 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe Retryable do it 'is enabled by default' do expect(described_class).to be_enabled end it 'could be disabled' do described_class.disable expect(described_class).not_to be_enabled end context 'when disabled' do before do described_class.disable end it 'could be re-enabled' do described_class.enable expect(described_class).to be_enabled end end context 'when configured locally' do it 'does not affect the original global config' do new_sleep = 2 original_sleep = described_class.configuration.send(:sleep) expect(original_sleep).not_to eq(new_sleep) counter(tries: 2, sleep: new_sleep) do |tries, ex| raise StandardError if tries < 1 end actual = described_class.configuration.send(:sleep) expect(actual).to eq(original_sleep) 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) described_class.configure do |config| config.sleep = 3 end counter(tries: 2) do |tries, ex| expect(ex.class).to eq(StandardError) if tries > 0 raise StandardError if tries < 1 end expect(counter.count).to eq(2) end end end retryable-3.0.5/spec/retryable/version_spec.rb0000644000004100000410000000164113641337156021522 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe Retryable::Version do before do allow(described_class).to receive(:major).and_return(2) allow(described_class).to receive(:minor).and_return(0) allow(described_class).to receive(:patch).and_return(4) end describe '.to_h' do it 'returns a hash with the right values' do expect(described_class.to_h).to be_a Hash expect(described_class.to_h[:major]).to eq(2) expect(described_class.to_h[:minor]).to eq(0) expect(described_class.to_h[:patch]).to eq(4) end end describe '.to_a' do it 'returns an array with the right values' do expect(described_class.to_a).to be_an Array expect(described_class.to_a).to eq([2, 0, 4]) end end describe '.to_s' do it 'returns a string with the right value' do expect(described_class.to_s).to be_a String expect(described_class.to_s).to eq('2.0.4') end end end retryable-3.0.5/spec/spec_helper.rb0000644000004100000410000000057113641337156017324 0ustar www-datawww-datarequire 'retryable' require 'simplecov' # rubocop:disable Style/ExpandPathArguments Dir.glob(File.expand_path('../support/**/*.rb', __FILE__), &method(:require)) # rubocop:enable Style/ExpandPathArguments SimpleCov.start RSpec.configure do |config| config.disable_monkey_patching! config.include(Counter) config.before do Retryable.configuration = nil end end retryable-3.0.5/spec/support/0000755000004100000410000000000013641337156016217 5ustar www-datawww-dataretryable-3.0.5/spec/support/counter.rb0000644000004100000410000000211513641337156020222 0ustar www-datawww-datamodule Counter class PlainGenerator attr_reader :count def initialize(options) @options = options @count = 0 end def around Retryable.retryable(@options) do |*arguments| increment yield(*arguments) end end private def increment @count += 1 end end class GeneratorWithContext attr_reader :count def initialize(context_key, options) @context_key = context_key @count = 0 @options = options end def around Retryable.with_context(@context_key, @options) do |*arguments| increment yield(*arguments) end end private def increment @count += 1 end end def counter(options = {}, &block) @counter ||= PlainGenerator.new(options) @counter.around(&block) if block_given? @counter end def counter_with_context(context_key, options = {}, &block) @counter_with_context ||= GeneratorWithContext.new(context_key, options) @counter_with_context.around(&block) if block_given? @counter_with_context end end retryable-3.0.5/CHANGELOG.md0000644000004100000410000000635013641337156015366 0ustar www-datawww-data## Retryable 3.0.5 ## Instead of :infinite magic constant from now on you can just use Ruby's native infinity data type e.g. Float::INFINITY. See https://github.com/nfedyashev/retryable/commit/16f60bb09560c9470266dca8cd47c934594a67c5 This version is backwards compatible with older versions, no changes needed in your code. ## Retryable 3.0.4 ## Fixed typo in exception message given invalid :matching argument type https://github.com/nfedyashev/retryable/pull/29 Thanks @msroz ## Retryable 3.0.3 ## No changes to the source code, only added direct Changelog link on rubygems.org for ease of use. ## Retryable 3.0.2 ## * :log_method param has been added for flexible logging of your retries. It is silent by default. ## Retryable 3.0.1 ## * :matching param from now on could be called in form of array with multiple matching conditions. This version is backwards compatible with 3.0.0 ## Retryable 3.0.0 ## NOTE: this version is backwards compatible with 2.0.4 version unless you're running it against Ruby 1.8 version. * retryable can now also be configured via stored contexts. * Ruby 1.8 support has been dropped. Thanks @chubchenko for refactoring and various improvements. ## 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. Thanks @alexcastano ## 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-3.0.5/Rakefile0000644000004100000410000000030513641337156015214 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-3.0.5/lib/0000755000004100000410000000000013641337156014317 5ustar www-datawww-dataretryable-3.0.5/lib/retryable.rb0000644000004100000410000000767513641337156016654 0ustar www-datawww-datarequire 'retryable/version' require 'retryable/configuration' require 'forwardable' # Runs a code block, and retries it when an exception occurs. It's great when working with flakey webservices (for example). module Retryable class << self extend Forwardable # 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.contexts = {} # config.ensure = proc {} # config.exception_cb = proc {} # config.log_method = proc {} # config.matching = /.*/ # config.not = [] # config.on = StandardError # config.sleep = 1 # config.sleep_method = ->(seconds) { Kernel.sleep(seconds) } # config.tries = 2 # end def configure yield(configuration) end # The configuration object. # @see Retryable.configure def configuration @configuration ||= Configuration.new end delegate [:enabled?, :enable, :disable] => :configuration def with_context(context_key, options = {}, &block) unless configuration.contexts.key?(context_key) raise ArgumentError, "#{context_key} not found in Retryable.configuration.contexts. Available contexts: #{configuration.contexts.keys}" end retryable(configuration.contexts[context_key].merge(options), &block) if block end alias retryable_with_context with_context # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/PerceivedComplexity def retryable(options = {}) opts = configuration.to_hash check_for_invalid_options(options, opts) opts.merge!(options) # rubocop:disable Style/NumericPredicate return if opts[:tries] == 0 # rubocop:enable Style/NumericPredicate on_exception = opts[:on].is_a?(Array) ? opts[:on] : [opts[:on]] not_exception = opts[:not].is_a?(Array) ? opts[:not] : [opts[:not]] matching = opts[:matching].is_a?(Array) ? opts[:matching] : [opts[:matching]] tries = opts[:tries] retries = 0 retry_exception = nil begin opts[:log_method].call(retries, retry_exception) if retries > 0 return yield retries, retry_exception rescue *not_exception raise rescue *on_exception => exception raise unless configuration.enabled? raise unless matches?(exception.message, matching) infinite_retries = :infinite || tries.respond_to?(:infinite?) && tries.infinite? raise if tries != infinite_retries && 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 # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity private def check_for_invalid_options(custom_options, default_options) invalid_options = default_options.merge(custom_options).keys - default_options.keys return if invalid_options.empty? raise ArgumentError, "[Retryable] Invalid options: #{invalid_options.join(', ')}" end def matches?(message, candidates) candidates.any? do |candidate| case candidate when String message.include?(candidate) when Regexp message =~ candidate else raise ArgumentError, ':matching must be a string or regex' end end end end end retryable-3.0.5/lib/retryable/0000755000004100000410000000000013641337156016310 5ustar www-datawww-dataretryable-3.0.5/lib/retryable/version.rb0000644000004100000410000000105313641337156020321 0ustar www-datawww-datamodule Retryable # This module holds the Retryable version information. module Version module_function # @return [Integer] def major 3 end # @return [Integer] def minor 0 end # @return [Integer] def patch 5 end # @return [Hash] def to_h { major: major, minor: minor, patch: patch } end # @return [Hash] def to_a [major, minor, patch].compact end # @return [String] def to_s to_a.join('.') end end end retryable-3.0.5/lib/retryable/configuration.rb0000644000004100000410000000264613641337156021514 0ustar www-datawww-datamodule Retryable # Used to set up and modify settings for the retryable. class Configuration VALID_OPTION_KEYS = [ :contexts, :ensure, :exception_cb, :log_method, :matching, :not, :on, :sleep, :sleep_method, :tries ].freeze attr_accessor(*VALID_OPTION_KEYS) attr_accessor :enabled def initialize @contexts = {} @ensure = proc {} @exception_cb = proc {} @log_method = proc {} @matching = /.*/ @not = [] @on = StandardError @sleep = 1 @sleep_method = ->(seconds) { Kernel.sleep(seconds) } @tries = 2 @enabled = true end def enable @enabled = true end alias enabled? enabled 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 VALID_OPTION_KEYS.each_with_object({}) do |key, memo| memo[key] = instance_variable_get("@#{key}") 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-3.0.5/retryable.gemspec0000644000004100000410000000221213641337156017104 0ustar www-datawww-data# rubocop:disable Style/ExpandPathArguments lib = File.expand_path('../lib', __FILE__) # rubocop:enable Style/ExpandPathArguments $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'retryable/version' Gem::Specification.new do |spec| spec.name = 'retryable' spec.version = Retryable::Version spec.authors = ['Nikita Fedyashev', 'Carlo Zottmann', 'Chu Yeow'] spec.email = ['nfedyashev@gmail.com'] spec.summary = 'Retrying code blocks in Ruby' spec.description = spec.summary spec.homepage = 'http://github.com/nfedyashev/retryable' spec.metadata = { 'changelog_uri' => 'https://github.com/nfedyashev/retryable/blob/master/CHANGELOG.md', 'source_code_uri' => 'https://github.com/nfedyashev/retryable/tree/master' } spec.licenses = ['MIT'] spec.require_paths = ['lib'] spec.files = Dir['{config,lib,spec}/**/*', '*.md', '*.gemspec', 'Gemfile', 'Rakefile'] spec.test_files = spec.files.grep(%r{^spec/}) spec.required_ruby_version = Gem::Requirement.new('>= 1.9.3') spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') spec.add_development_dependency 'bundler' end retryable-3.0.5/Gemfile0000644000004100000410000000046713641337156015053 0ustar www-datawww-datasource 'http://rubygems.org' gem 'rake', '~> 10.4' gem 'yard' group :development do gem 'fasterer' gem 'overcommit' gem 'pry', '= 0.9.12.6' gem 'rubocop' gem 'rubocop-rspec' end group :test do gem 'codeclimate-test-reporter' gem 'rspec', '~> 3.1' gem 'simplecov', require: false end gemspec