retry_block 1.0.2 → 1.1.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.
- data/README.rdoc +183 -0
- data/lib/retry_block/version.rb +1 -1
- data/lib/retry_block.rb +3 -1
- data/test/test_retry_block.rb +31 -0
- metadata +3 -2
data/README.rdoc
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
== README: retry_block
|
|
2
|
+
|
|
3
|
+
<i>Take control of unstable or indeterminate code with retry_block.</i>
|
|
4
|
+
|
|
5
|
+
The retry_block method allows you to easily retry a block of code a given
|
|
6
|
+
number of times or until successful. You may specify a failure callback to
|
|
7
|
+
be called each time the block of code fails.
|
|
8
|
+
|
|
9
|
+
=== A Quick Example
|
|
10
|
+
|
|
11
|
+
Sometimes examples are more useful than words:
|
|
12
|
+
|
|
13
|
+
require 'retry_block'
|
|
14
|
+
|
|
15
|
+
fail_callback = lambda do |attempt, exception|
|
|
16
|
+
puts "attempt ##{attempt} failed. Sleeping 1 second"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
retry_block(:fail_callback => fail_callback, :sleep => 1) do |attempt|
|
|
20
|
+
raise "Never see this exception" if attempt <= 5
|
|
21
|
+
puts "Success!"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
In the above code, an exception is raised for the first 5 tries of the
|
|
25
|
+
block of code given to retry_block. Upon each failure the fail_callback
|
|
26
|
+
lambda is called, printing out a message warning the user that the block
|
|
27
|
+
failed. Also upon each failure retry_block sleeps for one second. On the
|
|
28
|
+
last attempt the block succeeds and exits. Here is the output from running
|
|
29
|
+
the above code:
|
|
30
|
+
|
|
31
|
+
attempt #1 failed. Sleeping 1 second
|
|
32
|
+
attempt #2 failed. Sleeping 1 second
|
|
33
|
+
attempt #3 failed. Sleeping 1 second
|
|
34
|
+
attempt #4 failed. Sleeping 1 second
|
|
35
|
+
attempt #5 failed. Sleeping 1 second
|
|
36
|
+
Success!
|
|
37
|
+
|
|
38
|
+
=== Maximum Attempts
|
|
39
|
+
|
|
40
|
+
Notice that by default retry_block will retry a block of code until it
|
|
41
|
+
succeed. If you want to apply a maximum amount of times to retry, pass
|
|
42
|
+
that number to the <tt>:attempts</tt> option. Here is an example where a
|
|
43
|
+
block of code is run 3 times and finally fails:
|
|
44
|
+
|
|
45
|
+
require 'retry_block'
|
|
46
|
+
|
|
47
|
+
retry_block(:attempts => 3) do
|
|
48
|
+
puts "trying"
|
|
49
|
+
raise RuntimeError, "I Failed!"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Here is the output of the above code:
|
|
53
|
+
|
|
54
|
+
trying
|
|
55
|
+
trying
|
|
56
|
+
trying
|
|
57
|
+
I Failed! (RuntimeError)
|
|
58
|
+
|
|
59
|
+
Notice that if the block of code fails the maximum number of times,
|
|
60
|
+
retry_block will raise the exception to the calling code.
|
|
61
|
+
|
|
62
|
+
=== Catching Specific Exceptions
|
|
63
|
+
|
|
64
|
+
It is possible to tell retry_block which exceptions to catch. Pass either
|
|
65
|
+
an exception or a list of exceptions to the <tt>:catch</tt> option. By
|
|
66
|
+
default retry_block watches for <tt>Exception</tt>. If the block of code
|
|
67
|
+
raises an exception that is not watched by retry_block, the exception will
|
|
68
|
+
be raised to the calling code. Note that the :fail_callback callback will
|
|
69
|
+
NOT be called when an unexpected exception occurs. Here is an example to
|
|
70
|
+
demonstrate:
|
|
71
|
+
|
|
72
|
+
require 'retry_block'
|
|
73
|
+
|
|
74
|
+
class UnexpectedError < Exception; end
|
|
75
|
+
|
|
76
|
+
# Run forever until success or an unexpected exception occurs.
|
|
77
|
+
retry_block(:catch => [RuntimeError, ArgumentError]) do |attempts|
|
|
78
|
+
puts "retrying"
|
|
79
|
+
raise UnexpectedError if attempts == 10
|
|
80
|
+
raise RuntimeError if (attempts % 2) == 0
|
|
81
|
+
raise ArgumentError if (attempts % 2) == 1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
The output of the above code is:
|
|
85
|
+
|
|
86
|
+
retrying
|
|
87
|
+
retrying
|
|
88
|
+
retrying
|
|
89
|
+
retrying
|
|
90
|
+
retrying
|
|
91
|
+
retrying
|
|
92
|
+
retrying
|
|
93
|
+
retrying
|
|
94
|
+
retrying
|
|
95
|
+
retrying
|
|
96
|
+
UnexpectedError (UnexpectedError)
|
|
97
|
+
|
|
98
|
+
Notice that in the code above, retry_block gracefully handles RuntimeError
|
|
99
|
+
and ArgumentError because these have been passed to the <tt>:catch</tt>
|
|
100
|
+
option. But when <tt>UnexpectedError</tt> occurs, the exception is raised
|
|
101
|
+
to the calling code.
|
|
102
|
+
|
|
103
|
+
=== Sleeping Between Attempts
|
|
104
|
+
|
|
105
|
+
Finally, retry_block has a <tt>:sleep</tt> option. Use this when you
|
|
106
|
+
require that there is a pause between retries of the block of code. This
|
|
107
|
+
is often handy, for instance, when relying on external events (network IO,
|
|
108
|
+
user IO, etc.)
|
|
109
|
+
|
|
110
|
+
require 'retry_block'
|
|
111
|
+
|
|
112
|
+
callback = lambda do puts "sleeping 1 second" end
|
|
113
|
+
|
|
114
|
+
retry_block(:attempts => 3, :sleep => 1, :fail_callback => callback) do |attempts|
|
|
115
|
+
raise unless attempts == 3
|
|
116
|
+
puts "finished"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
The output of the above code is:
|
|
120
|
+
|
|
121
|
+
sleeping 1 second
|
|
122
|
+
sleeping 1 second
|
|
123
|
+
finished
|
|
124
|
+
|
|
125
|
+
In the above code, we see that we ask retry_block to retry a total of 3
|
|
126
|
+
times. The first two tries fail, resulting in sleeping 1 second each. The
|
|
127
|
+
last attempt is successful.
|
|
128
|
+
|
|
129
|
+
=== Block Failure Callback
|
|
130
|
+
|
|
131
|
+
It is possible to provide a callback Proc or lambda to the
|
|
132
|
+
<tt>:fail_callback</tt> option. Anytime that an exception occurs in your
|
|
133
|
+
code that is handled by the <tt>:catch</tt> option (which is all exceptions
|
|
134
|
+
by default), the callback provided will be called. Your callback can
|
|
135
|
+
optionally be called with the current attempt number and the exception
|
|
136
|
+
caught. Here is an example:
|
|
137
|
+
|
|
138
|
+
require 'retry_block'
|
|
139
|
+
|
|
140
|
+
callback = lambda do |attempt, exception|
|
|
141
|
+
puts "Failure ##{attempt} with message: #{exception.message}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
retry_block(:fail_callback => callback) do |attempt|
|
|
145
|
+
raise "Foo" if attempt == 1
|
|
146
|
+
raise "Bar" if attempt == 2
|
|
147
|
+
raise "Baz" if attempt == 3
|
|
148
|
+
puts "Finished"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
The output of the above code is:
|
|
152
|
+
|
|
153
|
+
Failure #1 with message: Foo
|
|
154
|
+
Failure #1 with message: Bar
|
|
155
|
+
Failure #1 with message: Baz
|
|
156
|
+
Finished
|
|
157
|
+
|
|
158
|
+
This can be useful if, for instance, you would like to sleep for
|
|
159
|
+
exponentially longer periods of time after each attempt:
|
|
160
|
+
|
|
161
|
+
require 'retry_block'
|
|
162
|
+
|
|
163
|
+
callback = lambda do |attempt|
|
|
164
|
+
# Sleep longer and longer, maxing out at 16
|
|
165
|
+
sleep_time = [16, 2**(attempt-1)].min
|
|
166
|
+
puts "Failed again! Sleeping #{sleep_time} seconds ..."
|
|
167
|
+
sleep sleep_time
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
retry_block(:fail_callback => callback) do |attempt|
|
|
171
|
+
raise if attempt <= 6
|
|
172
|
+
puts "Finished"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
The output of the above code is:
|
|
176
|
+
|
|
177
|
+
Failed again! Sleeping 1 seconds ...
|
|
178
|
+
Failed again! Sleeping 2 seconds ...
|
|
179
|
+
Failed again! Sleeping 4 seconds ...
|
|
180
|
+
Failed again! Sleeping 8 seconds ...
|
|
181
|
+
Failed again! Sleeping 16 seconds ...
|
|
182
|
+
Failed again! Sleeping 16 seconds ...
|
|
183
|
+
Finished
|
data/lib/retry_block/version.rb
CHANGED
data/lib/retry_block.rb
CHANGED
|
@@ -26,8 +26,10 @@ module Kernel
|
|
|
26
26
|
opts[:fail_callback].call *callback_opts
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
attempts += 1
|
|
30
|
+
|
|
29
31
|
# If we've maxed out our attempts, raise the exception to the calling code
|
|
30
|
-
raise if (not opts[:attempts].nil?) and
|
|
32
|
+
raise if (not opts[:attempts].nil?) and attempts > opts[:attempts]
|
|
31
33
|
|
|
32
34
|
# Sleep before the next retry if the option was given
|
|
33
35
|
sleep opts[:sleep] if opts[:sleep].is_a? Numeric and opts[:sleep] > 0
|
data/test/test_retry_block.rb
CHANGED
|
@@ -168,6 +168,37 @@ class TestRetryBlock < Test::Unit::TestCase
|
|
|
168
168
|
assert_equal 100, @run_count
|
|
169
169
|
assert_equal 99, @fail_count
|
|
170
170
|
end
|
|
171
|
+
|
|
172
|
+
def test_attempts_passed_to_fail_callback
|
|
173
|
+
test_counter = 0
|
|
174
|
+
test_callback = lambda do |attempt, exception|
|
|
175
|
+
test_counter += 1
|
|
176
|
+
assert_equal test_counter, attempt
|
|
177
|
+
end
|
|
178
|
+
retry_block(:attempts => 3, :fail_callback => test_callback) do
|
|
179
|
+
@run_count += 1
|
|
180
|
+
raise TestException unless @run_count == 3
|
|
181
|
+
end
|
|
182
|
+
assert_equal 3, @run_count
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def test_attempts_passed_to_block
|
|
186
|
+
retry_block(:attempts => 3) do |attempts|
|
|
187
|
+
@run_count += 1
|
|
188
|
+
assert_equal @run_count, attempts
|
|
189
|
+
raise TestException unless @run_count == 3
|
|
190
|
+
end
|
|
191
|
+
assert_equal 3, @run_count
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_attempts_incremented_when_run_forever
|
|
195
|
+
retry_block() do |attempts|
|
|
196
|
+
@run_count += 1
|
|
197
|
+
assert_equal @run_count, attempts
|
|
198
|
+
raise TestException unless @run_count == 10
|
|
199
|
+
end
|
|
200
|
+
assert_equal 10, @run_count
|
|
201
|
+
end
|
|
171
202
|
end
|
|
172
203
|
|
|
173
204
|
class TestException < Exception
|
metadata
CHANGED
|
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
|
|
|
5
5
|
prerelease:
|
|
6
6
|
segments:
|
|
7
7
|
- 1
|
|
8
|
+
- 1
|
|
8
9
|
- 0
|
|
9
|
-
|
|
10
|
-
version: 1.0.2
|
|
10
|
+
version: 1.1.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Alfred J. Fazio
|
|
@@ -30,6 +30,7 @@ extra_rdoc_files: []
|
|
|
30
30
|
files:
|
|
31
31
|
- .gitignore
|
|
32
32
|
- Gemfile
|
|
33
|
+
- README.rdoc
|
|
33
34
|
- Rakefile
|
|
34
35
|
- lib/retry_block.rb
|
|
35
36
|
- lib/retry_block/version.rb
|