retryable 3.0.1 → 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/retryable.svg)](https://badge.fury.io/rb/retryable)
|
4
4
|
[![Build Status](https://travis-ci.org/nfedyashev/retryable.png?branch=master)](https://travis-ci.org/nfedyashev/retryable)
|
5
|
-
[![Dependency Status](https://www.versioneye.com/ruby/retryable/badge.svg)](https://www.versioneye.com/ruby/retryable)
|
6
5
|
[![Code Climate](https://codeclimate.com/github/nfedyashev/retryable/badges/gpa.svg)](https://codeclimate.com/github/nfedyashev/retryable)
|
7
6
|
[![Test Coverage](https://codeclimate.com/github/nfedyashev/retryable/badges/coverage.svg)](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
|