retriable 3.2.1 → 3.4.0
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/.github/workflows/main.yml +2 -2
- data/.gitignore +2 -0
- data/.rubocop.yml +4 -6
- data/CHANGELOG.md +11 -1
- data/README.md +28 -1
- data/lib/retriable/config.rb +10 -8
- data/lib/retriable/exponential_backoff.rb +7 -6
- data/lib/retriable/version.rb +1 -1
- data/lib/retriable.rb +91 -35
- data/retriable.gemspec +5 -2
- data/spec/config_spec.rb +4 -0
- data/spec/retriable_spec.rb +93 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c6c4929adafa9b223f3fbe0ee530ef8a7d1e764cac8f5605be9ce33e3d1b0cc
|
|
4
|
+
data.tar.gz: 5d7013b0d5ce9ab8514a8deab7a32d77b0422cc02ead4aceb5a8c9b5ba28bff1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a83302cfe426348ae0a35e6410edce33c8985d49289ab06100be7c5d49a586f93a429632814b2129e67b20dde8963eb2f5941bcc59d12f4c1db56ce05fd6784
|
|
7
|
+
data.tar.gz: 792c54b8b8805eb11c93d9ac100671b357b91a465424939a5c3995a2a50a8449033a0e94138bdf2510b2a7f89b4dc35787b0c490c53d63c9a0d2b85f044c80a9
|
data/.github/workflows/main.yml
CHANGED
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 2.3
|
|
4
|
+
|
|
1
5
|
Style/StringLiterals:
|
|
2
6
|
EnforcedStyle: double_quotes
|
|
3
7
|
|
|
@@ -13,12 +17,6 @@ Style/TrailingCommaInArguments:
|
|
|
13
17
|
Lint/InheritException:
|
|
14
18
|
Enabled: false
|
|
15
19
|
|
|
16
|
-
Layout/FirstArrayElementIndentation:
|
|
17
|
-
Enabled: false
|
|
18
|
-
|
|
19
|
-
Layout/FirstHashElementIndentation:
|
|
20
|
-
Enabled: false
|
|
21
|
-
|
|
22
20
|
Style/NegatedIf:
|
|
23
21
|
Enabled: false
|
|
24
22
|
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
# HEAD
|
|
2
2
|
|
|
3
|
+
## 3.4.0
|
|
4
|
+
|
|
5
|
+
- Add `retry_if` option to support custom retry predicates, including checks against wrapped `exception.cause` values.
|
|
6
|
+
|
|
7
|
+
## 3.3.0
|
|
8
|
+
|
|
9
|
+
- Refactor `Retriable.retriable` internals into focused private helpers to improve readability while preserving behavior.
|
|
10
|
+
- Modernize `.rubocop.yml` with explicit modern defaults to enable new cops while preserving existing project style policies.
|
|
11
|
+
|
|
3
12
|
## 3.2.1
|
|
4
|
-
|
|
13
|
+
|
|
14
|
+
- Remove executables from gemspec as it was polluting the path for some users. Thanks @hsbt.
|
|
5
15
|
|
|
6
16
|
## 3.2.0
|
|
7
17
|
|
data/README.md
CHANGED
|
@@ -32,7 +32,7 @@ require 'retriable'
|
|
|
32
32
|
In your Gemfile:
|
|
33
33
|
|
|
34
34
|
```ruby
|
|
35
|
-
gem 'retriable', '~> 3.
|
|
35
|
+
gem 'retriable', '~> 3.4'
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## Usage
|
|
@@ -83,6 +83,7 @@ Here are the available options, in some vague order of relevance to most common
|
|
|
83
83
|
| ---------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
84
|
| **`tries`** | `3` | Number of attempts to make at running your code block (includes initial attempt). |
|
|
85
85
|
| **`on`** | `[StandardError]` | Type of exceptions to retry. [Read more](#configuring-which-options-to-retry-with-on). |
|
|
86
|
+
| **`retry_if`** | `nil` | Callable (for example a `Proc` or lambda) that receives the rescued exception and returns true/false to decide whether to retry. [Read more](#advanced-retry-matching-with-retry_if). |
|
|
86
87
|
| **`on_retry`** | `nil` | `Proc` to call after each try is rescued. [Read more](#callbacks). |
|
|
87
88
|
| **`sleep_disabled`** | `false` | When true, disable exponential backoff and attempt retries immediately. |
|
|
88
89
|
| **`base_interval`** | `0.5` | The initial interval in seconds between tries. |
|
|
@@ -104,6 +105,32 @@ Here are the available options, in some vague order of relevance to most common
|
|
|
104
105
|
- A single `Regexp` pattern (retries exceptions ONLY if their `message` matches the pattern)
|
|
105
106
|
- An array of patterns (retries exceptions ONLY if their `message` matches at least one of the patterns)
|
|
106
107
|
|
|
108
|
+
#### Advanced Retry Matching With :retry_if
|
|
109
|
+
|
|
110
|
+
Use **`:retry_if`** when retry logic depends on details that `:on` does not cover. The Proc receives the rescued exception and should return `true` to retry or `false` to re-raise immediately.
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
def caused_by?(error, klass)
|
|
114
|
+
current = error
|
|
115
|
+
while current
|
|
116
|
+
return true if current.is_a?(klass)
|
|
117
|
+
|
|
118
|
+
current = current.cause
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
Retriable.retriable(
|
|
125
|
+
on: [Faraday::ConnectionFailed],
|
|
126
|
+
retry_if: ->(exception) { caused_by?(exception, Errno::ECONNRESET) }
|
|
127
|
+
) do
|
|
128
|
+
# code here...
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`:retry_if` runs after the exception type has matched `:on`.
|
|
133
|
+
|
|
107
134
|
### Configuration
|
|
108
135
|
|
|
109
136
|
You can change the global defaults with a `#configure` block:
|
data/lib/retriable/config.rb
CHANGED
|
@@ -4,14 +4,15 @@ require_relative "exponential_backoff"
|
|
|
4
4
|
|
|
5
5
|
module Retriable
|
|
6
6
|
class Config
|
|
7
|
-
ATTRIBUTES = (ExponentialBackoff::ATTRIBUTES + [
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
ATTRIBUTES = (ExponentialBackoff::ATTRIBUTES + %i[
|
|
8
|
+
sleep_disabled
|
|
9
|
+
max_elapsed_time
|
|
10
|
+
intervals
|
|
11
|
+
timeout
|
|
12
|
+
on
|
|
13
|
+
retry_if
|
|
14
|
+
on_retry
|
|
15
|
+
contexts
|
|
15
16
|
]).freeze
|
|
16
17
|
|
|
17
18
|
attr_accessor(*ATTRIBUTES)
|
|
@@ -29,6 +30,7 @@ module Retriable
|
|
|
29
30
|
@intervals = nil
|
|
30
31
|
@timeout = nil
|
|
31
32
|
@on = [StandardError]
|
|
33
|
+
@retry_if = nil
|
|
32
34
|
@on_retry = nil
|
|
33
35
|
@contexts = {}
|
|
34
36
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Retriable
|
|
4
4
|
class ExponentialBackoff
|
|
5
|
-
ATTRIBUTES = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
ATTRIBUTES = %i[
|
|
6
|
+
tries
|
|
7
|
+
base_interval
|
|
8
|
+
multiplier
|
|
9
|
+
max_interval
|
|
10
|
+
rand_factor
|
|
11
11
|
].freeze
|
|
12
12
|
|
|
13
13
|
attr_accessor(*ATTRIBUTES)
|
|
@@ -21,6 +21,7 @@ module Retriable
|
|
|
21
21
|
|
|
22
22
|
opts.each do |k, v|
|
|
23
23
|
raise ArgumentError, "#{k} is not a valid option" if !ATTRIBUTES.include?(k)
|
|
24
|
+
|
|
24
25
|
instance_variable_set(:"@#{k}", v)
|
|
25
26
|
end
|
|
26
27
|
end
|
data/lib/retriable/version.rb
CHANGED
data/lib/retriable.rb
CHANGED
|
@@ -18,64 +18,120 @@ module Retriable
|
|
|
18
18
|
|
|
19
19
|
def with_context(context_key, options = {}, &block)
|
|
20
20
|
if !config.contexts.key?(context_key)
|
|
21
|
-
raise ArgumentError,
|
|
21
|
+
raise ArgumentError,
|
|
22
|
+
"#{context_key} not found in Retriable.config.contexts. Available contexts: #{config.contexts.keys}"
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
return unless block_given?
|
|
26
|
+
|
|
27
|
+
retriable(config.contexts[context_key].merge(options), &block)
|
|
25
28
|
end
|
|
26
29
|
|
|
27
|
-
def retriable(opts = {})
|
|
30
|
+
def retriable(opts = {}, &block)
|
|
28
31
|
local_config = opts.empty? ? config : Config.new(config.to_h.merge(opts))
|
|
29
32
|
|
|
30
|
-
tries
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
on = local_config.on
|
|
39
|
-
on_retry = local_config.on_retry
|
|
40
|
-
sleep_disabled = local_config.sleep_disabled
|
|
33
|
+
tries = local_config.tries
|
|
34
|
+
intervals = build_intervals(local_config, tries)
|
|
35
|
+
timeout = local_config.timeout
|
|
36
|
+
on = local_config.on
|
|
37
|
+
retry_if = local_config.retry_if
|
|
38
|
+
on_retry = local_config.on_retry
|
|
39
|
+
sleep_disabled = local_config.sleep_disabled
|
|
40
|
+
max_elapsed_time = local_config.max_elapsed_time
|
|
41
41
|
|
|
42
42
|
exception_list = on.is_a?(Hash) ? on.keys : on
|
|
43
|
+
exception_list = [*exception_list]
|
|
43
44
|
start_time = Time.now
|
|
44
45
|
elapsed_time = -> { Time.now - start_time }
|
|
45
46
|
|
|
46
|
-
if !intervals
|
|
47
|
-
intervals = ExponentialBackoff.new(
|
|
48
|
-
tries: tries - 1,
|
|
49
|
-
base_interval: base_interval,
|
|
50
|
-
multiplier: multiplier,
|
|
51
|
-
max_interval: max_interval,
|
|
52
|
-
rand_factor: rand_factor
|
|
53
|
-
).intervals
|
|
54
|
-
end
|
|
55
|
-
|
|
56
47
|
tries = intervals.size + 1
|
|
57
48
|
|
|
49
|
+
execute_tries(
|
|
50
|
+
tries: tries, intervals: intervals, timeout: timeout,
|
|
51
|
+
exception_list: exception_list, on: on, retry_if: retry_if, on_retry: on_retry,
|
|
52
|
+
elapsed_time: elapsed_time, max_elapsed_time: max_elapsed_time,
|
|
53
|
+
sleep_disabled: sleep_disabled, &block
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def execute_tries( # rubocop:disable Metrics/ParameterLists
|
|
58
|
+
tries:, intervals:, timeout:, exception_list:,
|
|
59
|
+
on:, retry_if:, on_retry:, elapsed_time:, max_elapsed_time:, sleep_disabled:, &block
|
|
60
|
+
)
|
|
58
61
|
tries.times do |index|
|
|
59
62
|
try = index + 1
|
|
60
63
|
|
|
61
64
|
begin
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
rescue *[*exception_list] => exception
|
|
66
|
-
if on.is_a?(Hash)
|
|
67
|
-
raise unless exception_list.any? do |e|
|
|
68
|
-
exception.is_a?(e) && ([*on[e]].empty? || [*on[e]].any? { |pattern| exception.message =~ pattern })
|
|
69
|
-
end
|
|
70
|
-
end
|
|
65
|
+
return call_with_timeout(timeout, try, &block)
|
|
66
|
+
rescue *exception_list => e
|
|
67
|
+
raise unless retriable_exception?(e, on, exception_list, retry_if)
|
|
71
68
|
|
|
72
69
|
interval = intervals[index]
|
|
73
|
-
on_retry
|
|
70
|
+
call_on_retry(on_retry, e, try, elapsed_time.call, interval)
|
|
74
71
|
|
|
75
|
-
raise
|
|
72
|
+
raise unless can_retry?(try, tries, elapsed_time.call, interval, max_elapsed_time)
|
|
76
73
|
|
|
77
74
|
sleep interval if sleep_disabled != true
|
|
78
75
|
end
|
|
79
76
|
end
|
|
80
77
|
end
|
|
78
|
+
|
|
79
|
+
def build_intervals(local_config, tries)
|
|
80
|
+
return local_config.intervals if local_config.intervals
|
|
81
|
+
|
|
82
|
+
ExponentialBackoff.new(
|
|
83
|
+
tries: tries - 1,
|
|
84
|
+
base_interval: local_config.base_interval,
|
|
85
|
+
multiplier: local_config.multiplier,
|
|
86
|
+
max_interval: local_config.max_interval,
|
|
87
|
+
rand_factor: local_config.rand_factor,
|
|
88
|
+
).intervals
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def call_with_timeout(timeout, try)
|
|
92
|
+
return Timeout.timeout(timeout) { yield(try) } if timeout
|
|
93
|
+
|
|
94
|
+
yield(try)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def call_on_retry(on_retry, exception, try, elapsed_time, interval)
|
|
98
|
+
return unless on_retry
|
|
99
|
+
|
|
100
|
+
on_retry.call(exception, try, elapsed_time, interval)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def can_retry?(try, tries, elapsed_time, interval, max_elapsed_time)
|
|
104
|
+
try < tries && (elapsed_time + interval) <= max_elapsed_time
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# When `on` is a Hash, we need to verify the exception matches a pattern.
|
|
108
|
+
# For any non-Hash `on` value (e.g., Array of classes, single Exception class,
|
|
109
|
+
# or Module), the `rescue *exception_list` clause already guarantees the
|
|
110
|
+
# exception is retriable with respect to `on`; `retry_if`, if provided, is an
|
|
111
|
+
# additional gate that can still cause this method to return false.
|
|
112
|
+
def retriable_exception?(exception, on, exception_list, retry_if)
|
|
113
|
+
return false if on.is_a?(Hash) && !hash_exception_match?(exception, on, exception_list)
|
|
114
|
+
return false if retry_if && !retry_if.call(exception)
|
|
115
|
+
|
|
116
|
+
true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def hash_exception_match?(exception, on, exception_list)
|
|
120
|
+
exception_list.any? do |error_class|
|
|
121
|
+
next false unless exception.is_a?(error_class)
|
|
122
|
+
|
|
123
|
+
patterns = [*on[error_class]]
|
|
124
|
+
patterns.empty? || patterns.any? { |pattern| exception.message =~ pattern }
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private_class_method(
|
|
129
|
+
:execute_tries,
|
|
130
|
+
:build_intervals,
|
|
131
|
+
:call_with_timeout,
|
|
132
|
+
:call_on_retry,
|
|
133
|
+
:can_retry?,
|
|
134
|
+
:retriable_exception?,
|
|
135
|
+
:hash_exception_match?,
|
|
136
|
+
)
|
|
81
137
|
end
|
data/retriable.gemspec
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
5
|
require "retriable/version"
|
|
5
6
|
|
|
@@ -9,7 +10,9 @@ Gem::Specification.new do |spec|
|
|
|
9
10
|
spec.authors = ["Jack Chu"]
|
|
10
11
|
spec.email = ["jack@jackchu.com"]
|
|
11
12
|
spec.summary = "Retriable is a simple DSL to retry failed code blocks with randomized exponential backoff"
|
|
12
|
-
spec.description = "Retriable is a simple DSL to retry failed code blocks with randomized
|
|
13
|
+
spec.description = "Retriable is a simple DSL to retry failed code blocks with randomized " \
|
|
14
|
+
"exponential backoff. This is especially useful when interacting with external " \
|
|
15
|
+
"APIs/services or file system calls."
|
|
13
16
|
spec.homepage = "https://github.com/kamui/retriable"
|
|
14
17
|
spec.license = "MIT"
|
|
15
18
|
|
data/spec/config_spec.rb
CHANGED
|
@@ -40,6 +40,10 @@ describe Retriable::Config do
|
|
|
40
40
|
expect(default_config.on).to eq([StandardError])
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
it "retry_if defaults to nil" do
|
|
44
|
+
expect(default_config.retry_if).to be_nil
|
|
45
|
+
end
|
|
46
|
+
|
|
43
47
|
it "on_retry handler defaults to nil" do
|
|
44
48
|
expect(default_config.on_retry).to be_nil
|
|
45
49
|
end
|
data/spec/retriable_spec.rb
CHANGED
|
@@ -96,6 +96,28 @@ describe Retriable do
|
|
|
96
96
|
expect(@tries).to eq(10)
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
it "does not call on_retry when explicitly set to false" do
|
|
100
|
+
callback_called = false
|
|
101
|
+
original_on_retry = described_class.config.on_retry
|
|
102
|
+
|
|
103
|
+
begin
|
|
104
|
+
described_class.configure do |c|
|
|
105
|
+
c.on_retry = proc { |_exception, _try, _elapsed_time, _next_interval| callback_called = true }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
expect do
|
|
109
|
+
described_class.retriable(on_retry: false, tries: 3) { increment_tries_with_exception }
|
|
110
|
+
end.to raise_error(StandardError)
|
|
111
|
+
|
|
112
|
+
expect(@tries).to eq(3)
|
|
113
|
+
expect(callback_called).to be(false)
|
|
114
|
+
ensure
|
|
115
|
+
described_class.configure do |c|
|
|
116
|
+
c.on_retry = original_on_retry
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
99
121
|
context "with rand_factor 0.0 and an on_retry handler" do
|
|
100
122
|
let(:tries) { 6 }
|
|
101
123
|
let(:no_rand_timetable) { { 1 => 0.5, 2 => 0.75, 3 => 1.125 } }
|
|
@@ -237,6 +259,50 @@ describe Retriable do
|
|
|
237
259
|
end
|
|
238
260
|
end
|
|
239
261
|
|
|
262
|
+
context "with a :retry_if parameter" do
|
|
263
|
+
it "retries only when retry_if returns true" do
|
|
264
|
+
described_class.retriable(tries: 3, retry_if: ->(_exception) { @tries < 3 }) do
|
|
265
|
+
increment_tries
|
|
266
|
+
raise StandardError, "StandardError occurred" if @tries < 3
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
expect(@tries).to eq(3)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it "does not retry when retry_if returns false" do
|
|
273
|
+
expect do
|
|
274
|
+
described_class.retriable(tries: 3, retry_if: ->(_exception) { false }) do
|
|
275
|
+
increment_tries_with_exception
|
|
276
|
+
end
|
|
277
|
+
end.to raise_error(StandardError)
|
|
278
|
+
|
|
279
|
+
expect(@tries).to eq(1)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it "can retry based on the wrapped exception cause" do
|
|
283
|
+
root_cause_class = Class.new(StandardError)
|
|
284
|
+
wrapper_class = Class.new(StandardError)
|
|
285
|
+
|
|
286
|
+
described_class.retriable(
|
|
287
|
+
on: [wrapper_class],
|
|
288
|
+
tries: 3,
|
|
289
|
+
retry_if: ->(exception) { exception.cause.is_a?(root_cause_class) },
|
|
290
|
+
) do
|
|
291
|
+
increment_tries
|
|
292
|
+
|
|
293
|
+
if @tries < 3
|
|
294
|
+
begin
|
|
295
|
+
raise root_cause_class, "root cause"
|
|
296
|
+
rescue root_cause_class
|
|
297
|
+
raise wrapper_class, "wrapped"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
expect(@tries).to eq(3)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
240
306
|
it "runs for a max elapsed time of 2 seconds" do
|
|
241
307
|
described_class.configure { |c| c.sleep_disabled = false }
|
|
242
308
|
|
|
@@ -255,6 +321,17 @@ describe Retriable do
|
|
|
255
321
|
end
|
|
256
322
|
|
|
257
323
|
context "#configure" do
|
|
324
|
+
it "exposes only the intended public API" do
|
|
325
|
+
public_api_methods = %i[
|
|
326
|
+
retriable
|
|
327
|
+
with_context
|
|
328
|
+
configure
|
|
329
|
+
config
|
|
330
|
+
]
|
|
331
|
+
|
|
332
|
+
expect(described_class.singleton_methods(false)).to match_array(public_api_methods)
|
|
333
|
+
end
|
|
334
|
+
|
|
258
335
|
it "raises NoMethodError on invalid configuration" do
|
|
259
336
|
expect { described_class.configure { |c| c.does_not_exist = 123 } }.to raise_error(NoMethodError)
|
|
260
337
|
end
|
|
@@ -275,6 +352,22 @@ describe Retriable do
|
|
|
275
352
|
expect(@tries).to eq(1)
|
|
276
353
|
end
|
|
277
354
|
|
|
355
|
+
it "returns nil when called without a block" do
|
|
356
|
+
expect(described_class.with_context(:sql)).to be_nil
|
|
357
|
+
expect(@tries).to eq(0)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
it "passes try count through to the context block" do
|
|
361
|
+
seen_tries = []
|
|
362
|
+
|
|
363
|
+
described_class.with_context(:api) do |try|
|
|
364
|
+
seen_tries << try
|
|
365
|
+
raise StandardError if try < 3
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
expect(seen_tries).to eq([1, 2, 3])
|
|
369
|
+
end
|
|
370
|
+
|
|
278
371
|
it "respects the context options" do
|
|
279
372
|
expect { described_class.with_context(:api) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
280
373
|
expect(@tries).to eq(api_tries)
|
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.
|
|
4
|
+
version: 3.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jack Chu
|
|
@@ -52,7 +52,7 @@ dependencies:
|
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '3.1'
|
|
54
54
|
description: Retriable is a simple DSL to retry failed code blocks with randomized
|
|
55
|
-
exponential backoff. This is especially useful when interacting external
|
|
55
|
+
exponential backoff. This is especially useful when interacting with external APIs/services
|
|
56
56
|
or file system calls.
|
|
57
57
|
email:
|
|
58
58
|
- jack@jackchu.com
|