retriable 3.5.0 → 3.5.1
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 +2 -0
- data/lib/retriable/config.rb +28 -0
- data/lib/retriable/exponential_backoff.rb +14 -0
- data/lib/retriable/validation.rb +41 -0
- data/lib/retriable/version.rb +1 -1
- data/lib/retriable.rb +3 -0
- data/spec/config_spec.rb +9 -0
- data/spec/retriable_spec.rb +31 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0cb0bb3751f465d22addceebf5466b165871741e5820c7c3291d2e842f6f48a
|
|
4
|
+
data.tar.gz: 8bef0641e9c3b0e39c04d79ffcbdaf4542790264ce6648f6540db933cfddbc54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6c4e07023a30aa934d5397717344c480fb6af8dd56349fcbcb9b85b9ba5539a3f16d2b16a30cbb3682e4140a04f3ace9ecbf0602e68bc4a1a5c805230a031de6
|
|
7
|
+
data.tar.gz: 4272451f1213b81537fd8f7cd85ced8a341b8f8dbf228493a42393b74cee3400e90eb9aa466ec3e404b98eef78b2a42ade5600b49c5d8d9ba7dd9e9b4ae1f444
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# HEAD
|
|
2
2
|
|
|
3
|
+
## 3.5.1
|
|
4
|
+
|
|
5
|
+
- Fix: Validate retry timing and count options before use to reject invalid retry configurations. `tries` must now be a positive integer unless a custom `intervals` array is provided.
|
|
6
|
+
|
|
3
7
|
## 3.5.0
|
|
4
8
|
|
|
5
9
|
- Fix: Do not count skipped sleep intervals against `max_elapsed_time` when `sleep_disabled` is true.
|
data/README.md
CHANGED
|
@@ -94,6 +94,8 @@ Here are the available options, in some vague order of relevance to most common
|
|
|
94
94
|
| **`intervals`** | `nil` | Skip generated intervals and provide your own array of intervals in seconds. [Read more](#custom-interval-array). |
|
|
95
95
|
| **`timeout`** | `nil` | Number of seconds to allow the code block to run before raising a `Timeout::Error` inside each try. `nil` means the code block can run forever without raising error. The implementation uses `Timeout::timeout`, which may be [unsafe](https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/) [and](http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html) [even](https://adamhooper.medium.com/in-ruby-dont-use-timeout-77d9d4e5a001) [dangerous](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). Proceed with caution. |
|
|
96
96
|
|
|
97
|
+
Timing options are validated before retrying. `tries` must be a positive integer when Retriable generates intervals. `base_interval`, `max_interval`, `multiplier`, `max_elapsed_time`, and `timeout` must be non-negative numbers, with `max_elapsed_time` and `timeout` also accepting `nil`. `rand_factor` must be a number from `0` through `1`. If provided, `intervals` must be an array of non-negative numbers; because it replaces generated intervals, it also overrides `tries`, `base_interval`, `max_interval`, `rand_factor`, and `multiplier` validation.
|
|
98
|
+
|
|
97
99
|
#### Configuring Which Options to Retry With :on
|
|
98
100
|
|
|
99
101
|
**`:on`** Can take the form:
|
data/lib/retriable/config.rb
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "exponential_backoff"
|
|
4
|
+
require_relative "validation"
|
|
4
5
|
|
|
5
6
|
module Retriable
|
|
6
7
|
class Config
|
|
8
|
+
include Validation
|
|
9
|
+
|
|
7
10
|
ATTRIBUTES = (ExponentialBackoff::ATTRIBUTES + %i[
|
|
8
11
|
sleep_disabled
|
|
9
12
|
max_elapsed_time
|
|
@@ -39,6 +42,8 @@ module Retriable
|
|
|
39
42
|
|
|
40
43
|
instance_variable_set(:"@#{k}", v)
|
|
41
44
|
end
|
|
45
|
+
|
|
46
|
+
validate!
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
def to_h
|
|
@@ -46,5 +51,28 @@ module Retriable
|
|
|
46
51
|
hash[key] = public_send(key)
|
|
47
52
|
end
|
|
48
53
|
end
|
|
54
|
+
|
|
55
|
+
def validate!
|
|
56
|
+
validate_optional_non_negative_number(:max_elapsed_time, max_elapsed_time)
|
|
57
|
+
validate_optional_non_negative_number(:timeout, timeout)
|
|
58
|
+
validate_intervals
|
|
59
|
+
return if intervals
|
|
60
|
+
|
|
61
|
+
validate_positive_integer(:tries, tries)
|
|
62
|
+
validate_non_negative_number(:base_interval, base_interval)
|
|
63
|
+
validate_non_negative_number(:multiplier, multiplier)
|
|
64
|
+
validate_non_negative_number(:max_interval, max_interval)
|
|
65
|
+
validate_rand_factor
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def validate_intervals
|
|
71
|
+
return if intervals.nil?
|
|
72
|
+
raise ArgumentError, "intervals must be an Array" unless intervals.is_a?(Array)
|
|
73
|
+
return if intervals.all? { |interval| finite_number?(interval) && interval >= 0 }
|
|
74
|
+
|
|
75
|
+
raise ArgumentError, "intervals must contain only non-negative numbers"
|
|
76
|
+
end
|
|
49
77
|
end
|
|
50
78
|
end
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "validation"
|
|
4
|
+
|
|
3
5
|
module Retriable
|
|
4
6
|
class ExponentialBackoff
|
|
7
|
+
include Validation
|
|
8
|
+
|
|
5
9
|
ATTRIBUTES = %i[
|
|
6
10
|
tries
|
|
7
11
|
base_interval
|
|
@@ -24,6 +28,8 @@ module Retriable
|
|
|
24
28
|
|
|
25
29
|
instance_variable_set(:"@#{k}", v)
|
|
26
30
|
end
|
|
31
|
+
|
|
32
|
+
validate!
|
|
27
33
|
end
|
|
28
34
|
|
|
29
35
|
def intervals
|
|
@@ -38,6 +44,14 @@ module Retriable
|
|
|
38
44
|
|
|
39
45
|
private
|
|
40
46
|
|
|
47
|
+
def validate!
|
|
48
|
+
validate_non_negative_integer(:tries, tries)
|
|
49
|
+
validate_non_negative_number(:base_interval, base_interval)
|
|
50
|
+
validate_non_negative_number(:multiplier, multiplier)
|
|
51
|
+
validate_non_negative_number(:max_interval, max_interval)
|
|
52
|
+
validate_rand_factor
|
|
53
|
+
end
|
|
54
|
+
|
|
41
55
|
def randomize(interval)
|
|
42
56
|
delta = rand_factor * interval.to_f
|
|
43
57
|
min = interval - delta
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Retriable
|
|
4
|
+
module Validation
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def validate_positive_integer(name, value)
|
|
8
|
+
return if value.is_a?(Integer) && value.positive?
|
|
9
|
+
|
|
10
|
+
raise ArgumentError, "#{name} must be a positive integer"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def validate_non_negative_integer(name, value)
|
|
14
|
+
return if value.is_a?(Integer) && value >= 0
|
|
15
|
+
|
|
16
|
+
raise ArgumentError, "#{name} must be a non-negative integer"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def validate_non_negative_number(name, value)
|
|
20
|
+
return if finite_number?(value) && value >= 0
|
|
21
|
+
|
|
22
|
+
raise ArgumentError, "#{name} must be a non-negative number"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validate_optional_non_negative_number(name, value)
|
|
26
|
+
return if value.nil?
|
|
27
|
+
|
|
28
|
+
validate_non_negative_number(name, value)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def validate_rand_factor
|
|
32
|
+
return if finite_number?(rand_factor) && rand_factor >= 0 && rand_factor <= 1
|
|
33
|
+
|
|
34
|
+
raise ArgumentError, "rand_factor must be between 0 and 1"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def finite_number?(value)
|
|
38
|
+
value.is_a?(Numeric) && value.to_f.finite?
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/retriable/version.rb
CHANGED
data/lib/retriable.rb
CHANGED
|
@@ -47,6 +47,9 @@ module Retriable
|
|
|
47
47
|
Config.new(apply_override_options(config.to_h.merge(opts), @override_config))
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
# Config is mutable through `configure`, so validate again immediately before use.
|
|
51
|
+
local_config.validate!
|
|
52
|
+
|
|
50
53
|
tries = local_config.tries
|
|
51
54
|
intervals = build_intervals(local_config, tries)
|
|
52
55
|
timeout = local_config.timeout
|
data/spec/config_spec.rb
CHANGED
|
@@ -56,4 +56,13 @@ describe Retriable::Config do
|
|
|
56
56
|
it "raises errors on invalid configuration" do
|
|
57
57
|
expect { described_class.new(does_not_exist: 123) }.to raise_error(ArgumentError, /not a valid option/)
|
|
58
58
|
end
|
|
59
|
+
|
|
60
|
+
it "raises errors on invalid timing configuration" do
|
|
61
|
+
expect { described_class.new(rand_factor: 1.1) }.to raise_error(ArgumentError, /rand_factor/)
|
|
62
|
+
expect { described_class.new(timeout: -1) }.to raise_error(ArgumentError, /timeout/)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "raises errors when intervals is not an array" do
|
|
66
|
+
expect { described_class.new(intervals: "1") }.to raise_error(ArgumentError, /intervals must be an Array/)
|
|
67
|
+
end
|
|
59
68
|
end
|
data/spec/retriable_spec.rb
CHANGED
|
@@ -375,6 +375,37 @@ describe Retriable do
|
|
|
375
375
|
it "raises ArgumentError on invalid options" do
|
|
376
376
|
expect { described_class.retriable(does_not_exist: 123) { increment_tries } }.to raise_error(ArgumentError)
|
|
377
377
|
end
|
|
378
|
+
|
|
379
|
+
it "raises ArgumentError when tries is not a positive integer" do
|
|
380
|
+
expect { described_class.retriable(tries: 1.5) { increment_tries } }
|
|
381
|
+
.to raise_error(ArgumentError, /tries/)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
it "raises ArgumentError when an interval is negative" do
|
|
385
|
+
expect { described_class.retriable(intervals: [-1]) { increment_tries } }
|
|
386
|
+
.to raise_error(ArgumentError, /intervals/)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
it "raises ArgumentError when configured timing options become invalid" do
|
|
390
|
+
described_class.configure { |config| config.tries = 0 }
|
|
391
|
+
|
|
392
|
+
expect { described_class.retriable { increment_tries } }
|
|
393
|
+
.to raise_error(ArgumentError, /tries/)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
it "does not validate generated backoff options when intervals are provided" do
|
|
397
|
+
described_class.retriable(intervals: [0], tries: 0, rand_factor: 1.1) { increment_tries }
|
|
398
|
+
|
|
399
|
+
expect(@tries).to eq(1)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
it "allows an empty interval array as one attempt" do
|
|
403
|
+
expect do
|
|
404
|
+
described_class.retriable(intervals: []) { increment_tries_with_exception }
|
|
405
|
+
end.to raise_error(StandardError)
|
|
406
|
+
|
|
407
|
+
expect(@tries).to eq(1)
|
|
408
|
+
end
|
|
378
409
|
end
|
|
379
410
|
|
|
380
411
|
context "#configure" do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: retriable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.5.
|
|
4
|
+
version: 3.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jack Chu
|
|
@@ -78,6 +78,7 @@ files:
|
|
|
78
78
|
- lib/retriable/config.rb
|
|
79
79
|
- lib/retriable/core_ext/kernel.rb
|
|
80
80
|
- lib/retriable/exponential_backoff.rb
|
|
81
|
+
- lib/retriable/validation.rb
|
|
81
82
|
- lib/retriable/version.rb
|
|
82
83
|
- retriable.gemspec
|
|
83
84
|
- sig/retriable.rbs
|