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 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
@@ -1,3 +1,3 @@
1
1
  module RetryBlock
2
- VERSION = "1.0.2"
2
+ VERSION = "1.1.0"
3
3
  end
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 (attempts += 1) > opts[:attempts]
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
@@ -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
- - 2
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