attempt_this 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/attempt_this/attempt_object.rb +112 -0
- data/lib/attempt_this/binary_backoff_policy.rb +15 -0
- data/lib/attempt_this/exception_type_filter.rb +14 -0
- data/lib/attempt_this.rb +11 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a25ae3dbdf326fbbfd6ac7f4f87576495d4aa2ff
|
4
|
+
data.tar.gz: 570ce5129322b14d1b0c61a463daeb6b3c335962
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e97953cc5b47eedbe6f6398ec1e1ad528aadf2cd36829dc8e5533c80846b0344fd1f5efc95a7bf4fc8481b866175f1a732dd1a4de88d57ddfa66f44f2cc96f68
|
7
|
+
data.tar.gz: 135cd4ad6336d9c63b9a6d58c3858706dd5445d73cd882e9e94ea6ea0dbbbb9db40df5a1dabfd66fea82b480c8ec9ca565fefcfd8bd4b7506601917879851c98
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require_relative 'binary_backoff_policy.rb'
|
2
|
+
require_relative 'exception_type_filter.rb'
|
3
|
+
|
4
|
+
module AttemptThis
|
5
|
+
# Retry policy implementation.
|
6
|
+
# This class is internal and is not supposed to be used outside of the module.
|
7
|
+
class AttemptObject
|
8
|
+
# Initializes object with enumerator.
|
9
|
+
def initialize(enumerator)
|
10
|
+
@enumerator = enumerator
|
11
|
+
end
|
12
|
+
|
13
|
+
# Executes the code block.
|
14
|
+
def attempt(block)
|
15
|
+
if (block)
|
16
|
+
last_exception = nil
|
17
|
+
first_time = true
|
18
|
+
|
19
|
+
@delay_policy = ->{} unless @delay_policy
|
20
|
+
@reset_method = ->{} unless @reset_method
|
21
|
+
@exception_filter = ExceptionTypeFilter.new([StandardError]) unless @exception_filter
|
22
|
+
|
23
|
+
@enumerator.each do
|
24
|
+
@delay_policy.call unless first_time
|
25
|
+
last_exception = nil
|
26
|
+
begin
|
27
|
+
block.call
|
28
|
+
break
|
29
|
+
rescue => ex
|
30
|
+
raise unless @exception_filter.include?(ex)
|
31
|
+
last_exception = ex
|
32
|
+
@reset_method.call
|
33
|
+
end
|
34
|
+
first_time = false
|
35
|
+
end
|
36
|
+
|
37
|
+
# Re-raise the last exception
|
38
|
+
if (last_exception)
|
39
|
+
if (@default_method)
|
40
|
+
@default_method.call
|
41
|
+
else
|
42
|
+
raise last_exception
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return self to allow chaining calls.
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Specifies delay in seconds between failed attempts.
|
52
|
+
def with_delay(delay, &block)
|
53
|
+
# Delay should be either an integer or a range of integers.
|
54
|
+
if (delay.is_a?(Numeric))
|
55
|
+
raise(ArgumentError, "Delay should be a non-negative number; got #{delay}!") unless delay >= 0
|
56
|
+
delay = delay..delay
|
57
|
+
elsif delay.is_a?(Range)
|
58
|
+
raise(ArgumentError, "Range members should be numbers; got #{delay}!") unless delay.first.is_a?(Numeric) && delay.last.is_a?(Numeric)
|
59
|
+
raise(ArgumentError, "Range members should be non-negative; got #{delay}!") unless delay.first >= 0 && delay.last >= 0
|
60
|
+
raise(ArgumentError, "Range's end should be greater than or equal to range's start; got #{delay}!") unless delay.first <= delay.last
|
61
|
+
else
|
62
|
+
raise(ArgumentError, "Delay should be either an number or a range of numbers; got #{delay}!")
|
63
|
+
end
|
64
|
+
raise(ArgumentError, 'Delay policy has already been specified!') if @delay_policy
|
65
|
+
@delay_policy = ->{Kernel.sleep(rand(delay))}
|
66
|
+
|
67
|
+
attempt(block)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Specifies reset method that will be called after each failed attempt.
|
71
|
+
def with_reset(reset_method, &block)
|
72
|
+
raise(ArgumentError, 'Reset method is nil!') unless reset_method
|
73
|
+
raise(ArgumentError, 'Reset method has already been speicifed!') if @reset_method
|
74
|
+
|
75
|
+
@reset_method = reset_method
|
76
|
+
attempt(block)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Specifies default method that should be called after all attempts have failed.
|
80
|
+
def and_default_to(default_method, &block)
|
81
|
+
raise(ArgumentError, 'Default method is nil!') unless default_method
|
82
|
+
raise(ArgumentError, 'Default method has already been specified!') if @default_method
|
83
|
+
|
84
|
+
@default_method = default_method
|
85
|
+
attempt(block)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Specifies delay which doubles between failed attempts.
|
89
|
+
def with_binary_backoff(initial_delay, &block)
|
90
|
+
raise(ArgumentError, "Delay should be a number; got ${initial_delay}!") unless initial_delay.is_a?(Numeric)
|
91
|
+
raise(ArgumentError, "Delay should be a positive number; got #{initial_delay}!") unless initial_delay > 0
|
92
|
+
raise(ArgumentError, "Delay policy has already been specified!") if @delay_policy
|
93
|
+
|
94
|
+
@delay_policy = BinaryBackoffPolicy.new(initial_delay)
|
95
|
+
attempt(block)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Specifies exceptions
|
99
|
+
def with_filter(*exceptions, &block)
|
100
|
+
raise(ArgumentError, "Empty exceptions list!") unless exceptions.size > 0
|
101
|
+
# Everything must be an exception.
|
102
|
+
exceptions.each do |e|
|
103
|
+
raise(ArgumentError, "Not an exception: #{e}!") unless e <= Exception
|
104
|
+
end
|
105
|
+
|
106
|
+
raise(ArgumentError, "Exception filter has already been specified!") if @exception_filter
|
107
|
+
|
108
|
+
@exception_filter = ExceptionTypeFilter.new(exceptions)
|
109
|
+
attempt(block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module AttemptThis
|
2
|
+
# Implementation of binary backoff policy. Internal use only.
|
3
|
+
class BinaryBackoffPolicy
|
4
|
+
# Initializer.
|
5
|
+
def initialize(initial_delay)
|
6
|
+
@delay = initial_delay
|
7
|
+
end
|
8
|
+
|
9
|
+
# Calls the policy.
|
10
|
+
def call
|
11
|
+
Kernel.sleep(@delay)
|
12
|
+
@delay *= 2
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module AttemptThis
|
2
|
+
# Type-based exception filter.
|
3
|
+
class ExceptionTypeFilter
|
4
|
+
# Initializer.
|
5
|
+
def initialize(exception_classes)
|
6
|
+
@exception_classes = Array.new(exception_classes)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Tells whether the given exception satisfies the filter.
|
10
|
+
def include?(exception)
|
11
|
+
@exception_classes.any?{|klass| exception.is_a?(klass)}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/attempt_this.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'attempt_this/attempt_object.rb'
|
2
|
+
|
3
|
+
module AttemptThis
|
4
|
+
# Attempts code block until it doesn't throw an exception or the end of enumerator has been reached.
|
5
|
+
def attempt(enumerator, &block)
|
6
|
+
raise(ArgumentError, 'Nil enumerator!') if enumerator.nil?
|
7
|
+
|
8
|
+
impl = AttemptObject.new(enumerator)
|
9
|
+
impl.attempt(block)
|
10
|
+
end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attempt_this
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aliaksei Baturytski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-05-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Retry on exception with configurable number of times, delays, exception
|
14
|
+
filters, and fall back strategies
|
15
|
+
email: abaturytski@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/attempt_this.rb
|
21
|
+
- lib/attempt_this/attempt_object.rb
|
22
|
+
- lib/attempt_this/binary_backoff_policy.rb
|
23
|
+
- lib/attempt_this/exception_type_filter.rb
|
24
|
+
homepage: https://github.com/aliakb/attempt_this
|
25
|
+
licenses: []
|
26
|
+
metadata: {}
|
27
|
+
post_install_message:
|
28
|
+
rdoc_options: []
|
29
|
+
require_paths:
|
30
|
+
- lib
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - '>='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 2.0.0
|
44
|
+
signing_key:
|
45
|
+
specification_version: 4
|
46
|
+
summary: Retry policy mix-in
|
47
|
+
test_files: []
|