retryable 2.0.4 → 3.0.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/CHANGELOG.md +11 -1
- data/Gemfile +25 -0
- data/README.md +97 -60
- data/Rakefile +2 -2
- data/lib/retryable.rb +20 -18
- data/lib/retryable/configuration.rb +14 -21
- data/lib/retryable/version.rb +33 -9
- data/retryable.gemspec +22 -18
- data/spec/{lib → retryable}/configuration_spec.rb +9 -9
- data/spec/retryable/version_spec.rb +32 -0
- data/spec/retryable_spec.rb +152 -0
- data/spec/retryable_with_context_spec.rb +56 -0
- data/spec/spec_helper.rb +8 -17
- data/spec/support/counter.rb +58 -0
- metadata +49 -65
- data/spec/lib/retryable_spec.rb +0 -151
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d2cf59f84731d2def08398714a4366b4b92d15eb
|
4
|
+
data.tar.gz: 391c2797ed522cc3a95c2d767335eb3bbb50a105
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7c5df44eb01cc472a8e4f1dc41469b02af8f062fa11290f4cf00481621cdd0e66e5caf93eeef46a2504d9e9fe858c3905d4d1ca479676785b99590668c4dc848
|
7
|
+
data.tar.gz: 3c60ad3b26e1af54e5c411d525d3edc271cfb2bf5302f67d2bdc043af649f519cc31d97c1374cdee188d1db7b83eef3db85a4e4b76bafe383b53c3d667240990
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,18 @@
|
|
1
|
+
## Retryable 3.0.0 ##
|
2
|
+
NOTE: this version is backwards compatible with 2.0.4 version unless you're running it against Ruby 1.8 version.
|
3
|
+
|
4
|
+
* retryable can now also be configured via stored contexts.
|
5
|
+
* Ruby 1.8 support has been dropped.
|
6
|
+
|
7
|
+
Thanks @chubchenko for refactoring and various improvements.
|
8
|
+
|
1
9
|
## Retryable 2.0.4 ##
|
2
10
|
|
3
11
|
* :infinite value is now available as :tries paramater. Use it for retrying your blocks infinitely until it stops failing.
|
4
12
|
* :sleep_method parameter has been added. This can be very useful when you are working with Celluloid which implements its own version of the method sleep.
|
5
|
-
Use `:sleep_method
|
13
|
+
Use `:sleep_method: Celluloid.method(:sleep)` in such cases.
|
14
|
+
|
15
|
+
Thanks @alexcastano
|
6
16
|
|
7
17
|
## Retryable 2.0.3 ##
|
8
18
|
|
data/Gemfile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
gem 'rake', '~> 10.4'
|
4
|
+
gem 'yard'
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
gem 'fasterer'
|
8
|
+
gem 'overcommit'
|
9
|
+
gem 'pry', '= 0.9.12.6'
|
10
|
+
gem 'rubocop'
|
11
|
+
gem 'rubocop-rspec'
|
12
|
+
end
|
13
|
+
|
14
|
+
group :test do
|
15
|
+
gem 'codeclimate-test-reporter'
|
16
|
+
gem 'rspec', '~> 3.1'
|
17
|
+
gem 'simplecov', require: false
|
18
|
+
end
|
19
|
+
|
20
|
+
platforms :rbx do
|
21
|
+
gem 'racc'
|
22
|
+
gem 'rubysl', '~> 2.0'
|
23
|
+
end
|
24
|
+
|
25
|
+
gemspec
|
data/README.md
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
-
|
2
|
-
=====
|
1
|
+
# Retryable
|
3
2
|
|
3
|
+
[](https://badge.fury.io/rb/retryable)
|
4
4
|
[](https://travis-ci.org/nfedyashev/retryable)
|
5
5
|
[](https://www.versioneye.com/ruby/retryable)
|
6
|
+
[](https://codeclimate.com/github/nfedyashev/retryable)
|
7
|
+
[](https://codeclimate.com/github/nfedyashev/retryable/coverage)
|
8
|
+
|
9
|
+
[](http://inch-ci.org/github/nfedyashev/retryable)
|
6
10
|
|
7
11
|
Description
|
8
12
|
--------
|
@@ -16,6 +20,21 @@ runs the passed block. Should an exception occur, it'll retry for (n-1) times.
|
|
16
20
|
Should the number of retries be reached without success, the last exception
|
17
21
|
will be raised.
|
18
22
|
|
23
|
+
Installation
|
24
|
+
-------
|
25
|
+
|
26
|
+
Install the gem:
|
27
|
+
|
28
|
+
``` bash
|
29
|
+
$ gem install retryable
|
30
|
+
```
|
31
|
+
|
32
|
+
Add it to your Gemfile:
|
33
|
+
|
34
|
+
``` ruby
|
35
|
+
gem 'retryable'
|
36
|
+
```
|
37
|
+
|
19
38
|
|
20
39
|
Examples
|
21
40
|
--------
|
@@ -25,24 +44,24 @@ Open an URL, retry up to two times when an `OpenURI::HTTPError` occurs.
|
|
25
44
|
``` ruby
|
26
45
|
require "open-uri"
|
27
46
|
|
28
|
-
Retryable.retryable(:
|
47
|
+
Retryable.retryable(tries: 3, on: OpenURI::HTTPError) do
|
29
48
|
xml = open("http://example.com/test.xml").read
|
30
49
|
end
|
31
50
|
```
|
32
51
|
|
33
52
|
Try the block forever.
|
34
53
|
```ruby
|
35
|
-
Retryable.retryable(:
|
36
|
-
#
|
54
|
+
Retryable.retryable(tries: :infinite) do
|
55
|
+
# code here
|
37
56
|
end
|
38
57
|
```
|
39
58
|
|
40
|
-
Do
|
59
|
+
Do something, retry up to four times for either `ArgumentError` or
|
41
60
|
`TimeoutError` exceptions.
|
42
61
|
|
43
62
|
``` ruby
|
44
|
-
Retryable.retryable(:
|
45
|
-
#
|
63
|
+
Retryable.retryable(tries: 5, on: [ArgumentError, TimeoutError]) do
|
64
|
+
# code here
|
46
65
|
end
|
47
66
|
```
|
48
67
|
|
@@ -51,33 +70,42 @@ Ensure that block of code is executed, regardless of whether an exception was ra
|
|
51
70
|
``` ruby
|
52
71
|
f = File.open("testfile")
|
53
72
|
|
54
|
-
ensure_cb =
|
73
|
+
ensure_cb = proc do |retries|
|
55
74
|
puts "total retry attempts: #{retries}"
|
56
75
|
|
57
76
|
f.close
|
58
77
|
end
|
59
78
|
|
60
|
-
Retryable.retryable(:
|
79
|
+
Retryable.retryable(ensure: ensure_cb) do
|
61
80
|
# process file
|
62
81
|
end
|
63
82
|
```
|
64
83
|
|
65
84
|
## Defaults
|
66
85
|
|
67
|
-
:
|
86
|
+
tries: 2,
|
87
|
+
on: StandardError,
|
88
|
+
sleep: 1,
|
89
|
+
matching : /.*/,
|
90
|
+
ensure: proc { },
|
91
|
+
exception_cb: proc { },
|
92
|
+
not: [],
|
93
|
+
sleep_method: lambda { |n| Kernel.sleep(n) },
|
94
|
+
contexts = {}
|
68
95
|
|
69
96
|
Retryable also could be configured globally to change those defaults:
|
70
97
|
|
71
|
-
```
|
98
|
+
```ruby
|
72
99
|
Retryable.configure do |config|
|
73
|
-
config.ensure =
|
74
|
-
config.exception_cb =
|
100
|
+
config.ensure = proc {}
|
101
|
+
config.exception_cb = proc {}
|
75
102
|
config.matching = /.*/
|
76
103
|
config.on = StandardError
|
77
104
|
config.sleep = 1
|
78
105
|
config.tries = 2
|
79
106
|
config.not = []
|
80
107
|
config.sleep_method = Celluloid.method(:sleep)
|
108
|
+
config.contexts = {}
|
81
109
|
end
|
82
110
|
```
|
83
111
|
|
@@ -86,19 +114,19 @@ Sleeping
|
|
86
114
|
--------
|
87
115
|
By default Retryable waits for one second between retries. You can change this and even provide your own exponential backoff scheme.
|
88
116
|
|
89
|
-
```
|
90
|
-
Retryable.retryable(:
|
91
|
-
Retryable.retryable(:
|
92
|
-
Retryable.retryable(:
|
117
|
+
```ruby
|
118
|
+
Retryable.retryable(sleep: 0) { } # don't pause at all between retries
|
119
|
+
Retryable.retryable(sleep: 10) { } # sleep ten seconds between retries
|
120
|
+
Retryable.retryable(sleep: lambda { |n| 4**n }) { } # sleep 1, 4, 16, etc. each try
|
93
121
|
```
|
94
122
|
|
95
123
|
Matching error messages
|
96
124
|
--------
|
97
125
|
You can also retry based on the exception message:
|
98
126
|
|
99
|
-
```
|
100
|
-
Retryable.retryable(:
|
101
|
-
raise "
|
127
|
+
```ruby
|
128
|
+
Retryable.retryable(matching: /IO timeout/) do |retries, exception|
|
129
|
+
raise "oops IO timeout!" if retries == 0
|
102
130
|
end
|
103
131
|
```
|
104
132
|
|
@@ -106,31 +134,61 @@ Block Parameters
|
|
106
134
|
--------
|
107
135
|
Your block is called with two optional parameters: the number of tries until now, and the most recent exception.
|
108
136
|
|
109
|
-
```
|
137
|
+
```ruby
|
110
138
|
Retryable.retryable do |retries, exception|
|
111
139
|
puts "try #{retries} failed with exception: #{exception}" if retries > 0
|
112
|
-
|
140
|
+
# code here
|
113
141
|
end
|
114
142
|
```
|
115
143
|
|
116
144
|
Callback to run after an exception is rescued
|
117
145
|
--------
|
118
146
|
|
119
|
-
```
|
120
|
-
exception_cb =
|
147
|
+
```ruby
|
148
|
+
exception_cb = proc do |exception|
|
121
149
|
# http://smartinez87.github.io/exception_notification
|
122
|
-
ExceptionNotifier.notify_exception(exception, :
|
150
|
+
ExceptionNotifier.notify_exception(exception, data: {message: "it failed"})
|
123
151
|
end
|
124
152
|
|
125
|
-
Retryable.retryable(:
|
126
|
-
#
|
153
|
+
Retryable.retryable(exception_cb: exception_cb) do
|
154
|
+
# code here
|
127
155
|
end
|
128
156
|
```
|
129
157
|
|
130
|
-
|
158
|
+
Contexts
|
131
159
|
--------
|
132
160
|
|
161
|
+
Contexts allow you to extract common `Retryable.retryable` calling options for reuse or readability purposes.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
Retryable.configure do |config|
|
165
|
+
config.contexts[:faulty_service] = {
|
166
|
+
:on: [FaultyServiceTimeoutError],
|
167
|
+
:sleep: 10,
|
168
|
+
:tries: 5
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
Retryable.with_context(:faulty_service) {
|
174
|
+
# code here
|
175
|
+
}
|
176
|
+
```
|
177
|
+
|
178
|
+
You may also override options defined in your contexts:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
# :on & sleep defined in the context earlier are still effective
|
182
|
+
Retryable.with_context(:faulty_service, tries: 999) {
|
183
|
+
# code here
|
184
|
+
}
|
133
185
|
```
|
186
|
+
|
187
|
+
|
188
|
+
You can temporary disable retryable blocks
|
189
|
+
--------
|
190
|
+
|
191
|
+
```ruby
|
134
192
|
Retryable.enabled?
|
135
193
|
=> true
|
136
194
|
|
@@ -145,10 +203,10 @@ Specify exceptions where a retry should NOT be performed
|
|
145
203
|
No more tries will be made if an exception listed in `:not` is raised.
|
146
204
|
Takes precedence over `:on`.
|
147
205
|
|
148
|
-
```
|
206
|
+
```ruby
|
149
207
|
class MyError < StandardError; end
|
150
208
|
|
151
|
-
Retryable.retryable(:
|
209
|
+
Retryable.retryable(tries: 5, on: [StandardError], not: [MyError]) do
|
152
210
|
raise MyError "No retries!"
|
153
211
|
end
|
154
212
|
|
@@ -159,9 +217,9 @@ Specify the sleep method to use
|
|
159
217
|
This can be very useful when you are working with [Celluloid](https://github.com/celluloid/celluloid)
|
160
218
|
which implements its own version of the method sleep.
|
161
219
|
|
162
|
-
```
|
163
|
-
Retryable.retryable(:
|
164
|
-
|
220
|
+
```ruby
|
221
|
+
Retryable.retryable(sleep_method: Celluloid.method(:sleep)) do
|
222
|
+
# code here
|
165
223
|
end
|
166
224
|
```
|
167
225
|
|
@@ -171,12 +229,15 @@ Supported Ruby Versions
|
|
171
229
|
This library aims to support and is [tested against][travis] the following Ruby
|
172
230
|
versions:
|
173
231
|
|
174
|
-
* Ruby 1.8.7
|
175
|
-
* Ruby 1.9.2
|
176
232
|
* Ruby 1.9.3
|
177
233
|
* Ruby 2.0.0
|
178
234
|
* Ruby 2.1.2
|
179
235
|
* Ruby 2.2.0
|
236
|
+
* Ruby 2.3.1
|
237
|
+
* Ruby 2.4.0
|
238
|
+
* Ruby 2.5.0
|
239
|
+
|
240
|
+
*NOTE: if you need `retryable` to be running on Ruby 1.8 use gem versions prior to 3.0.0 release*
|
180
241
|
|
181
242
|
If something doesn't work on one of these versions, it's a bug.
|
182
243
|
|
@@ -185,27 +246,3 @@ however support will only be provided for the versions listed above.
|
|
185
246
|
|
186
247
|
If you would like this library to support another Ruby version or
|
187
248
|
implementation, you may volunteer to be a maintainer.
|
188
|
-
|
189
|
-
|
190
|
-
Installation
|
191
|
-
-------
|
192
|
-
|
193
|
-
Install the gem:
|
194
|
-
|
195
|
-
``` bash
|
196
|
-
$ gem install retryable
|
197
|
-
```
|
198
|
-
|
199
|
-
Add it to your Gemfile:
|
200
|
-
|
201
|
-
``` ruby
|
202
|
-
gem 'retryable'
|
203
|
-
```
|
204
|
-
|
205
|
-
## Thanks
|
206
|
-
|
207
|
-
[Chu Yeow for this nifty piece of code](http://blog.codefront.net/2008/01/14/retrying-code-blocks-in-ruby-on-exceptions-whatever/)
|
208
|
-
|
209
|
-
[Scott Bronson](https://github.com/bronson/retryable)
|
210
|
-
|
211
|
-
[travis]: http://travis-ci.org/nfedyashev/retryable
|
data/Rakefile
CHANGED
data/lib/retryable.rb
CHANGED
@@ -11,13 +11,15 @@ module Retryable
|
|
11
11
|
#
|
12
12
|
# @example
|
13
13
|
# Retryable.configure do |config|
|
14
|
-
# config.
|
15
|
-
# config.
|
14
|
+
# config.contexts = {}
|
15
|
+
# config.ensure = proc {}
|
16
|
+
# config.exception_cb = proc {}
|
16
17
|
# config.matching = /.*/
|
17
18
|
# config.on = StandardError
|
18
19
|
# config.sleep = 1
|
19
20
|
# config.tries = 2
|
20
21
|
# config.not = []
|
22
|
+
# config.sleep_method = ->(seconds) { Kernel.sleep(seconds) }
|
21
23
|
# end
|
22
24
|
def configure
|
23
25
|
yield(configuration)
|
@@ -29,6 +31,15 @@ module Retryable
|
|
29
31
|
@configuration ||= Configuration.new
|
30
32
|
end
|
31
33
|
|
34
|
+
def with_context(context_key, options = {}, &block)
|
35
|
+
unless configuration.contexts.keys.include? context_key
|
36
|
+
raise ArgumentError, "#{context_key} not found in Retryable.configuration.contexts. Available contexts: #{configuration.contexts.keys}"
|
37
|
+
end
|
38
|
+
retryable(configuration.contexts[context_key].merge(options), &block) if block
|
39
|
+
end
|
40
|
+
|
41
|
+
alias retryable_with_context with_context
|
42
|
+
|
32
43
|
def enabled?
|
33
44
|
configuration.enabled?
|
34
45
|
end
|
@@ -41,25 +52,16 @@ module Retryable
|
|
41
52
|
configuration.disable
|
42
53
|
end
|
43
54
|
|
44
|
-
def retryable(options = {}
|
45
|
-
opts =
|
46
|
-
:tries => self.configuration.tries,
|
47
|
-
:sleep => self.configuration.sleep,
|
48
|
-
:on => self.configuration.on,
|
49
|
-
:matching => self.configuration.matching,
|
50
|
-
:ensure => self.configuration.ensure,
|
51
|
-
:exception_cb => self.configuration.exception_cb,
|
52
|
-
:not => self.configuration.not,
|
53
|
-
:sleep_method => self.configuration.sleep_method
|
54
|
-
}
|
55
|
+
def retryable(options = {})
|
56
|
+
opts = configuration.to_hash
|
55
57
|
|
56
58
|
check_for_invalid_options(options, opts)
|
57
59
|
opts.merge!(options)
|
58
60
|
|
59
61
|
return if opts[:tries] == 0
|
60
62
|
|
61
|
-
on_exception = [
|
62
|
-
not_exception = [
|
63
|
+
on_exception = [opts[:on]].flatten
|
64
|
+
not_exception = [opts[:not]].flatten
|
63
65
|
tries = opts[:tries]
|
64
66
|
retries = 0
|
65
67
|
retry_exception = nil
|
@@ -71,7 +73,7 @@ module Retryable
|
|
71
73
|
rescue *on_exception => exception
|
72
74
|
raise unless configuration.enabled?
|
73
75
|
raise unless exception.message =~ opts[:matching]
|
74
|
-
raise if tries != :infinite && retries+1 >= tries
|
76
|
+
raise if tries != :infinite && retries + 1 >= tries
|
75
77
|
|
76
78
|
# Interrupt Exception could be raised while sleeping
|
77
79
|
begin
|
@@ -95,8 +97,8 @@ module Retryable
|
|
95
97
|
|
96
98
|
def check_for_invalid_options(custom_options, default_options)
|
97
99
|
invalid_options = default_options.merge(custom_options).keys - default_options.keys
|
98
|
-
|
99
|
-
raise ArgumentError
|
100
|
+
return if invalid_options.empty?
|
101
|
+
raise ArgumentError, "[Retryable] Invalid options: #{invalid_options.join(', ')}"
|
100
102
|
end
|
101
103
|
end
|
102
104
|
end
|
@@ -1,45 +1,39 @@
|
|
1
1
|
module Retryable
|
2
2
|
# Used to set up and modify settings for the retryable.
|
3
3
|
class Configuration
|
4
|
-
|
4
|
+
VALID_OPTION_KEYS = [
|
5
|
+
:contexts,
|
5
6
|
:ensure,
|
6
7
|
:exception_cb,
|
7
8
|
:matching,
|
9
|
+
:not,
|
8
10
|
:on,
|
9
11
|
:sleep,
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:sleep_method
|
12
|
+
:sleep_method,
|
13
|
+
:tries
|
13
14
|
].freeze
|
14
15
|
|
15
|
-
attr_accessor
|
16
|
-
attr_accessor :exception_cb
|
17
|
-
attr_accessor :matching
|
18
|
-
attr_accessor :on
|
19
|
-
attr_accessor :sleep
|
20
|
-
attr_accessor :tries
|
21
|
-
attr_accessor :not
|
22
|
-
attr_accessor :sleep_method
|
16
|
+
attr_accessor(*VALID_OPTION_KEYS)
|
23
17
|
|
24
18
|
attr_accessor :enabled
|
25
19
|
|
26
|
-
alias_method :enabled?, :enabled
|
27
|
-
|
28
20
|
def initialize
|
29
|
-
@
|
30
|
-
@
|
21
|
+
@contexts = {}
|
22
|
+
@ensure = proc {}
|
23
|
+
@exception_cb = proc {}
|
31
24
|
@matching = /.*/
|
32
25
|
@on = StandardError
|
33
26
|
@sleep = 1
|
34
27
|
@tries = 2
|
35
28
|
@not = []
|
36
|
-
@sleep_method =
|
37
|
-
@enabled
|
29
|
+
@sleep_method = ->(seconds) { Kernel.sleep(seconds) }
|
30
|
+
@enabled = true
|
38
31
|
end
|
39
32
|
|
40
33
|
def enable
|
41
34
|
@enabled = true
|
42
35
|
end
|
36
|
+
alias enabled? enabled
|
43
37
|
|
44
38
|
def disable
|
45
39
|
@enabled = false
|
@@ -54,9 +48,8 @@ module Retryable
|
|
54
48
|
|
55
49
|
# Returns a hash of all configurable options
|
56
50
|
def to_hash
|
57
|
-
|
58
|
-
|
59
|
-
hash
|
51
|
+
VALID_OPTION_KEYS.each_with_object({}) do |key, memo|
|
52
|
+
memo[key] = instance_variable_get("@#{key}")
|
60
53
|
end
|
61
54
|
end
|
62
55
|
|
data/lib/retryable/version.rb
CHANGED
@@ -1,16 +1,40 @@
|
|
1
1
|
module Retryable
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
PATCH = 4 unless defined? Retryable::Version::PATCH
|
2
|
+
# This module holds the Retryable version information.
|
3
|
+
module Version
|
4
|
+
module_function
|
6
5
|
|
7
|
-
|
6
|
+
# @return [Integer]
|
7
|
+
def major
|
8
|
+
3
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [Integer]
|
12
|
+
def minor
|
13
|
+
0
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Integer]
|
17
|
+
def patch
|
18
|
+
0
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Hash]
|
22
|
+
def to_h
|
23
|
+
{
|
24
|
+
major: major,
|
25
|
+
minor: minor,
|
26
|
+
patch: patch
|
27
|
+
}
|
28
|
+
end
|
8
29
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
30
|
+
# @return [Hash]
|
31
|
+
def to_a
|
32
|
+
[major, minor, patch].compact
|
33
|
+
end
|
13
34
|
|
35
|
+
# @return [String]
|
36
|
+
def to_s
|
37
|
+
to_a.join('.')
|
14
38
|
end
|
15
39
|
end
|
16
40
|
end
|
data/retryable.gemspec
CHANGED
@@ -1,20 +1,24 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'retryable/version'
|
3
4
|
|
4
|
-
Gem::Specification.new do |
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'retryable'
|
7
|
+
spec.version = Retryable::Version
|
8
|
+
spec.authors = ['Nikita Fedyashev', 'Carlo Zottmann', 'Chu Yeow']
|
9
|
+
spec.email = ['nfedyashev@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Retrying code blocks in Ruby'
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = 'http://github.com/nfedyashev/retryable'
|
14
|
+
spec.licenses = ['MIT']
|
15
|
+
|
16
|
+
spec.require_paths = ['lib']
|
17
|
+
spec.files = Dir['{config,lib,spec}/**/*', '*.md', '*.gemspec', 'Gemfile', 'Rakefile']
|
18
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
19
|
+
|
20
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 1.9.3')
|
21
|
+
spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.0'
|
20
24
|
end
|
@@ -2,22 +2,22 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe Retryable do
|
4
4
|
it 'is enabled by default' do
|
5
|
-
expect(
|
5
|
+
expect(described_class).to be_enabled
|
6
6
|
end
|
7
7
|
|
8
8
|
it 'could be disabled' do
|
9
|
-
|
10
|
-
expect(
|
9
|
+
described_class.disable
|
10
|
+
expect(described_class).not_to be_enabled
|
11
11
|
end
|
12
12
|
|
13
13
|
context 'when disabled' do
|
14
14
|
before do
|
15
|
-
|
15
|
+
described_class.disable
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'could be re-enabled' do
|
19
|
-
|
20
|
-
expect(
|
19
|
+
described_class.enable
|
20
|
+
expect(described_class).to be_enabled
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -25,15 +25,15 @@ RSpec.describe Retryable do
|
|
25
25
|
it 'passes retry count and exception on retry' do
|
26
26
|
expect(Kernel).to receive(:sleep).once.with(3)
|
27
27
|
|
28
|
-
|
28
|
+
described_class.configure do |config|
|
29
29
|
config.sleep = 3
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
counter(tries: 2) do |tries, ex|
|
33
33
|
expect(ex.class).to eq(StandardError) if tries > 0
|
34
34
|
raise StandardError if tries < 1
|
35
35
|
end
|
36
|
-
expect(
|
36
|
+
expect(counter.count).to eq(2)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Retryable::Version do
|
4
|
+
before do
|
5
|
+
allow(described_class).to receive(:major).and_return(2)
|
6
|
+
allow(described_class).to receive(:minor).and_return(0)
|
7
|
+
allow(described_class).to receive(:patch).and_return(4)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.to_h' do
|
11
|
+
it 'returns a hash with the right values' do
|
12
|
+
expect(described_class.to_h).to be_a Hash
|
13
|
+
expect(described_class.to_h[:major]).to eq(2)
|
14
|
+
expect(described_class.to_h[:minor]).to eq(0)
|
15
|
+
expect(described_class.to_h[:patch]).to eq(4)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.to_a' do
|
20
|
+
it 'returns an array with the right values' do
|
21
|
+
expect(described_class.to_a).to be_an Array
|
22
|
+
expect(described_class.to_a).to eq([2, 0, 4])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '.to_s' do
|
27
|
+
it 'returns a string with the right value' do
|
28
|
+
expect(described_class.to_s).to be_a String
|
29
|
+
expect(described_class.to_s).to eq('2.0.4')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
RSpec.describe Retryable do
|
5
|
+
describe '.retryable' do
|
6
|
+
before do
|
7
|
+
described_class.enable
|
8
|
+
@attempt = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'catch StandardError only by default' do
|
12
|
+
expect do
|
13
|
+
counter(tries: 2) { |tries| raise Exception if tries < 1 }
|
14
|
+
end.to raise_error Exception
|
15
|
+
expect(counter.count).to eq(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'retries on default exception' do
|
19
|
+
expect(Kernel).to receive(:sleep).once.with(1)
|
20
|
+
|
21
|
+
counter(tries: 2) { |tries| raise StandardError if tries < 1 }
|
22
|
+
expect(counter.count).to eq(2)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not retry if disabled' do
|
26
|
+
described_class.disable
|
27
|
+
|
28
|
+
expect do
|
29
|
+
counter(tries: 2) { raise }
|
30
|
+
end.to raise_error RuntimeError
|
31
|
+
expect(counter.count).to eq(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'executes *ensure* clause' do
|
35
|
+
ensure_cb = proc do |retries|
|
36
|
+
expect(retries).to eq(0)
|
37
|
+
end
|
38
|
+
|
39
|
+
described_class.retryable(ensure: ensure_cb) {}
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'passes retry count and exception on retry' do
|
43
|
+
expect(Kernel).to receive(:sleep).once.with(1)
|
44
|
+
|
45
|
+
counter(tries: 2) do |tries, ex|
|
46
|
+
expect(ex.class).to eq(StandardError) if tries > 0
|
47
|
+
raise StandardError if tries < 1
|
48
|
+
end
|
49
|
+
expect(counter.count).to eq(2)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'makes another try if exception is covered by :on' do
|
53
|
+
allow(Kernel).to receive(:sleep)
|
54
|
+
counter(on: [StandardError, ArgumentError, RuntimeError]) do |tries|
|
55
|
+
raise ArgumentError if tries < 1
|
56
|
+
end
|
57
|
+
expect(counter.count).to eq(2)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'does not try on unexpected exception' do
|
61
|
+
allow(Kernel).to receive(:sleep)
|
62
|
+
expect do
|
63
|
+
counter(on: RuntimeError) { |tries| raise StandardError if tries < 1 }
|
64
|
+
end.to raise_error StandardError
|
65
|
+
expect(counter.count).to eq(1)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'retries three times' do
|
69
|
+
allow(Kernel).to receive(:sleep)
|
70
|
+
counter(tries: 3) { |tries| raise StandardError if tries < 2 }
|
71
|
+
expect(counter.count).to eq(3)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'retries infinitely' do
|
75
|
+
expect do
|
76
|
+
Timeout.timeout(3) do
|
77
|
+
counter(tries: :infinite, sleep: 0.1) { raise StandardError }
|
78
|
+
end
|
79
|
+
end.to raise_error Timeout::Error
|
80
|
+
|
81
|
+
expect(counter.count).to be > 10
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'executes exponential backoff scheme for :sleep option' do
|
85
|
+
[1, 4, 16, 64].each { |i| expect(Kernel).to receive(:sleep).once.ordered.with(i) }
|
86
|
+
expect do
|
87
|
+
described_class.retryable(tries: 5, sleep: ->(n) { 4**n }) { raise RangeError }
|
88
|
+
end.to raise_error RangeError
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'calls :sleep_method option' do
|
92
|
+
sleep_method = double
|
93
|
+
expect(sleep_method).to receive(:call).twice
|
94
|
+
expect do
|
95
|
+
described_class.retryable(tries: 3, sleep_method: sleep_method) { |tries| raise RangeError if tries < 9 }
|
96
|
+
end.to raise_error RangeError
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'does not retry any exception if :on is empty list' do
|
100
|
+
expect do
|
101
|
+
counter(on: []) { raise }
|
102
|
+
end.to raise_error RuntimeError
|
103
|
+
expect(counter.count).to eq(1)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'catches an exception that matches the regex' do
|
107
|
+
expect(Kernel).to receive(:sleep).once.with(1)
|
108
|
+
counter(matching: /IO timeout/) { |c, _e| raise 'yo, IO timeout!' if c == 0 }
|
109
|
+
expect(counter.count).to eq(2)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'does not catch an exception that does not match the regex' do
|
113
|
+
expect(Kernel).not_to receive(:sleep)
|
114
|
+
expect do
|
115
|
+
counter(matching: /TimeError/) { raise 'yo, IO timeout!' }
|
116
|
+
end.to raise_error RuntimeError
|
117
|
+
expect(counter.count).to eq(1)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'does not allow invalid options' do
|
121
|
+
expect do
|
122
|
+
described_class.retryable(bad_option: 2) { raise 'this is bad' }
|
123
|
+
end.to raise_error ArgumentError, '[Retryable] Invalid options: bad_option'
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'accepts a callback to run after an exception is rescued' do
|
127
|
+
expect do
|
128
|
+
described_class.retryable(sleep: 0, exception_cb: proc { |e| @raised = e.to_s }) do |tries|
|
129
|
+
raise StandardError, 'this is fun!' if tries < 1
|
130
|
+
end
|
131
|
+
end.not_to raise_error
|
132
|
+
|
133
|
+
expect(@raised).to eq('this is fun!')
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'does not retry on :not exception' do
|
137
|
+
expect do
|
138
|
+
counter(not: RuntimeError) { |tries| raise RuntimeError if tries < 1 }
|
139
|
+
end.to raise_error RuntimeError
|
140
|
+
expect(counter.count).to eq(1)
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'gives precidence for :not over :on' do
|
144
|
+
expect do
|
145
|
+
counter(sleep: 0, tries: 3, on: StandardError, not: IndexError) do |tries|
|
146
|
+
raise tries >= 1 ? IndexError : StandardError
|
147
|
+
end
|
148
|
+
end.to raise_error IndexError
|
149
|
+
expect(counter.count).to eq(2)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Retryable do
|
4
|
+
describe '.with_context' do
|
5
|
+
before do
|
6
|
+
described_class.enable
|
7
|
+
@attempt = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'properly checks context configuration' do
|
11
|
+
expect do
|
12
|
+
described_class.with_context(:foo) {}
|
13
|
+
end.to raise_error ArgumentError, 'foo not found in Retryable.configuration.contexts. Available contexts: []'
|
14
|
+
|
15
|
+
expect do
|
16
|
+
described_class.retryable_with_context(:bar) {}
|
17
|
+
end.to raise_error ArgumentError, 'bar not found in Retryable.configuration.contexts. Available contexts: []'
|
18
|
+
|
19
|
+
expect do
|
20
|
+
described_class.configure do |config|
|
21
|
+
config.contexts[:faulty_service] = {
|
22
|
+
sleep: 3
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
described_class.retryable_with_context(:baz) {}
|
27
|
+
end.to raise_error ArgumentError, 'baz not found in Retryable.configuration.contexts. Available contexts: [:faulty_service]'
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'properly fetches context options' do
|
31
|
+
allow(Kernel).to receive(:sleep)
|
32
|
+
|
33
|
+
described_class.configure do |config|
|
34
|
+
config.contexts[:faulty_service] = {
|
35
|
+
tries: 3
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
c = counter_with_context(:faulty_service) { |tries| raise StandardError if tries < 2 }
|
40
|
+
expect(c.count).to eq(3)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'properly overrides context options with local arguments' do
|
44
|
+
allow(Kernel).to receive(:sleep)
|
45
|
+
|
46
|
+
described_class.configure do |config|
|
47
|
+
config.contexts[:faulty_service] = {
|
48
|
+
tries: 1
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
c = counter_with_context(:faulty_service, tries: 3) { |tries| raise StandardError if tries < 2 }
|
53
|
+
expect(c.count).to eq(3)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,25 +1,16 @@
|
|
1
|
-
require
|
2
|
-
require '
|
3
|
-
require 'pry'
|
1
|
+
require 'retryable'
|
2
|
+
require 'simplecov'
|
4
3
|
|
5
|
-
|
6
|
-
config.disable_monkey_patching!
|
4
|
+
Dir.glob(File.expand_path('../support/**/*.rb', __FILE__), &method(:require))
|
7
5
|
|
8
|
-
|
9
|
-
reset_config
|
10
|
-
end
|
6
|
+
SimpleCov.start
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
return Retryable.retryable(*opts) do |*args|
|
15
|
-
@try_count += 1
|
16
|
-
yield *args
|
17
|
-
end
|
18
|
-
end
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.disable_monkey_patching!
|
19
10
|
|
20
|
-
|
11
|
+
config.include(Counter)
|
21
12
|
|
22
|
-
|
13
|
+
config.before do
|
23
14
|
Retryable.configuration = nil
|
24
15
|
end
|
25
16
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Counter
|
2
|
+
class PlainGenerator
|
3
|
+
attr_reader :count
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
@count = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def around
|
11
|
+
Retryable.retryable(@options) do |*arguments|
|
12
|
+
increment
|
13
|
+
yield(*arguments)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def increment
|
20
|
+
@count += 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class GeneratorWithContext
|
25
|
+
attr_reader :count
|
26
|
+
|
27
|
+
def initialize(context_key, options)
|
28
|
+
@context_key = context_key
|
29
|
+
@count = 0
|
30
|
+
@options = options
|
31
|
+
end
|
32
|
+
|
33
|
+
def around
|
34
|
+
Retryable.with_context(@context_key, @options) do |*arguments|
|
35
|
+
increment
|
36
|
+
yield(*arguments)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def increment
|
43
|
+
@count += 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def counter(options = {}, &block)
|
48
|
+
@counter ||= PlainGenerator.new(options)
|
49
|
+
@counter.around(&block) if block_given?
|
50
|
+
@counter
|
51
|
+
end
|
52
|
+
|
53
|
+
def counter_with_context(context_key, options = {}, &block)
|
54
|
+
@counter_with_context ||= GeneratorWithContext.new(context_key, options)
|
55
|
+
@counter_with_context.around(&block) if block_given?
|
56
|
+
@counter_with_context
|
57
|
+
end
|
58
|
+
end
|
metadata
CHANGED
@@ -1,97 +1,81 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: retryable
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 2
|
8
|
-
- 0
|
9
|
-
- 4
|
10
|
-
version: 2.0.4
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 3.0.0
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Nikita Fedyashev
|
14
8
|
- Carlo Zottmann
|
15
9
|
- Chu Yeow
|
16
10
|
autorequire:
|
17
11
|
bindir: bin
|
18
12
|
cert_chain: []
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
dependencies:
|
23
|
-
- !ruby/object:Gem::Dependency
|
13
|
+
date: 2018-01-09 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
24
16
|
name: bundler
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
- !ruby/object:Gem::Version
|
31
|
-
hash: 15
|
32
|
-
segments:
|
33
|
-
- 1
|
34
|
-
- 0
|
35
|
-
version: "1.0"
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - "~>"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
36
22
|
type: :development
|
37
|
-
|
38
|
-
|
39
|
-
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - "~>"
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '1.0'
|
29
|
+
description: Retrying code blocks in Ruby
|
30
|
+
email:
|
31
|
+
- nfedyashev@gmail.com
|
40
32
|
executables: []
|
41
|
-
|
42
33
|
extensions: []
|
43
|
-
|
44
34
|
extra_rdoc_files: []
|
45
|
-
|
46
|
-
files:
|
35
|
+
files:
|
47
36
|
- CHANGELOG.md
|
37
|
+
- Gemfile
|
48
38
|
- LICENSE.md
|
49
39
|
- README.md
|
50
40
|
- Rakefile
|
51
|
-
- retryable.
|
41
|
+
- lib/retryable.rb
|
52
42
|
- lib/retryable/configuration.rb
|
53
43
|
- lib/retryable/version.rb
|
54
|
-
-
|
55
|
-
- spec/
|
56
|
-
- spec/
|
44
|
+
- retryable.gemspec
|
45
|
+
- spec/retryable/configuration_spec.rb
|
46
|
+
- spec/retryable/version_spec.rb
|
47
|
+
- spec/retryable_spec.rb
|
48
|
+
- spec/retryable_with_context_spec.rb
|
57
49
|
- spec/spec_helper.rb
|
58
|
-
|
50
|
+
- spec/support/counter.rb
|
59
51
|
homepage: http://github.com/nfedyashev/retryable
|
60
|
-
licenses:
|
52
|
+
licenses:
|
61
53
|
- MIT
|
54
|
+
metadata: {}
|
62
55
|
post_install_message:
|
63
56
|
rdoc_options: []
|
64
|
-
|
65
|
-
require_paths:
|
57
|
+
require_paths:
|
66
58
|
- lib
|
67
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
-
|
69
|
-
requirements:
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
70
61
|
- - ">="
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
version: "0"
|
76
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
-
none: false
|
78
|
-
requirements:
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 1.9.3
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
79
66
|
- - ">="
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
hash: 23
|
82
|
-
segments:
|
83
|
-
- 1
|
84
|
-
- 3
|
85
|
-
- 6
|
67
|
+
- !ruby/object:Gem::Version
|
86
68
|
version: 1.3.6
|
87
69
|
requirements: []
|
88
|
-
|
89
70
|
rubyforge_project:
|
90
|
-
rubygems_version:
|
71
|
+
rubygems_version: 2.4.5.2
|
91
72
|
signing_key:
|
92
|
-
specification_version:
|
93
|
-
summary:
|
94
|
-
test_files:
|
95
|
-
- spec/
|
96
|
-
- spec/
|
73
|
+
specification_version: 4
|
74
|
+
summary: Retrying code blocks in Ruby
|
75
|
+
test_files:
|
76
|
+
- spec/retryable/configuration_spec.rb
|
77
|
+
- spec/retryable/version_spec.rb
|
78
|
+
- spec/retryable_spec.rb
|
79
|
+
- spec/retryable_with_context_spec.rb
|
97
80
|
- spec/spec_helper.rb
|
81
|
+
- spec/support/counter.rb
|
data/spec/lib/retryable_spec.rb
DELETED
@@ -1,151 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'timeout'
|
3
|
-
|
4
|
-
RSpec.describe 'Retryable.retryable' do
|
5
|
-
before(:each) do
|
6
|
-
Retryable.enable
|
7
|
-
@attempt = 0
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'catch StandardError only by default' do
|
11
|
-
expect do
|
12
|
-
count_retryable(:tries => 2) { |tries, ex| raise Exception if tries < 1 }
|
13
|
-
end.to raise_error Exception
|
14
|
-
expect(@try_count).to eq(1)
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'retries on default exception' do
|
18
|
-
expect(Kernel).to receive(:sleep).once.with(1)
|
19
|
-
|
20
|
-
count_retryable(:tries => 2) { |tries, ex| raise StandardError if tries < 1 }
|
21
|
-
expect(@try_count).to eq(2)
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'does not retry if disabled' do
|
25
|
-
Retryable.disable
|
26
|
-
|
27
|
-
expect do
|
28
|
-
count_retryable(:tries => 2) { raise }
|
29
|
-
end.to raise_error RuntimeError
|
30
|
-
expect(@try_count).to eq(1)
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'executes *ensure* clause' do
|
34
|
-
ensure_cb = Proc.new do |retries|
|
35
|
-
expect(retries).to eq(0)
|
36
|
-
end
|
37
|
-
|
38
|
-
Retryable.retryable(:ensure => ensure_cb) { }
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'passes retry count and exception on retry' do
|
42
|
-
expect(Kernel).to receive(:sleep).once.with(1)
|
43
|
-
|
44
|
-
count_retryable(:tries => 2) do |tries, ex|
|
45
|
-
expect(ex.class).to eq(StandardError) if tries > 0
|
46
|
-
raise StandardError if tries < 1
|
47
|
-
end
|
48
|
-
expect(@try_count).to eq(2)
|
49
|
-
end
|
50
|
-
|
51
|
-
it 'makes another try if exception is covered by :on' do
|
52
|
-
allow(Kernel).to receive(:sleep)
|
53
|
-
count_retryable(:on => [StandardError, ArgumentError, RuntimeError] ) { |tries, ex| raise ArgumentError if tries < 1 }
|
54
|
-
expect(@try_count).to eq(2)
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'does not try on unexpected exception' do
|
58
|
-
allow(Kernel).to receive(:sleep)
|
59
|
-
expect do
|
60
|
-
count_retryable(:on => RuntimeError ) { |tries, ex| raise StandardError if tries < 1 }
|
61
|
-
end.to raise_error StandardError
|
62
|
-
expect(@try_count).to eq(1)
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'retries three times' do
|
66
|
-
allow(Kernel).to receive(:sleep)
|
67
|
-
count_retryable(:tries => 3) { |tries, ex| raise StandardError if tries < 2 }
|
68
|
-
expect(@try_count).to eq(3)
|
69
|
-
end
|
70
|
-
|
71
|
-
it 'retries infinitely' do
|
72
|
-
expect do
|
73
|
-
Timeout::timeout(3) do
|
74
|
-
count_retryable(:tries => :infinite, :sleep => 0.1) { |tries, ex| raise StandardError }
|
75
|
-
end
|
76
|
-
end.to raise_error Timeout::Error
|
77
|
-
|
78
|
-
expect(@try_count).to be > 10
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'retries on default exception' do
|
82
|
-
expect(Kernel).to receive(:sleep).once.with(1)
|
83
|
-
|
84
|
-
count_retryable(:tries => 2) { |tries, ex| raise StandardError if tries < 1 }
|
85
|
-
expect(@try_count).to eq(2)
|
86
|
-
end
|
87
|
-
|
88
|
-
it 'executes exponential backoff scheme for :sleep option' do
|
89
|
-
[1, 4, 16, 64].each { |i| expect(Kernel).to receive(:sleep).once.ordered.with(i) }
|
90
|
-
expect do
|
91
|
-
Retryable.retryable(:tries => 5, :sleep => lambda { |n| 4**n }) { raise RangeError }
|
92
|
-
end.to raise_error RangeError
|
93
|
-
end
|
94
|
-
|
95
|
-
it 'calls :sleep_method option' do
|
96
|
-
sleep_method = double
|
97
|
-
expect(sleep_method).to receive(:call).twice
|
98
|
-
expect do
|
99
|
-
Retryable.retryable(:tries => 3, :sleep_method => sleep_method) { |tries, ex| raise RangeError if tries < 9}
|
100
|
-
end.to raise_error RangeError
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'does not retry any exception if :on is empty list' do
|
104
|
-
expect do
|
105
|
-
count_retryable(:on => []) { raise }
|
106
|
-
end.to raise_error RuntimeError
|
107
|
-
expect(@try_count).to eq(1)
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'catches an exception that matches the regex' do
|
111
|
-
expect(Kernel).to receive(:sleep).once.with(1)
|
112
|
-
count_retryable(:matching => /IO timeout/) { |c,e| raise "yo, IO timeout!" if c == 0 }
|
113
|
-
expect(@try_count).to eq(2)
|
114
|
-
end
|
115
|
-
|
116
|
-
it 'does not catch an exception that does not match the regex' do
|
117
|
-
expect(Kernel).not_to receive(:sleep)
|
118
|
-
expect do
|
119
|
-
count_retryable(:matching => /TimeError/) { raise "yo, IO timeout!" }
|
120
|
-
end.to raise_error RuntimeError
|
121
|
-
expect(@try_count).to eq(1)
|
122
|
-
end
|
123
|
-
|
124
|
-
it 'does not allow invalid options' do
|
125
|
-
expect do
|
126
|
-
Retryable.retryable(:bad_option => 2) { raise "this is bad" }
|
127
|
-
end.to raise_error ArgumentError, '[Retryable] Invalid options: bad_option'
|
128
|
-
end
|
129
|
-
|
130
|
-
it 'accepts a callback to run after an exception is rescued' do
|
131
|
-
expect do
|
132
|
-
Retryable.retryable(:sleep => 0, :exception_cb => Proc.new {|e| @raised = e.to_s }) {|tries, ex| raise StandardError.new("this is fun!") if tries < 1 }
|
133
|
-
end.not_to raise_error
|
134
|
-
|
135
|
-
expect(@raised).to eq("this is fun!")
|
136
|
-
end
|
137
|
-
|
138
|
-
it 'does not retry on :not exception' do
|
139
|
-
expect do
|
140
|
-
count_retryable(:not => RuntimeError ) { |tries, ex| raise RuntimeError if tries < 1 }
|
141
|
-
end.to raise_error RuntimeError
|
142
|
-
expect(@try_count).to eq(1)
|
143
|
-
end
|
144
|
-
|
145
|
-
it 'gives precidence for :not over :on' do
|
146
|
-
expect do
|
147
|
-
count_retryable(:sleep => 0, :tries => 3, :on => StandardError, :not => IndexError ) { |tries, ex| raise tries >= 1 ? IndexError : StandardError }
|
148
|
-
end.to raise_error IndexError
|
149
|
-
expect(@try_count).to eq(2)
|
150
|
-
end
|
151
|
-
end
|