forked from nfedyashev/retryable
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0f8b016
commit 3dbe02d
Showing
6 changed files
with
184 additions
and
151 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,135 +1,93 @@ | ||
require 'retryable/version' | ||
require 'retryable/config' | ||
require 'retryable/configuration' | ||
|
||
module 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 | ||
|
||
#alias_method :test_mode?, :test_mode | ||
|
||
def initialize | ||
@ensure = Proc.new {} | ||
@exception_cb = Proc.new {} | ||
@matching = /.*/ | ||
@on = StandardError | ||
@sleep = 1 | ||
@tries = 2 | ||
end | ||
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 | ||
|
||
# Allows config options to be read like a hash | ||
# Call this method to modify defaults in your initializers. | ||
# | ||
# @param [Symbol] option Key for a given attribute | ||
def [](option) | ||
send(option) | ||
# @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 | ||
|
||
# Returns a hash of all configurable options | ||
def to_hash | ||
OPTIONS.inject({}) do |hash, option| | ||
hash[option.to_sym] = self.send(option) | ||
hash | ||
end | ||
# The configuration object. | ||
# @see Retryable.configure | ||
def configuration | ||
@configuration ||= Configuration.new | ||
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) | ||
def enabled? | ||
configuration.enabled? | ||
end | ||
end | ||
end | ||
|
||
module Retryable | ||
# 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 self.configure | ||
yield(configuration) | ||
end | ||
def enable | ||
configuration.enable | ||
end | ||
|
||
# The configuration object. | ||
# @see Retryable.configure | ||
def self.configuration | ||
@configuration ||= Configuration.new | ||
end | ||
def disable | ||
configuration.disable | ||
end | ||
|
||
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 | ||
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 | ||
|
||
def self.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 Retryable.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 | ||
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 | ||
|
||
retries += 1 | ||
retry_exception = exception | ||
opts[:exception_cb].call(retry_exception) | ||
retry | ||
ensure | ||
opts[:ensure].call(retries) | ||
end | ||
end | ||
|
||
private | ||
private | ||
|
||
def self.check_for_invalid_options(custom_options, default_options) | ||
invalid_options = default_options.merge(custom_options).keys - default_options.keys | ||
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? | ||
raise ArgumentError.new("[Retryable] Invalid options: #{invalid_options.join(", ")}") unless invalid_options.empty? | ||
end | ||
end | ||
end | ||
|
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
module 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 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
require '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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters