retryable 3.0.1 → 3.0.2
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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +34 -11
- data/lib/retryable.rb +19 -17
- data/lib/retryable/configuration.rb +5 -2
- data/lib/retryable/version.rb +1 -1
- data/retryable.gemspec +3 -0
- data/spec/retryable_logging_spec.rb +34 -0
- data/spec/retryable_spec.rb +4 -2
- data/spec/spec_helper.rb +4 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 166388a879ef75725b9a8195680f0018ecaf92d65355171e3755ab45479c9882
|
4
|
+
data.tar.gz: 4c89395fee2436620a17586627af3cae189aefc2b60b195682853c73dec7d0e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8669644175373da9208bfacd9978a620eda7b2f0789fbfc2149e1333af74456c91699f3adacbd116502360b0457f781f7d9748a48bf01bc21a885314df0a4269
|
7
|
+
data.tar.gz: a4780ebe5eaae4c1f91412dfd2a31948c14467a072cca3f737b3bcbb49b9526bb0fe78669df110879d47a50e6c4b05d4825558bec3df0608d79230d633db9327
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## Retryable 3.0.2 ##
|
2
|
+
|
3
|
+
* :log_method param has been added for flexible logging of your retries. It is silent by default.
|
4
|
+
|
1
5
|
## Retryable 3.0.1 ##
|
2
6
|
|
3
7
|
* :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
|
data/README.md
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/retryable)
|
4
4
|
[](https://travis-ci.org/nfedyashev/retryable)
|
5
|
-
[](https://www.versioneye.com/ruby/retryable)
|
6
5
|
[](https://codeclimate.com/github/nfedyashev/retryable)
|
7
6
|
[](https://codeclimate.com/github/nfedyashev/retryable/coverage)
|
8
7
|
|
@@ -57,10 +56,10 @@ end
|
|
57
56
|
```
|
58
57
|
|
59
58
|
Do something, retry up to four times for either `ArgumentError` or
|
60
|
-
`
|
59
|
+
`Timeout::Error` exceptions.
|
61
60
|
|
62
61
|
``` ruby
|
63
|
-
Retryable.retryable(tries: 5, on: [ArgumentError,
|
62
|
+
Retryable.retryable(tries: 5, on: [ArgumentError, Timeout::Error]) do
|
64
63
|
# code here
|
65
64
|
end
|
66
65
|
```
|
@@ -83,29 +82,31 @@ end
|
|
83
82
|
|
84
83
|
## Defaults
|
85
84
|
|
86
|
-
|
87
|
-
on: StandardError,
|
88
|
-
sleep: 1,
|
89
|
-
matching : /.*/,
|
85
|
+
contexts: {},
|
90
86
|
ensure: proc { },
|
91
87
|
exception_cb: proc { },
|
88
|
+
log_method: proc { },
|
89
|
+
matching : /.*/,
|
92
90
|
not: [],
|
91
|
+
on: StandardError,
|
92
|
+
sleep: 1,
|
93
93
|
sleep_method: lambda { |n| Kernel.sleep(n) },
|
94
|
-
|
94
|
+
tries: 2
|
95
95
|
|
96
96
|
Retryable also could be configured globally to change those defaults:
|
97
97
|
|
98
98
|
```ruby
|
99
99
|
Retryable.configure do |config|
|
100
|
+
config.contexts = {}
|
100
101
|
config.ensure = proc {}
|
101
102
|
config.exception_cb = proc {}
|
103
|
+
config.log_method = proc {}
|
102
104
|
config.matching = /.*/
|
105
|
+
config.not = []
|
103
106
|
config.on = StandardError
|
104
107
|
config.sleep = 1
|
105
|
-
config.tries = 2
|
106
|
-
config.not = []
|
107
108
|
config.sleep_method = Celluloid.method(:sleep)
|
108
|
-
config.
|
109
|
+
config.tries = 2
|
109
110
|
end
|
110
111
|
```
|
111
112
|
|
@@ -160,6 +161,28 @@ Retryable.retryable(exception_cb: exception_cb) do
|
|
160
161
|
end
|
161
162
|
```
|
162
163
|
|
164
|
+
Logging
|
165
|
+
--------
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
|
169
|
+
# or extract it to global config instead:
|
170
|
+
log_method = lambda do |retries, exception|
|
171
|
+
Logger.new(STDOUT).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
|
172
|
+
end
|
173
|
+
|
174
|
+
Retryable.retryable(log_method: log_method, matching: /IO timeout/) do |retries, exception|
|
175
|
+
raise "oops IO timeout!" if retries == 0
|
176
|
+
end
|
177
|
+
#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'
|
178
|
+
```
|
179
|
+
|
180
|
+
If you prefer to use Rails' native logger:
|
181
|
+
|
182
|
+
log_method = lambda do |retries, exception|
|
183
|
+
Rails.logger.debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
|
184
|
+
end
|
185
|
+
|
163
186
|
Contexts
|
164
187
|
--------
|
165
188
|
|
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
|
@@ -14,12 +18,13 @@ module Retryable
|
|
14
18
|
# config.contexts = {}
|
15
19
|
# config.ensure = proc {}
|
16
20
|
# config.exception_cb = proc {}
|
21
|
+
# config.log_method = proc {}
|
17
22
|
# config.matching = /.*/
|
23
|
+
# config.not = []
|
18
24
|
# config.on = StandardError
|
19
25
|
# config.sleep = 1
|
20
|
-
# config.tries = 2
|
21
|
-
# config.not = []
|
22
26
|
# config.sleep_method = ->(seconds) { Kernel.sleep(seconds) }
|
27
|
+
# config.tries = 2
|
23
28
|
# end
|
24
29
|
def configure
|
25
30
|
yield(configuration)
|
@@ -31,8 +36,10 @@ module Retryable
|
|
31
36
|
@configuration ||= Configuration.new
|
32
37
|
end
|
33
38
|
|
39
|
+
delegate [:enabled?, :enable, :disable] => :configuration
|
40
|
+
|
34
41
|
def with_context(context_key, options = {}, &block)
|
35
|
-
unless configuration.contexts.
|
42
|
+
unless configuration.contexts.key?(context_key)
|
36
43
|
raise ArgumentError, "#{context_key} not found in Retryable.configuration.contexts. Available contexts: #{configuration.contexts.keys}"
|
37
44
|
end
|
38
45
|
retryable(configuration.contexts[context_key].merge(options), &block) if block
|
@@ -40,35 +47,28 @@ module Retryable
|
|
40
47
|
|
41
48
|
alias retryable_with_context with_context
|
42
49
|
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
def enable
|
48
|
-
configuration.enable
|
49
|
-
end
|
50
|
-
|
51
|
-
def disable
|
52
|
-
configuration.disable
|
53
|
-
end
|
54
|
-
|
50
|
+
# rubocop:disable Metrics/MethodLength
|
51
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
55
52
|
def retryable(options = {})
|
56
53
|
opts = configuration.to_hash
|
57
54
|
|
58
55
|
check_for_invalid_options(options, opts)
|
59
56
|
opts.merge!(options)
|
60
57
|
|
58
|
+
# rubocop:disable Style/NumericPredicate
|
61
59
|
return if opts[:tries] == 0
|
60
|
+
# rubocop:enable Style/NumericPredicate
|
62
61
|
|
63
62
|
on_exception = opts[:on].is_a?(Array) ? opts[:on] : [opts[:on]]
|
64
63
|
not_exception = opts[:not].is_a?(Array) ? opts[:not] : [opts[:not]]
|
65
64
|
|
66
|
-
matching = [opts[:matching]]
|
65
|
+
matching = opts[:matching].is_a?(Array) ? opts[:matching] : [opts[:matching]]
|
67
66
|
tries = opts[:tries]
|
68
67
|
retries = 0
|
69
68
|
retry_exception = nil
|
70
69
|
|
71
70
|
begin
|
71
|
+
opts[:log_method].call(retries, retry_exception) if retries > 0
|
72
72
|
return yield retries, retry_exception
|
73
73
|
rescue *not_exception
|
74
74
|
raise
|
@@ -94,6 +94,8 @@ module Retryable
|
|
94
94
|
opts[:ensure].call(retries)
|
95
95
|
end
|
96
96
|
end
|
97
|
+
# rubocop:enable Metrics/MethodLength
|
98
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
97
99
|
|
98
100
|
private
|
99
101
|
|
@@ -111,7 +113,7 @@ module Retryable
|
|
111
113
|
when Regexp
|
112
114
|
message =~ candidate
|
113
115
|
else
|
114
|
-
raise ArgumentError,
|
116
|
+
raise ArgumentError, ':matches must be a string or regex'
|
115
117
|
end
|
116
118
|
end
|
117
119
|
end
|
@@ -5,6 +5,7 @@ module Retryable
|
|
5
5
|
:contexts,
|
6
6
|
:ensure,
|
7
7
|
:exception_cb,
|
8
|
+
:log_method,
|
8
9
|
:matching,
|
9
10
|
:not,
|
10
11
|
:on,
|
@@ -21,12 +22,14 @@ module Retryable
|
|
21
22
|
@contexts = {}
|
22
23
|
@ensure = proc {}
|
23
24
|
@exception_cb = proc {}
|
25
|
+
@log_method = proc {}
|
24
26
|
@matching = /.*/
|
27
|
+
@not = []
|
25
28
|
@on = StandardError
|
26
29
|
@sleep = 1
|
27
|
-
@tries = 2
|
28
|
-
@not = []
|
29
30
|
@sleep_method = ->(seconds) { Kernel.sleep(seconds) }
|
31
|
+
@tries = 2
|
32
|
+
|
30
33
|
@enabled = true
|
31
34
|
end
|
32
35
|
|
data/lib/retryable/version.rb
CHANGED
data/retryable.gemspec
CHANGED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
RSpec.describe Retryable do
|
5
|
+
describe '.retryable' do
|
6
|
+
before do
|
7
|
+
described_class.enable
|
8
|
+
expect(Kernel).to receive(:sleep)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:retryable) do
|
12
|
+
-> { Retryable.retryable(tries: 2) { |tries| raise StandardError, "because foo" if tries < 1 } }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'given default configuration' do
|
16
|
+
it 'does not output anything' do
|
17
|
+
expect { retryable.call }.not_to output.to_stdout_from_any_process
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'given custom STDOUT logger config option' do
|
22
|
+
it 'does not output anything' do
|
23
|
+
described_class.configure do |config|
|
24
|
+
config.log_method = lambda do |retries, exception|
|
25
|
+
Logger.new(STDOUT).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
expect { retryable.call }.to output(/\[Attempt #1\] Retrying because \[StandardError - because foo\]/).to_stdout_from_any_process
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
data/spec/retryable_spec.rb
CHANGED
@@ -126,11 +126,11 @@ RSpec.describe Retryable do
|
|
126
126
|
|
127
127
|
it 'catches an exception in the list of matches' do
|
128
128
|
expect(Kernel).to receive(:sleep).once.with(1)
|
129
|
-
counter(matching: [/IO timeout/,
|
129
|
+
counter(matching: [/IO timeout/, 'IO tymeout']) { |c, _e| raise 'yo, IO timeout!' if c == 0 }
|
130
130
|
expect(counter.count).to eq(2)
|
131
131
|
|
132
132
|
expect(Kernel).to receive(:sleep).once.with(1)
|
133
|
-
counter(matching: [/IO timeout/,
|
133
|
+
counter(matching: [/IO timeout/, 'IO tymeout']) { |c, _e| raise 'yo, IO tymeout!' if c == 0 }
|
134
134
|
expect(counter.count).to eq(4)
|
135
135
|
end
|
136
136
|
|
@@ -140,6 +140,7 @@ RSpec.describe Retryable do
|
|
140
140
|
end.to raise_error ArgumentError, '[Retryable] Invalid options: bad_option'
|
141
141
|
end
|
142
142
|
|
143
|
+
# rubocop:disable Rspec/InstanceVariable
|
143
144
|
it 'accepts a callback to run after an exception is rescued' do
|
144
145
|
expect do
|
145
146
|
described_class.retryable(sleep: 0, exception_cb: proc { |e| @raised = e.to_s }) do |tries|
|
@@ -149,6 +150,7 @@ RSpec.describe Retryable do
|
|
149
150
|
|
150
151
|
expect(@raised).to eq('this is fun!')
|
151
152
|
end
|
153
|
+
# rubocop:enable Rspec/InstanceVariable
|
152
154
|
|
153
155
|
it 'does not retry on :not exception' do
|
154
156
|
expect do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: retryable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nikita Fedyashev
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2018-
|
13
|
+
date: 2018-09-01 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -44,6 +44,7 @@ files:
|
|
44
44
|
- retryable.gemspec
|
45
45
|
- spec/retryable/configuration_spec.rb
|
46
46
|
- spec/retryable/version_spec.rb
|
47
|
+
- spec/retryable_logging_spec.rb
|
47
48
|
- spec/retryable_spec.rb
|
48
49
|
- spec/retryable_with_context_spec.rb
|
49
50
|
- spec/spec_helper.rb
|
@@ -68,13 +69,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
69
|
version: 1.3.6
|
69
70
|
requirements: []
|
70
71
|
rubyforge_project:
|
71
|
-
rubygems_version: 2.7.
|
72
|
+
rubygems_version: 2.7.6
|
72
73
|
signing_key:
|
73
74
|
specification_version: 4
|
74
75
|
summary: Retrying code blocks in Ruby
|
75
76
|
test_files:
|
76
77
|
- spec/retryable/configuration_spec.rb
|
77
78
|
- spec/retryable/version_spec.rb
|
79
|
+
- spec/retryable_logging_spec.rb
|
78
80
|
- spec/retryable_spec.rb
|
79
81
|
- spec/retryable_with_context_spec.rb
|
80
82
|
- spec/spec_helper.rb
|