retryable 2.0.4 → 3.0.5
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 +35 -1
- data/Gemfile +20 -0
- data/README.md +136 -66
- data/Rakefile +2 -2
- data/lib/retryable/configuration.rb +18 -22
- data/lib/retryable/version.rb +33 -9
- data/lib/retryable.rb +48 -27
- data/retryable.gemspec +29 -18
- data/spec/retryable/configuration_spec.rb +55 -0
- data/spec/retryable/version_spec.rb +32 -0
- data/spec/retryable_logging_spec.rb +34 -0
- data/spec/retryable_spec.rb +192 -0
- data/spec/retryable_with_context_spec.rb +56 -0
- data/spec/spec_helper.rb +10 -17
- data/spec/support/counter.rb +58 -0
- metadata +53 -66
- data/spec/lib/configuration_spec.rb +0 -39
- data/spec/lib/retryable_spec.rb +0 -151
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4adf6498d39c3a76bb6c3ea4c2ef8bd479d620b65c969cc43c970ae1dc1e8dd7
|
4
|
+
data.tar.gz: d60ff44f63519d2df8a70ca8729e5db2cda03e2b9ba2bf9af42dae66df9f65d5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 24fc7de390a5996451a2c9d0296811b031ce21e38a3c4374d4a002e3e12d6278c07f31a84dd86efcf9006fd2ff2f7ef40e40da791f2d98dedb56ee2b0c7b3006
|
7
|
+
data.tar.gz: '09360b246ca4c6868fc40807f1fc4978933f3aae138237efdb396e51e52a36fd2c8e1a44310365336cc9d43c08df62726d9d390739e3fdcce23640abdb0b2733'
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,42 @@
|
|
1
|
+
## Retryable 3.0.5 ##
|
2
|
+
|
3
|
+
Instead of :infinite magic constant from now on you can just use Ruby's native infinity data type e.g. Float::INFINITY.
|
4
|
+
See https://github.com/nfedyashev/retryable/commit/16f60bb09560c9470266dca8cd47c934594a67c5
|
5
|
+
This version is backwards compatible with older versions, no changes needed in your code.
|
6
|
+
|
7
|
+
## Retryable 3.0.4 ##
|
8
|
+
|
9
|
+
Fixed typo in exception message given invalid :matching argument type https://github.com/nfedyashev/retryable/pull/29
|
10
|
+
Thanks @msroz
|
11
|
+
|
12
|
+
## Retryable 3.0.3 ##
|
13
|
+
|
14
|
+
No changes to the source code, only added direct Changelog link on
|
15
|
+
rubygems.org for ease of use.
|
16
|
+
|
17
|
+
## Retryable 3.0.2 ##
|
18
|
+
|
19
|
+
* :log_method param has been added for flexible logging of your retries. It is silent by default.
|
20
|
+
|
21
|
+
## Retryable 3.0.1 ##
|
22
|
+
|
23
|
+
* :matching param from now on could be called in form of array with multiple matching conditions. This version is backwards compatible with 3.0.0
|
24
|
+
|
25
|
+
## Retryable 3.0.0 ##
|
26
|
+
NOTE: this version is backwards compatible with 2.0.4 version unless you're running it against Ruby 1.8 version.
|
27
|
+
|
28
|
+
* retryable can now also be configured via stored contexts.
|
29
|
+
* Ruby 1.8 support has been dropped.
|
30
|
+
|
31
|
+
Thanks @chubchenko for refactoring and various improvements.
|
32
|
+
|
1
33
|
## Retryable 2.0.4 ##
|
2
34
|
|
3
35
|
* :infinite value is now available as :tries paramater. Use it for retrying your blocks infinitely until it stops failing.
|
4
36
|
* :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
|
37
|
+
Use `:sleep_method: Celluloid.method(:sleep)` in such cases.
|
38
|
+
|
39
|
+
Thanks @alexcastano
|
6
40
|
|
7
41
|
## Retryable 2.0.3 ##
|
8
42
|
|
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
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
|
+
gemspec
|
data/README.md
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
=====
|
1
|
+
# Retryable
|
3
2
|
|
3
|
+
[](https://badge.fury.io/rb/retryable)
|
4
4
|
[](https://travis-ci.org/nfedyashev/retryable)
|
5
|
-
[](https://codeclimate.com/github/nfedyashev/retryable)
|
6
|
+
[](https://codeclimate.com/github/nfedyashev/retryable/coverage)
|
7
|
+
|
8
|
+
[](http://inch-ci.org/github/nfedyashev/retryable)
|
6
9
|
|
7
10
|
Description
|
8
11
|
--------
|
@@ -16,6 +19,21 @@ runs the passed block. Should an exception occur, it'll retry for (n-1) times.
|
|
16
19
|
Should the number of retries be reached without success, the last exception
|
17
20
|
will be raised.
|
18
21
|
|
22
|
+
Installation
|
23
|
+
-------
|
24
|
+
|
25
|
+
Install the gem:
|
26
|
+
|
27
|
+
``` bash
|
28
|
+
$ gem install retryable
|
29
|
+
```
|
30
|
+
|
31
|
+
Add it to your Gemfile:
|
32
|
+
|
33
|
+
``` ruby
|
34
|
+
gem 'retryable'
|
35
|
+
```
|
36
|
+
|
19
37
|
|
20
38
|
Examples
|
21
39
|
--------
|
@@ -25,24 +43,26 @@ Open an URL, retry up to two times when an `OpenURI::HTTPError` occurs.
|
|
25
43
|
``` ruby
|
26
44
|
require "open-uri"
|
27
45
|
|
28
|
-
Retryable.retryable(:
|
46
|
+
Retryable.retryable(tries: 3, on: OpenURI::HTTPError) do
|
29
47
|
xml = open("http://example.com/test.xml").read
|
30
48
|
end
|
31
49
|
```
|
32
50
|
|
33
51
|
Try the block forever.
|
34
52
|
```ruby
|
35
|
-
|
36
|
-
|
53
|
+
# For ruby versions prior to 1.9.2 use :infinite symbol instead
|
54
|
+
Retryable.retryable(tries: Float::INFINITY) do
|
55
|
+
# code here
|
37
56
|
end
|
57
|
+
|
38
58
|
```
|
39
59
|
|
40
|
-
Do
|
41
|
-
`
|
60
|
+
Do something, retry up to four times for either `ArgumentError` or
|
61
|
+
`Timeout::Error` exceptions.
|
42
62
|
|
43
63
|
``` ruby
|
44
|
-
Retryable.retryable(:
|
45
|
-
#
|
64
|
+
Retryable.retryable(tries: 5, on: [ArgumentError, Timeout::Error]) do
|
65
|
+
# code here
|
46
66
|
end
|
47
67
|
```
|
48
68
|
|
@@ -51,33 +71,44 @@ Ensure that block of code is executed, regardless of whether an exception was ra
|
|
51
71
|
``` ruby
|
52
72
|
f = File.open("testfile")
|
53
73
|
|
54
|
-
ensure_cb =
|
74
|
+
ensure_cb = proc do |retries|
|
55
75
|
puts "total retry attempts: #{retries}"
|
56
76
|
|
57
77
|
f.close
|
58
78
|
end
|
59
79
|
|
60
|
-
Retryable.retryable(:
|
80
|
+
Retryable.retryable(ensure: ensure_cb) do
|
61
81
|
# process file
|
62
82
|
end
|
63
83
|
```
|
64
84
|
|
65
85
|
## Defaults
|
66
86
|
|
67
|
-
:
|
87
|
+
contexts: {},
|
88
|
+
ensure: proc { },
|
89
|
+
exception_cb: proc { },
|
90
|
+
log_method: proc { },
|
91
|
+
matching : /.*/,
|
92
|
+
not: [],
|
93
|
+
on: StandardError,
|
94
|
+
sleep: 1,
|
95
|
+
sleep_method: lambda { |n| Kernel.sleep(n) },
|
96
|
+
tries: 2
|
68
97
|
|
69
98
|
Retryable also could be configured globally to change those defaults:
|
70
99
|
|
71
|
-
```
|
100
|
+
```ruby
|
72
101
|
Retryable.configure do |config|
|
73
|
-
config.
|
74
|
-
config.
|
102
|
+
config.contexts = {}
|
103
|
+
config.ensure = proc {}
|
104
|
+
config.exception_cb = proc {}
|
105
|
+
config.log_method = proc {}
|
75
106
|
config.matching = /.*/
|
107
|
+
config.not = []
|
76
108
|
config.on = StandardError
|
77
109
|
config.sleep = 1
|
78
|
-
config.tries = 2
|
79
|
-
config.not = []
|
80
110
|
config.sleep_method = Celluloid.method(:sleep)
|
111
|
+
config.tries = 2
|
81
112
|
end
|
82
113
|
```
|
83
114
|
|
@@ -86,19 +117,24 @@ Sleeping
|
|
86
117
|
--------
|
87
118
|
By default Retryable waits for one second between retries. You can change this and even provide your own exponential backoff scheme.
|
88
119
|
|
89
|
-
```
|
90
|
-
Retryable.retryable(:
|
91
|
-
Retryable.retryable(:
|
92
|
-
Retryable.retryable(:
|
120
|
+
```ruby
|
121
|
+
Retryable.retryable(sleep: 0) { } # don't pause at all between retries
|
122
|
+
Retryable.retryable(sleep: 10) { } # sleep ten seconds between retries
|
123
|
+
Retryable.retryable(sleep: lambda { |n| 4**n }) { } # sleep 1, 4, 16, etc. each try
|
93
124
|
```
|
94
125
|
|
95
126
|
Matching error messages
|
96
127
|
--------
|
97
128
|
You can also retry based on the exception message:
|
98
129
|
|
99
|
-
```
|
100
|
-
Retryable.retryable(:
|
101
|
-
raise "
|
130
|
+
```ruby
|
131
|
+
Retryable.retryable(matching: /IO timeout/) do |retries, exception|
|
132
|
+
raise "oops IO timeout!" if retries == 0
|
133
|
+
end
|
134
|
+
|
135
|
+
#matching param supports array format as well:
|
136
|
+
Retryable.retryable(matching: [/IO timeout/, "IO tymeout"]) do |retries, exception|
|
137
|
+
raise "oops IO timeout!" if retries == 0
|
102
138
|
end
|
103
139
|
```
|
104
140
|
|
@@ -106,31 +142,85 @@ Block Parameters
|
|
106
142
|
--------
|
107
143
|
Your block is called with two optional parameters: the number of tries until now, and the most recent exception.
|
108
144
|
|
109
|
-
```
|
145
|
+
```ruby
|
110
146
|
Retryable.retryable do |retries, exception|
|
111
147
|
puts "try #{retries} failed with exception: #{exception}" if retries > 0
|
112
|
-
|
148
|
+
# code here
|
113
149
|
end
|
114
150
|
```
|
115
151
|
|
116
152
|
Callback to run after an exception is rescued
|
117
153
|
--------
|
118
154
|
|
119
|
-
```
|
120
|
-
exception_cb =
|
155
|
+
```ruby
|
156
|
+
exception_cb = proc do |exception|
|
121
157
|
# http://smartinez87.github.io/exception_notification
|
122
|
-
ExceptionNotifier.notify_exception(exception, :
|
158
|
+
ExceptionNotifier.notify_exception(exception, data: {message: "it failed"})
|
123
159
|
end
|
124
160
|
|
125
|
-
Retryable.retryable(:
|
126
|
-
#
|
161
|
+
Retryable.retryable(exception_cb: exception_cb) do
|
162
|
+
# code here
|
127
163
|
end
|
128
164
|
```
|
129
165
|
|
130
|
-
|
166
|
+
Logging
|
131
167
|
--------
|
132
168
|
|
169
|
+
```ruby
|
170
|
+
|
171
|
+
# or extract it to global config instead:
|
172
|
+
log_method = lambda do |retries, exception|
|
173
|
+
Logger.new(STDOUT).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
|
174
|
+
end
|
175
|
+
|
176
|
+
Retryable.retryable(log_method: log_method, matching: /IO timeout/) do |retries, exception|
|
177
|
+
raise "oops IO timeout!" if retries == 0
|
178
|
+
end
|
179
|
+
#D, [2018-09-01T18:19:06.093811 #22535] DEBUG -- : [Attempt #1] Retrying because [RuntimeError - oops IO timeout!]: (irb#1):6:in `block in irb_binding' | /home/nikita/Projects/retryable/lib/retryable.rb:73:in `retryable' | (irb#1):6:in `irb_binding' | /home/nikita/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval' | /home/nikita/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/irb/workspace.rb:85:in `evaluate'
|
133
180
|
```
|
181
|
+
|
182
|
+
If you prefer to use Rails' native logger:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
log_method = lambda do |retries, exception|
|
186
|
+
Rails.logger.debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
Contexts
|
191
|
+
--------
|
192
|
+
|
193
|
+
Contexts allow you to extract common `Retryable.retryable` calling options for reuse or readability purposes.
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
Retryable.configure do |config|
|
197
|
+
config.contexts[:faulty_service] = {
|
198
|
+
:on: [FaultyServiceTimeoutError],
|
199
|
+
:sleep: 10,
|
200
|
+
:tries: 5
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
Retryable.with_context(:faulty_service) {
|
206
|
+
# code here
|
207
|
+
}
|
208
|
+
```
|
209
|
+
|
210
|
+
You may also override options defined in your contexts:
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
# :on & sleep defined in the context earlier are still effective
|
214
|
+
Retryable.with_context(:faulty_service, tries: 999) {
|
215
|
+
# code here
|
216
|
+
}
|
217
|
+
```
|
218
|
+
|
219
|
+
|
220
|
+
You can temporary disable retryable blocks
|
221
|
+
--------
|
222
|
+
|
223
|
+
```ruby
|
134
224
|
Retryable.enabled?
|
135
225
|
=> true
|
136
226
|
|
@@ -145,10 +235,10 @@ Specify exceptions where a retry should NOT be performed
|
|
145
235
|
No more tries will be made if an exception listed in `:not` is raised.
|
146
236
|
Takes precedence over `:on`.
|
147
237
|
|
148
|
-
```
|
238
|
+
```ruby
|
149
239
|
class MyError < StandardError; end
|
150
240
|
|
151
|
-
Retryable.retryable(:
|
241
|
+
Retryable.retryable(tries: 5, on: [StandardError], not: [MyError]) do
|
152
242
|
raise MyError "No retries!"
|
153
243
|
end
|
154
244
|
|
@@ -159,9 +249,9 @@ Specify the sleep method to use
|
|
159
249
|
This can be very useful when you are working with [Celluloid](https://github.com/celluloid/celluloid)
|
160
250
|
which implements its own version of the method sleep.
|
161
251
|
|
162
|
-
```
|
163
|
-
Retryable.retryable(:
|
164
|
-
|
252
|
+
```ruby
|
253
|
+
Retryable.retryable(sleep_method: Celluloid.method(:sleep)) do
|
254
|
+
# code here
|
165
255
|
end
|
166
256
|
```
|
167
257
|
|
@@ -171,12 +261,16 @@ Supported Ruby Versions
|
|
171
261
|
This library aims to support and is [tested against][travis] the following Ruby
|
172
262
|
versions:
|
173
263
|
|
174
|
-
* Ruby 1.8.7
|
175
|
-
* Ruby 1.9.2
|
176
264
|
* Ruby 1.9.3
|
177
265
|
* Ruby 2.0.0
|
178
|
-
* Ruby 2.1.
|
179
|
-
* Ruby 2.2.
|
266
|
+
* Ruby 2.1.10
|
267
|
+
* Ruby 2.2.10
|
268
|
+
* Ruby 2.3.8
|
269
|
+
* Ruby 2.4.5
|
270
|
+
* Ruby 2.5.3
|
271
|
+
* Ruby 2.6.1
|
272
|
+
|
273
|
+
*NOTE: if you need `retryable` to be running on Ruby 1.8 use gem versions prior to 3.0.0 release*
|
180
274
|
|
181
275
|
If something doesn't work on one of these versions, it's a bug.
|
182
276
|
|
@@ -185,27 +279,3 @@ however support will only be provided for the versions listed above.
|
|
185
279
|
|
186
280
|
If you would like this library to support another Ruby version or
|
187
281
|
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
@@ -1,45 +1,42 @@
|
|
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,
|
8
|
+
:log_method,
|
7
9
|
:matching,
|
10
|
+
:not,
|
8
11
|
:on,
|
9
12
|
:sleep,
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:sleep_method
|
13
|
+
:sleep_method,
|
14
|
+
:tries
|
13
15
|
].freeze
|
14
16
|
|
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
|
17
|
+
attr_accessor(*VALID_OPTION_KEYS)
|
23
18
|
|
24
19
|
attr_accessor :enabled
|
25
20
|
|
26
|
-
alias_method :enabled?, :enabled
|
27
|
-
|
28
21
|
def initialize
|
29
|
-
@
|
30
|
-
@
|
22
|
+
@contexts = {}
|
23
|
+
@ensure = proc {}
|
24
|
+
@exception_cb = proc {}
|
25
|
+
@log_method = proc {}
|
31
26
|
@matching = /.*/
|
27
|
+
@not = []
|
32
28
|
@on = StandardError
|
33
29
|
@sleep = 1
|
30
|
+
@sleep_method = ->(seconds) { Kernel.sleep(seconds) }
|
34
31
|
@tries = 2
|
35
|
-
|
36
|
-
@
|
37
|
-
@enabled = true
|
32
|
+
|
33
|
+
@enabled = true
|
38
34
|
end
|
39
35
|
|
40
36
|
def enable
|
41
37
|
@enabled = true
|
42
38
|
end
|
39
|
+
alias enabled? enabled
|
43
40
|
|
44
41
|
def disable
|
45
42
|
@enabled = false
|
@@ -54,9 +51,8 @@ module Retryable
|
|
54
51
|
|
55
52
|
# Returns a hash of all configurable options
|
56
53
|
def to_hash
|
57
|
-
|
58
|
-
|
59
|
-
hash
|
54
|
+
VALID_OPTION_KEYS.each_with_object({}) do |key, memo|
|
55
|
+
memo[key] = instance_variable_get("@#{key}")
|
60
56
|
end
|
61
57
|
end
|
62
58
|
|
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
|
+
5
|
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/lib/retryable.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
require 'retryable/version'
|
2
2
|
require 'retryable/configuration'
|
3
|
+
require 'forwardable'
|
3
4
|
|
5
|
+
# Runs a code block, and retries it when an exception occurs. It's great when working with flakey webservices (for example).
|
4
6
|
module Retryable
|
5
7
|
class << self
|
8
|
+
extend Forwardable
|
9
|
+
|
6
10
|
# A Retryable configuration object. Must act like a hash and return sensible
|
7
11
|
# values for all Retryable configuration options. See Retryable::Configuration.
|
8
12
|
attr_writer :configuration
|
@@ -11,13 +15,16 @@ module Retryable
|
|
11
15
|
#
|
12
16
|
# @example
|
13
17
|
# Retryable.configure do |config|
|
14
|
-
# config.
|
15
|
-
# config.
|
18
|
+
# config.contexts = {}
|
19
|
+
# config.ensure = proc {}
|
20
|
+
# config.exception_cb = proc {}
|
21
|
+
# config.log_method = proc {}
|
16
22
|
# config.matching = /.*/
|
23
|
+
# config.not = []
|
17
24
|
# config.on = StandardError
|
18
25
|
# config.sleep = 1
|
26
|
+
# config.sleep_method = ->(seconds) { Kernel.sleep(seconds) }
|
19
27
|
# config.tries = 2
|
20
|
-
# config.not = []
|
21
28
|
# end
|
22
29
|
def configure
|
23
30
|
yield(configuration)
|
@@ -29,49 +36,48 @@ module Retryable
|
|
29
36
|
@configuration ||= Configuration.new
|
30
37
|
end
|
31
38
|
|
32
|
-
|
33
|
-
configuration.enabled?
|
34
|
-
end
|
39
|
+
delegate [:enabled?, :enable, :disable] => :configuration
|
35
40
|
|
36
|
-
def
|
37
|
-
configuration.
|
41
|
+
def with_context(context_key, options = {}, &block)
|
42
|
+
unless configuration.contexts.key?(context_key)
|
43
|
+
raise ArgumentError, "#{context_key} not found in Retryable.configuration.contexts. Available contexts: #{configuration.contexts.keys}"
|
44
|
+
end
|
45
|
+
retryable(configuration.contexts[context_key].merge(options), &block) if block
|
38
46
|
end
|
39
47
|
|
40
|
-
|
41
|
-
configuration.disable
|
42
|
-
end
|
48
|
+
alias retryable_with_context with_context
|
43
49
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
}
|
50
|
+
# rubocop:disable Metrics/MethodLength
|
51
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
52
|
+
def retryable(options = {})
|
53
|
+
opts = configuration.to_hash
|
55
54
|
|
56
55
|
check_for_invalid_options(options, opts)
|
57
56
|
opts.merge!(options)
|
58
57
|
|
58
|
+
# rubocop:disable Style/NumericPredicate
|
59
59
|
return if opts[:tries] == 0
|
60
|
+
# rubocop:enable Style/NumericPredicate
|
60
61
|
|
61
|
-
on_exception = [ opts[:on] ]
|
62
|
-
not_exception = [ opts[:not] ]
|
62
|
+
on_exception = opts[:on].is_a?(Array) ? opts[:on] : [opts[:on]]
|
63
|
+
not_exception = opts[:not].is_a?(Array) ? opts[:not] : [opts[:not]]
|
64
|
+
|
65
|
+
matching = opts[:matching].is_a?(Array) ? opts[:matching] : [opts[:matching]]
|
63
66
|
tries = opts[:tries]
|
64
67
|
retries = 0
|
65
68
|
retry_exception = nil
|
66
69
|
|
67
70
|
begin
|
71
|
+
opts[:log_method].call(retries, retry_exception) if retries > 0
|
68
72
|
return yield retries, retry_exception
|
69
73
|
rescue *not_exception
|
70
74
|
raise
|
71
75
|
rescue *on_exception => exception
|
72
76
|
raise unless configuration.enabled?
|
73
|
-
raise unless exception.message
|
74
|
-
|
77
|
+
raise unless matches?(exception.message, matching)
|
78
|
+
|
79
|
+
infinite_retries = :infinite || tries.respond_to?(:infinite?) && tries.infinite?
|
80
|
+
raise if tries != infinite_retries && retries + 1 >= tries
|
75
81
|
|
76
82
|
# Interrupt Exception could be raised while sleeping
|
77
83
|
begin
|
@@ -90,13 +96,28 @@ module Retryable
|
|
90
96
|
opts[:ensure].call(retries)
|
91
97
|
end
|
92
98
|
end
|
99
|
+
# rubocop:enable Metrics/MethodLength
|
100
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
93
101
|
|
94
102
|
private
|
95
103
|
|
96
104
|
def check_for_invalid_options(custom_options, default_options)
|
97
105
|
invalid_options = default_options.merge(custom_options).keys - default_options.keys
|
106
|
+
return if invalid_options.empty?
|
107
|
+
raise ArgumentError, "[Retryable] Invalid options: #{invalid_options.join(', ')}"
|
108
|
+
end
|
98
109
|
|
99
|
-
|
110
|
+
def matches?(message, candidates)
|
111
|
+
candidates.any? do |candidate|
|
112
|
+
case candidate
|
113
|
+
when String
|
114
|
+
message.include?(candidate)
|
115
|
+
when Regexp
|
116
|
+
message =~ candidate
|
117
|
+
else
|
118
|
+
raise ArgumentError, ':matching must be a string or regex'
|
119
|
+
end
|
120
|
+
end
|
100
121
|
end
|
101
122
|
end
|
102
123
|
end
|