retry_block 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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