take2 0.0.4 → 0.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 +4 -4
- data/.circleci/config.yml +1 -1
- data/.hound.yml +3 -0
- data/.rubocop.yml +1201 -0
- data/Gemfile +4 -1
- data/Gemfile.lock +20 -2
- data/lib/take2.rb +55 -30
- data/lib/take2/backoff.rb +48 -0
- data/lib/take2/configuration.rb +44 -19
- data/lib/take2/version.rb +4 -2
- data/spec/spec_helper.rb +3 -1
- data/spec/take2/configuration_spec.rb +41 -51
- data/spec/take2_spec.rb +116 -113
- data/take2.gemspec +8 -6
- metadata +5 -2
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,20 +1,27 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
take2 (0.0.
|
4
|
+
take2 (0.0.5)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://rubygems.org/
|
8
8
|
specs:
|
9
|
+
ast (2.4.0)
|
9
10
|
coderay (1.1.2)
|
10
11
|
diff-lcs (1.3)
|
12
|
+
jaro_winkler (1.5.2)
|
11
13
|
method_source (0.8.2)
|
14
|
+
parallel (1.12.1)
|
15
|
+
parser (2.5.3.0)
|
16
|
+
ast (~> 2.4.0)
|
17
|
+
powerpack (0.1.2)
|
12
18
|
pry (0.10.4)
|
13
19
|
coderay (~> 1.1.0)
|
14
20
|
method_source (~> 0.8.1)
|
15
21
|
slop (~> 3.4)
|
16
22
|
pry-nav (0.2.4)
|
17
23
|
pry (>= 0.9.10, < 0.11.0)
|
24
|
+
rainbow (3.0.0)
|
18
25
|
rake (12.3.1)
|
19
26
|
rspec (3.8.0)
|
20
27
|
rspec-core (~> 3.8.0)
|
@@ -29,7 +36,17 @@ GEM
|
|
29
36
|
diff-lcs (>= 1.2.0, < 2.0)
|
30
37
|
rspec-support (~> 3.8.0)
|
31
38
|
rspec-support (3.8.0)
|
39
|
+
rubocop (0.62.0)
|
40
|
+
jaro_winkler (~> 1.5.1)
|
41
|
+
parallel (~> 1.10)
|
42
|
+
parser (>= 2.5, != 2.5.1.1)
|
43
|
+
powerpack (~> 0.1)
|
44
|
+
rainbow (>= 2.2.2, < 4.0)
|
45
|
+
ruby-progressbar (~> 1.7)
|
46
|
+
unicode-display_width (~> 1.4.0)
|
47
|
+
ruby-progressbar (1.10.0)
|
32
48
|
slop (3.6.0)
|
49
|
+
unicode-display_width (1.4.1)
|
33
50
|
|
34
51
|
PLATFORMS
|
35
52
|
ruby
|
@@ -39,7 +56,8 @@ DEPENDENCIES
|
|
39
56
|
pry-nav
|
40
57
|
rake
|
41
58
|
rspec (= 3.8.0)
|
59
|
+
rubocop
|
42
60
|
take2!
|
43
61
|
|
44
62
|
BUNDLED WITH
|
45
|
-
1.16.
|
63
|
+
1.16.6
|
data/lib/take2.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'net/http'
|
3
4
|
require 'take2/version'
|
4
5
|
require 'take2/configuration'
|
5
6
|
|
6
7
|
module Take2
|
7
|
-
|
8
8
|
def self.included(base)
|
9
|
-
base.extend
|
10
|
-
base.send
|
11
|
-
base.send
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
base.send(:set_defaults)
|
11
|
+
base.send(:include, InstanceMethods)
|
12
12
|
end
|
13
13
|
|
14
14
|
class << self
|
15
15
|
attr_accessor :configuration
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.
|
18
|
+
def self.config
|
19
19
|
@configuration ||= Configuration.new
|
20
20
|
end
|
21
21
|
|
@@ -28,11 +28,10 @@ module Take2
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.configure
|
31
|
-
yield(
|
31
|
+
yield(config) if block_given?
|
32
32
|
end
|
33
33
|
|
34
34
|
module InstanceMethods
|
35
|
-
|
36
35
|
# Yields a block and retries on retriable errors n times.
|
37
36
|
# The raised error could be the defined retriable or it child.
|
38
37
|
#
|
@@ -43,8 +42,10 @@ module Take2
|
|
43
42
|
# number_of_retries 3
|
44
43
|
# retriable_errors Net::HTTPRetriableError
|
45
44
|
# retriable_condition proc { |error| response_status(error.response) < 500 }
|
46
|
-
# on_retry proc { |error, tries|
|
47
|
-
#
|
45
|
+
# on_retry proc { |error, tries|
|
46
|
+
# puts "#{self.name} - Retrying.. #{tries} of #{self.retriable_configuration[:retries]} (#{error})"
|
47
|
+
# }
|
48
|
+
# backoff_strategy type: :exponential, start: 3
|
48
49
|
#
|
49
50
|
# def give_me_food
|
50
51
|
# call_api_with_retry do
|
@@ -55,25 +56,40 @@ module Take2
|
|
55
56
|
# end
|
56
57
|
#
|
57
58
|
# end
|
58
|
-
def call_api_with_retry(options = {})
|
59
|
+
def call_api_with_retry(options = {})
|
59
60
|
config = self.class.retriable_configuration
|
60
|
-
config.merge!
|
61
|
+
config.merge!(Take2.local_defaults(options)) unless options.empty?
|
61
62
|
tries ||= config[:retries]
|
62
63
|
begin
|
63
64
|
yield
|
64
65
|
rescue => e
|
65
|
-
if config[:retriable].map {|klass| e.class <= klass }.any?
|
66
|
+
if config[:retriable].map { |klass| e.class <= klass }.any?
|
66
67
|
unless tries.zero? || config[:retry_condition_proc]&.call(e)
|
67
68
|
config[:retry_proc]&.call(e, tries)
|
68
|
-
|
69
|
+
rest(config, tries)
|
69
70
|
tries -= 1
|
70
71
|
retry
|
71
72
|
end
|
72
|
-
end
|
73
|
+
end
|
73
74
|
raise e
|
74
75
|
end
|
75
76
|
end
|
76
|
-
|
77
|
+
alias_method :with_retry, :call_api_with_retry
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def rest(config, tries)
|
82
|
+
seconds = if config[:time_to_sleep].to_f > 0
|
83
|
+
config[:time_to_sleep].to_f
|
84
|
+
else
|
85
|
+
next_interval(config[:backoff_intervals], config[:retries], tries)
|
86
|
+
end
|
87
|
+
sleep(seconds)
|
88
|
+
end
|
89
|
+
|
90
|
+
def next_interval(intervals, retries, current)
|
91
|
+
intervals[retries - current]
|
92
|
+
end
|
77
93
|
end
|
78
94
|
|
79
95
|
module ClassMethods
|
@@ -101,11 +117,12 @@ module Take2
|
|
101
117
|
# Arguments:
|
102
118
|
# errors: List of retiable errors
|
103
119
|
def retriable_errors(*errors)
|
104
|
-
|
120
|
+
message = 'All retriable errors must be StandardError decendants'
|
121
|
+
raise ArgumentError, message unless errors.all? { |e| e <= StandardError }
|
105
122
|
self.retriable = errors
|
106
123
|
end
|
107
124
|
|
108
|
-
# Sets condition for retry attempt.
|
125
|
+
# Sets condition for retry attempt.
|
109
126
|
# If set, it MUST result to +false+ with number left retries greater that zero in order to retry.
|
110
127
|
#
|
111
128
|
# Example:
|
@@ -120,7 +137,7 @@ module Take2
|
|
120
137
|
self.retry_condition_proc = proc
|
121
138
|
end
|
122
139
|
|
123
|
-
# Defines a proc that is called *before* retry attempt.
|
140
|
+
# Defines a proc that is called *before* retry attempt.
|
124
141
|
#
|
125
142
|
# Example:
|
126
143
|
# class PizzaService
|
@@ -134,18 +151,28 @@ module Take2
|
|
134
151
|
self.retry_proc = proc
|
135
152
|
end
|
136
153
|
|
137
|
-
|
154
|
+
def sleep_before_retry(seconds)
|
155
|
+
unless (seconds.is_a?(Integer) || seconds.is_a?(Float)) && seconds.positive?
|
156
|
+
raise ArgumentError, 'Must be positive numer'
|
157
|
+
end
|
158
|
+
puts "DEPRECATION MESSAGE - The sleep_before_retry method is softly deprecated in favor of backoff_stategy \r
|
159
|
+
where the time to sleep is a starting point on the backoff intervals. Please implement it instead."
|
160
|
+
self.time_to_sleep = seconds
|
161
|
+
end
|
162
|
+
|
163
|
+
# Sets the backoff strategy
|
138
164
|
#
|
139
165
|
# Example:
|
140
166
|
# class PizzaService
|
141
167
|
# include Take2
|
142
|
-
#
|
168
|
+
# backoff_strategy type: :exponential, start: 3
|
143
169
|
# end
|
144
170
|
# Arguments:
|
145
|
-
#
|
146
|
-
def
|
147
|
-
|
148
|
-
|
171
|
+
# hash: object
|
172
|
+
def backoff_strategy(options)
|
173
|
+
available_types = [:constant, :linear, :fibonacci, :exponential]
|
174
|
+
raise ArgumentError, 'Incorrect backoff type' unless available_types.include?(options[:type])
|
175
|
+
self.backoff_intervals = Backoff.new(options[:type], options[:start]).intervals
|
149
176
|
end
|
150
177
|
|
151
178
|
# Exposes current class configuration
|
@@ -167,10 +194,8 @@ module Take2
|
|
167
194
|
end
|
168
195
|
|
169
196
|
def response_status(response)
|
170
|
-
return response.status if response.respond_to?
|
171
|
-
response.status_code if response.respond_to?
|
197
|
+
return response.status if response.respond_to?(:status)
|
198
|
+
response.status_code if response.respond_to?(:status_code)
|
172
199
|
end
|
173
|
-
|
174
200
|
end
|
175
|
-
|
176
|
-
end
|
201
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Take2
|
4
|
+
class Backoff
|
5
|
+
attr_reader :type, :start, :retries, :factor, :intervals
|
6
|
+
|
7
|
+
def initialize(type, start = 1, factor = 1, retries = 10)
|
8
|
+
@type = type
|
9
|
+
@start = start.to_i
|
10
|
+
@retries = retries
|
11
|
+
@factor = factor
|
12
|
+
@intervals = intervals_table
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def intervals_table
|
18
|
+
send(type)
|
19
|
+
end
|
20
|
+
|
21
|
+
def constant
|
22
|
+
Array.new(retries, start)
|
23
|
+
end
|
24
|
+
|
25
|
+
def linear
|
26
|
+
(start...(retries + start)).map { |i| i * factor }
|
27
|
+
end
|
28
|
+
|
29
|
+
def fibonacci
|
30
|
+
(1..20).map { |i| fibo(i) }.partition { |x| x >= start }.first.take(retries)
|
31
|
+
end
|
32
|
+
|
33
|
+
def exponential
|
34
|
+
(1..20).each_with_index.inject([]) do |memo, (el, ix)|
|
35
|
+
memo << if ix == 0
|
36
|
+
start
|
37
|
+
else
|
38
|
+
(2**el - 1) + rand(1..2**el)
|
39
|
+
end
|
40
|
+
end.take(retries)
|
41
|
+
end
|
42
|
+
|
43
|
+
def fibo(n, memo = {})
|
44
|
+
return n if n < 2
|
45
|
+
memo[n] ||= fibo(n - 1, memo) + fibo(n - 2, memo)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/take2/configuration.rb
CHANGED
@@ -1,6 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'take2/backoff'
|
4
|
+
|
1
5
|
module Take2
|
2
6
|
class Configuration
|
3
|
-
CONFIG_ATTRS = [:retries,
|
7
|
+
CONFIG_ATTRS = [:retries,
|
8
|
+
:retriable,
|
9
|
+
:retry_proc,
|
10
|
+
:retry_condition_proc,
|
11
|
+
:time_to_sleep,
|
12
|
+
:backoff_setup,
|
13
|
+
:backoff_intervals].freeze
|
14
|
+
|
4
15
|
attr_accessor(*CONFIG_ATTRS)
|
5
16
|
|
6
17
|
def initialize(options = {})
|
@@ -8,13 +19,15 @@ module Take2
|
|
8
19
|
@retries = 3
|
9
20
|
@retriable = [
|
10
21
|
Net::HTTPServerException,
|
11
|
-
Net::HTTPRetriableError,
|
22
|
+
Net::HTTPRetriableError,
|
12
23
|
Errno::ECONNRESET,
|
13
24
|
IOError,
|
14
|
-
|
25
|
+
].freeze
|
15
26
|
@retry_proc = proc {}
|
16
27
|
@retry_condition_proc = proc { false }
|
17
|
-
@time_to_sleep =
|
28
|
+
@time_to_sleep = 0 # TODO: Soft deprecate time to sleep
|
29
|
+
@backoff_setup = { type: :constant, start: 3 }
|
30
|
+
@backoff_intervals = Backoff.new(*@backoff_setup.values).intervals
|
18
31
|
# Overwriting the defaults
|
19
32
|
validate_options(options, &setter)
|
20
33
|
end
|
@@ -26,29 +39,41 @@ module Take2
|
|
26
39
|
end
|
27
40
|
|
28
41
|
def [](value)
|
29
|
-
|
42
|
+
public_send(value)
|
30
43
|
end
|
31
44
|
|
32
|
-
def validate_options(options
|
45
|
+
def validate_options(options)
|
33
46
|
options.each do |k, v|
|
34
|
-
raise ArgumentError, "#{k} is not a valid configuration"
|
47
|
+
raise ArgumentError, "#{k} is not a valid configuration" unless CONFIG_ATTRS.include?(k)
|
35
48
|
case k
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
when :retries
|
50
|
+
raise ArgumentError, "#{k} must be positive integer" unless v.is_a?(Integer) && v.positive?
|
51
|
+
when :time_to_sleep
|
52
|
+
raise ArgumentError, "#{k} must be positive number" unless (v.is_a?(Integer) || v.is_a?(Float)) && v >= 0
|
53
|
+
when :retriable
|
54
|
+
raise ArgumentError, "#{k} must be array of retriable errors" unless v.is_a?(Array)
|
55
|
+
when :retry_proc, :retry_condition_proc
|
56
|
+
raise ArgumentError, "#{k} must be Proc" unless v.is_a?(Proc)
|
57
|
+
when :backoff_setup
|
58
|
+
available_types = [:constant, :linear, :fibonacci, :exponential]
|
59
|
+
raise ArgumentError, 'Incorrect backoff type' unless available_types.include?(v[:type])
|
44
60
|
end
|
45
|
-
|
46
|
-
end
|
61
|
+
yield(k, v) if block_given?
|
62
|
+
end
|
47
63
|
end
|
48
64
|
|
49
65
|
def setter
|
50
|
-
|
66
|
+
->(key, value) {
|
67
|
+
if key == :backoff_setup
|
68
|
+
assign_backoff_intervals(value)
|
69
|
+
else
|
70
|
+
public_send("#{key}=", value)
|
71
|
+
end
|
72
|
+
}
|
51
73
|
end
|
52
74
|
|
75
|
+
def assign_backoff_intervals(backoff_setup)
|
76
|
+
@backoff_intervals = Backoff.new(backoff_setup[:type], backoff_setup[:start]).intervals
|
77
|
+
end
|
53
78
|
end
|
54
|
-
end
|
79
|
+
end
|
data/lib/take2/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,124 +1,114 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'spec_helper'
|
4
4
|
|
5
|
+
RSpec.describe(Take2::Configuration) do
|
5
6
|
describe 'default configurations' do
|
6
|
-
|
7
7
|
let(:default) { described_class.new }
|
8
8
|
|
9
9
|
it 'has correct default value for retries' do
|
10
|
-
expect(default.retries).to
|
10
|
+
expect(default.retries).to(eql(3))
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'has correct default retriable errors array' do
|
14
|
-
expect(default.retriable).to
|
14
|
+
expect(default.retriable).to(eql([
|
15
15
|
Net::HTTPServerException,
|
16
|
-
Net::HTTPRetriableError,
|
16
|
+
Net::HTTPRetriableError,
|
17
17
|
Errno::ECONNRESET,
|
18
18
|
IOError,
|
19
|
-
|
19
|
+
].freeze))
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'has default proc for retry_proc' do
|
23
23
|
p = proc {}
|
24
|
-
expect(default.retry_proc.call).to
|
24
|
+
expect(default.retry_proc.call).to(eql(p.call))
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'has default proc for retry_condition_proc' do
|
28
|
-
p = proc {false}
|
29
|
-
expect(default.retry_condition_proc.call).to
|
28
|
+
p = proc { false }
|
29
|
+
expect(default.retry_condition_proc.call).to(eql(p.call))
|
30
30
|
end
|
31
31
|
|
32
32
|
it 'has correct default value for time_to_sleep' do
|
33
|
-
expect(default.time_to_sleep).to
|
33
|
+
expect(default.time_to_sleep).to(eql(0))
|
34
34
|
end
|
35
35
|
|
36
|
+
it 'has correct default value for backoff_intervals' do
|
37
|
+
expect(default.backoff_intervals).to eql Array.new(10, 3)
|
38
|
+
end
|
36
39
|
end
|
37
40
|
|
38
41
|
describe 'overwriting the default configurations' do
|
39
|
-
|
40
42
|
context 'with valid hash' do
|
41
|
-
|
42
|
-
let!(:new_configs_hash) {
|
43
|
+
let!(:new_configs_hash) do
|
43
44
|
{
|
44
45
|
retries: 2,
|
45
46
|
retriable: [Net::HTTPRetriableError],
|
46
47
|
retry_condition_proc: proc { true },
|
47
|
-
retry_proc: proc { 2*2 },
|
48
|
-
time_to_sleep: 0
|
48
|
+
retry_proc: proc { 2 * 2 },
|
49
|
+
time_to_sleep: 0,
|
50
|
+
backoff_setup: { type: :linear, start: 3 }
|
49
51
|
}
|
50
|
-
|
52
|
+
end
|
51
53
|
|
52
54
|
let!(:new_configuration) { described_class.new(new_configs_hash).to_hash }
|
53
55
|
|
54
56
|
[:retries, :retriable, :retry_proc, :retry_condition_proc, :time_to_sleep].each do |key|
|
55
57
|
it "sets the #{key} key" do
|
56
58
|
if new_configs_hash[key].respond_to?(:call)
|
57
|
-
expect(new_configuration[key].call).to
|
58
|
-
else
|
59
|
-
expect(new_configuration[key]).to
|
59
|
+
expect(new_configuration[key].call).to(eql(new_configs_hash[key].call))
|
60
|
+
else
|
61
|
+
expect(new_configuration[key]).to(eql(new_configs_hash[key]))
|
60
62
|
end
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
66
|
+
it 'sets the backoff_intervals correctly' do
|
67
|
+
expect(new_configuration[:backoff_intervals])
|
68
|
+
.to eql(Take2::Backoff.new(
|
69
|
+
new_configs_hash[:backoff_setup][:type],
|
70
|
+
new_configs_hash[:backoff_setup][:start]
|
71
|
+
).intervals)
|
72
|
+
end
|
64
73
|
end
|
65
74
|
|
66
75
|
context 'with invalid hash' do
|
67
|
-
|
68
76
|
context 'when retries set to invalid value' do
|
69
|
-
|
70
77
|
it 'raises ArgumentError' do
|
71
|
-
|
72
|
-
expect { described_class.new(retries:
|
73
|
-
expect { described_class.new(retries: 0) }.to raise_error ArgumentError
|
74
|
-
|
78
|
+
expect { described_class.new(retries: -1) }.to(raise_error(ArgumentError))
|
79
|
+
expect { described_class.new(retries: 0) }.to(raise_error(ArgumentError))
|
75
80
|
end
|
76
|
-
|
77
81
|
end
|
78
82
|
|
79
83
|
context 'when time_to_sleep set to invalid value' do
|
80
|
-
|
81
84
|
it 'raises ArgumentError' do
|
82
|
-
|
83
|
-
expect { described_class.new(time_to_sleep: -1) }.to raise_error ArgumentError
|
84
|
-
|
85
|
+
expect { described_class.new(time_to_sleep: -1) }.to(raise_error(ArgumentError))
|
85
86
|
end
|
86
|
-
|
87
87
|
end
|
88
88
|
|
89
89
|
context 'when retriable set to invalid value' do
|
90
|
-
|
91
90
|
it 'raises ArgumentError' do
|
92
|
-
|
93
|
-
expect { described_class.new(retriable: StandardError) }.to raise_error ArgumentError
|
94
|
-
|
91
|
+
expect { described_class.new(retriable: StandardError) }.to(raise_error(ArgumentError))
|
95
92
|
end
|
96
|
-
|
97
93
|
end
|
98
94
|
|
99
95
|
context 'when retry_proc set to invalid value' do
|
100
|
-
|
101
96
|
it 'raises ArgumentError' do
|
102
|
-
|
103
|
-
expect { described_class.new(retry_proc: {}) }.to raise_error ArgumentError
|
104
|
-
|
97
|
+
expect { described_class.new(retry_proc: {}) }.to(raise_error(ArgumentError))
|
105
98
|
end
|
106
|
-
|
107
99
|
end
|
108
100
|
|
109
101
|
context 'when retry_condition_proc set to invalid value' do
|
110
|
-
|
111
102
|
it 'raises ArgumentError' do
|
112
|
-
|
113
|
-
expect { described_class.new(retry_condition_proc: {}) }.to raise_error ArgumentError
|
114
|
-
|
103
|
+
expect { described_class.new(retry_condition_proc: {}) }.to(raise_error(ArgumentError))
|
115
104
|
end
|
105
|
+
end
|
116
106
|
|
107
|
+
context 'when backoff_setup has incorrect type' do
|
108
|
+
it 'raises ArgumentError' do
|
109
|
+
expect { described_class.new(backoff_setup: { type: :log }) }.to(raise_error(ArgumentError))
|
110
|
+
end
|
117
111
|
end
|
118
|
-
|
119
112
|
end
|
120
|
-
|
121
113
|
end
|
122
|
-
|
123
|
-
|
124
|
-
end
|
114
|
+
end
|