retriable 3.7.0 → 3.8.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/CHANGELOG.md +6 -0
- data/README.md +34 -21
- data/lib/retriable/config.rb +51 -0
- data/lib/retriable/version.rb +1 -1
- data/spec/config_spec.rb +93 -1
- data/spec/retriable_spec.rb +83 -2
- data/spec/spec_helper.rb +13 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25948e6545b8d98b0c7b08416bdf23b017ced352eb2a4eb1f2e9fdac45912031
|
|
4
|
+
data.tar.gz: 4a92afb17f79a4ad173b046158ca634b0728fc6ccceedc1b7789c9106342b513
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e43731b305a64c806bf57c13d78da410412d8ea4adaec9f255bd434b2d190ac53b6fac29172b72ed6bcc7329a3d5378c4075d72f6dede791f1e0bd16f400e58e
|
|
7
|
+
data.tar.gz: 4259468487738b9ae5bcc817ac992af3341f15bcfe604ab0a61ddf1f8793b444775aa57c06ce5f95561e179186e7360e86a877afd02f3b346b68377ffb3ecaf9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# HEAD
|
|
2
2
|
|
|
3
|
+
## 3.8.0
|
|
4
|
+
|
|
5
|
+
### Deprecations
|
|
6
|
+
|
|
7
|
+
- Deprecated the `timeout:` option ahead of its removal in Retriable 4.0. Non-nil timeout values supplied through `Retriable.configure`, `Retriable.retriable(...)`, or `Retriable.with_override(...)` now emit a deprecation warning while keeping the existing runtime behavior unchanged. On Ruby 2.7+ the warning is emitted via `Kernel.warn(..., category: :deprecated)`, so callers can silence it through the standard Ruby controls (`Warning[:deprecated] = false`, `ruby -W:no-deprecated`, or a custom `Warning.warn`). To keep the notice from drowning busy applications, it is emitted at most once per process; suppression via `Warning[:deprecated]` leaves the warner armed for the next call that re-enables the category. Prefer library-native timeout settings, or wrap the retried block in `Timeout.timeout(...)` directly if you still need that behavior. See the README migration guidance for details.
|
|
8
|
+
|
|
3
9
|
## 3.7.0
|
|
4
10
|
|
|
5
11
|
- Feature: Opt-in unbounded retries via `tries: Float::INFINITY`. Requires a finite `max_elapsed_time` as a safety bound and is incompatible with custom `intervals:`. Both invalid configurations raise `ArgumentError` from `Config#validate!`.
|
data/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Retriable is a simple DSL to retry failed code blocks with randomized [exponenti
|
|
|
17
17
|
- [Configuration](#configuration)
|
|
18
18
|
- [Override](#override)
|
|
19
19
|
- [Example Usage](#example-usage)
|
|
20
|
+
- [Migrating off `timeout:`](#migrating-off-timeout)
|
|
20
21
|
- [Custom Interval Array](#custom-interval-array)
|
|
21
22
|
- [Turn off Exponential Backoff](#turn-off-exponential-backoff)
|
|
22
23
|
- [Callbacks](#callbacks)
|
|
@@ -55,7 +56,7 @@ require 'retriable'
|
|
|
55
56
|
In your Gemfile:
|
|
56
57
|
|
|
57
58
|
```ruby
|
|
58
|
-
gem 'retriable', '~> 3.
|
|
59
|
+
gem 'retriable', '~> 3.8'
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
## Usage
|
|
@@ -102,20 +103,20 @@ The default interval table with 10 tries looks like this (in seconds, rounded to
|
|
|
102
103
|
|
|
103
104
|
Here are the available options, in some vague order of relevance to most common use patterns:
|
|
104
105
|
|
|
105
|
-
| Option | Default | Definition
|
|
106
|
-
| ---------------------- | ----------------- |
|
|
107
|
-
| **`tries`** | `3` | Number of attempts to make at running your code block (includes initial attempt). Pass `Float::INFINITY` to keep retrying until success or until `max_elapsed_time` is reached.
|
|
108
|
-
| **`on`** | `[StandardError]` | Type of exceptions to retry. [Read more](#configuring-which-options-to-retry-with-on).
|
|
109
|
-
| **`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).
|
|
110
|
-
| **`on_retry`** | `nil` | `Proc` to call after each try is rescued. Pass `false` to disable a callback set in `#configure` for a single call. [Read more](#callbacks).
|
|
111
|
-
| **`sleep_disabled`** | `false` | When true, disable exponential backoff and attempt retries immediately.
|
|
112
|
-
| **`base_interval`** | `0.5` | The initial interval in seconds between tries.
|
|
113
|
-
| **`max_elapsed_time`** | `900` (15 min) | The maximum amount of total time in seconds that code is allowed to keep being retried. Set to `nil` to disable the time limit and retry based solely on `tries`.
|
|
114
|
-
| **`max_interval`** | `60` | The maximum interval in seconds that any individual retry can reach.
|
|
115
|
-
| **`multiplier`** | `1.5` | Each successive interval grows by this factor. A multipler of 1.5 means the next interval will be 1.5x the current interval.
|
|
116
|
-
| **`rand_factor`** | `0.5` | The percentage to randomize the next retry interval time. The next interval calculation is `randomized_interval = retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])`
|
|
117
|
-
| **`intervals`** | `nil` | Skip generated intervals and provide your own array of intervals in seconds. [Read more](#custom-interval-array).
|
|
118
|
-
| **`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.
|
|
106
|
+
| Option | Default | Definition |
|
|
107
|
+
| ---------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| **`tries`** | `3` | Number of attempts to make at running your code block (includes initial attempt). Pass `Float::INFINITY` to keep retrying until success or until `max_elapsed_time` is reached. |
|
|
109
|
+
| **`on`** | `[StandardError]` | Type of exceptions to retry. [Read more](#configuring-which-options-to-retry-with-on). |
|
|
110
|
+
| **`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). |
|
|
111
|
+
| **`on_retry`** | `nil` | `Proc` to call after each try is rescued. Pass `false` to disable a callback set in `#configure` for a single call. [Read more](#callbacks). |
|
|
112
|
+
| **`sleep_disabled`** | `false` | When true, disable exponential backoff and attempt retries immediately. |
|
|
113
|
+
| **`base_interval`** | `0.5` | The initial interval in seconds between tries. |
|
|
114
|
+
| **`max_elapsed_time`** | `900` (15 min) | The maximum amount of total time in seconds that code is allowed to keep being retried. Set to `nil` to disable the time limit and retry based solely on `tries`. |
|
|
115
|
+
| **`max_interval`** | `60` | The maximum interval in seconds that any individual retry can reach. |
|
|
116
|
+
| **`multiplier`** | `1.5` | Each successive interval grows by this factor. A multipler of 1.5 means the next interval will be 1.5x the current interval. |
|
|
117
|
+
| **`rand_factor`** | `0.5` | The percentage to randomize the next retry interval time. The next interval calculation is `randomized_interval = retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])` |
|
|
118
|
+
| **`intervals`** | `nil` | Skip generated intervals and provide your own array of intervals in seconds. [Read more](#custom-interval-array). |
|
|
119
|
+
| **`timeout`** | `nil` | Deprecated in 3.8.0 and removed in 4.0. 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. Non-nil values emit a deprecation warning. See [Migrating off `timeout:`](#migrating-off-timeout). |
|
|
119
120
|
|
|
120
121
|
Timing options are validated before retrying. `tries` must be a positive integer when Retriable generates intervals, or `Float::INFINITY` for unbounded retries. `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. `intervals` cannot be combined with `tries: Float::INFINITY`.
|
|
121
122
|
|
|
@@ -243,20 +244,32 @@ Retriable.retriable(on: {
|
|
|
243
244
|
end
|
|
244
245
|
```
|
|
245
246
|
|
|
246
|
-
|
|
247
|
+
#### Migrating off `timeout:`
|
|
247
248
|
|
|
248
|
-
The
|
|
249
|
+
The `timeout:` option is deprecated in Retriable 3.8.0 and will be removed in Retriable 4.0. It still works in 3.x, but any non-nil value supplied through `Retriable.configure`, `Retriable.retriable(...)`, or `Retriable.with_override(...)` emits a deprecation warning. In Retriable 4.0, passing `timeout:` will raise `ArgumentError` because it will no longer be a valid option.
|
|
250
|
+
|
|
251
|
+
`timeout:` is deprecated because it is a thin wrapper around `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/). It can interrupt the retried block at any line, including inside libraries that are not interrupt-safe.
|
|
252
|
+
|
|
253
|
+
Prefer timeout settings from the library you are calling, such as `Net::HTTP#read_timeout`, `Net::HTTP#open_timeout`, or Faraday's request timeout options. If you still need `Timeout.timeout`, wrap the retried block explicitly so the risk is visible at the call site:
|
|
249
254
|
|
|
250
255
|
```ruby
|
|
251
|
-
|
|
252
|
-
|
|
256
|
+
require "timeout"
|
|
257
|
+
|
|
258
|
+
Retriable.retriable(on: Timeout::Error, tries: 3) do
|
|
259
|
+
Timeout.timeout(5) do
|
|
260
|
+
# code here...
|
|
261
|
+
end
|
|
253
262
|
end
|
|
254
263
|
```
|
|
255
264
|
|
|
256
|
-
If you
|
|
265
|
+
Like the deprecated `timeout:` option, `Timeout.timeout(5)` inside the block is per-try — each retry gets a fresh 5-second budget. If you want an overall cap across all retries instead, prefer `max_elapsed_time:`.
|
|
266
|
+
|
|
267
|
+
The deprecation warning is emitted under the `:deprecated` warning category and at most once per process. To silence it (for example, in tests), use the standard Ruby controls — set `Warning[:deprecated] = false`, run with `ruby -W:no-deprecated`, or override `Warning.warn` to filter the message.
|
|
268
|
+
|
|
269
|
+
If you need millisecond units of time for the sleep interval:
|
|
257
270
|
|
|
258
271
|
```ruby
|
|
259
|
-
Retriable.retriable(base_interval: (200 / 1000.0)
|
|
272
|
+
Retriable.retriable(base_interval: (200 / 1000.0)) do
|
|
260
273
|
# code here...
|
|
261
274
|
end
|
|
262
275
|
```
|
data/lib/retriable/config.rb
CHANGED
|
@@ -18,6 +18,19 @@ module Retriable
|
|
|
18
18
|
contexts
|
|
19
19
|
]).freeze
|
|
20
20
|
|
|
21
|
+
TIMEOUT_DEPRECATION_MESSAGE = "NOTE: Retriable's `timeout:` option is deprecated and will be removed in " \
|
|
22
|
+
"Retriable 4.0. It is a thin wrapper around `Timeout.timeout`, which " \
|
|
23
|
+
"can interrupt execution at arbitrary lines and corrupt internal state " \
|
|
24
|
+
"in libraries that are not interrupt-safe. Prefer your library's native " \
|
|
25
|
+
"timeout, or wrap your block in `Timeout.timeout(...)` yourself."
|
|
26
|
+
private_constant :TIMEOUT_DEPRECATION_MESSAGE
|
|
27
|
+
|
|
28
|
+
@timeout_deprecation_warned = false
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
attr_accessor :timeout_deprecation_warned
|
|
32
|
+
end
|
|
33
|
+
|
|
21
34
|
attr_accessor(*ATTRIBUTES)
|
|
22
35
|
|
|
23
36
|
def initialize(opts = {})
|
|
@@ -53,6 +66,7 @@ module Retriable
|
|
|
53
66
|
end
|
|
54
67
|
|
|
55
68
|
def validate!
|
|
69
|
+
warn_timeout_deprecation
|
|
56
70
|
validate_optional_non_negative_number(:timeout, timeout)
|
|
57
71
|
validate_on(on)
|
|
58
72
|
validate_intervals
|
|
@@ -70,6 +84,43 @@ module Retriable
|
|
|
70
84
|
|
|
71
85
|
private
|
|
72
86
|
|
|
87
|
+
# Emits the `timeout:` deprecation notice at most once per process.
|
|
88
|
+
#
|
|
89
|
+
# On Rubies that support `Kernel#warn(category: :deprecated)` (2.7+), the
|
|
90
|
+
# notice is emitted under the `:deprecated` category, so callers can use the
|
|
91
|
+
# standard controls (`Warning[:deprecated] = false`, `-W:no-deprecated`,
|
|
92
|
+
# `Warning.warn` override) to silence it. On older Rubies the kwarg is not
|
|
93
|
+
# available and we fall back to plain `Kernel.warn`.
|
|
94
|
+
#
|
|
95
|
+
# When the warning is suppressed (either because `Warning[:deprecated]` is
|
|
96
|
+
# false or the runtime has otherwise muted the category), we deliberately
|
|
97
|
+
# leave the once-per-process flag unset so a future call with the category
|
|
98
|
+
# re-enabled still surfaces the notice.
|
|
99
|
+
def warn_timeout_deprecation
|
|
100
|
+
return if timeout.nil?
|
|
101
|
+
return if self.class.timeout_deprecation_warned
|
|
102
|
+
|
|
103
|
+
category_supported = deprecated_warning_category_supported?
|
|
104
|
+
return if category_supported && !deprecated_warnings_enabled?
|
|
105
|
+
|
|
106
|
+
self.class.timeout_deprecation_warned = true
|
|
107
|
+
if category_supported
|
|
108
|
+
Kernel.warn(TIMEOUT_DEPRECATION_MESSAGE, category: :deprecated)
|
|
109
|
+
else
|
|
110
|
+
Kernel.warn(TIMEOUT_DEPRECATION_MESSAGE)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def deprecated_warning_category_supported?
|
|
115
|
+
defined?(Warning) && Kernel.method(:warn).parameters.any? { |type, name| type == :key && name == :category }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def deprecated_warnings_enabled?
|
|
119
|
+
return true unless defined?(Warning) && Warning.respond_to?(:[])
|
|
120
|
+
|
|
121
|
+
Warning[:deprecated]
|
|
122
|
+
end
|
|
123
|
+
|
|
73
124
|
def validate_backoff_options
|
|
74
125
|
validate_non_negative_number(:base_interval, base_interval)
|
|
75
126
|
validate_non_negative_number(:multiplier, multiplier)
|
data/lib/retriable/version.rb
CHANGED
data/spec/config_spec.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
3
5
|
describe Retriable::Config do
|
|
4
6
|
let(:default_config) { described_class.new }
|
|
5
7
|
|
|
@@ -59,7 +61,97 @@ describe Retriable::Config do
|
|
|
59
61
|
|
|
60
62
|
it "raises errors on invalid timing configuration" do
|
|
61
63
|
expect { described_class.new(rand_factor: 1.1) }.to raise_error(ArgumentError, /rand_factor/)
|
|
62
|
-
expect
|
|
64
|
+
expect do
|
|
65
|
+
expect { described_class.new(timeout: -1) }.to raise_error(ArgumentError, /timeout/)
|
|
66
|
+
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "timeout deprecation" do
|
|
70
|
+
it "warns when timeout is configured" do
|
|
71
|
+
expect do
|
|
72
|
+
described_class.new(timeout: 5)
|
|
73
|
+
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "warns when timeout is set before validation" do
|
|
77
|
+
config = described_class.new
|
|
78
|
+
config.timeout = 5
|
|
79
|
+
|
|
80
|
+
expect do
|
|
81
|
+
config.validate!
|
|
82
|
+
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "does not warn when timeout is nil" do
|
|
86
|
+
expect do
|
|
87
|
+
described_class.new(timeout: nil)
|
|
88
|
+
end.not_to output.to_stderr
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "does not warn when timeout is omitted" do
|
|
92
|
+
expect do
|
|
93
|
+
described_class.new
|
|
94
|
+
end.not_to output.to_stderr
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "warns at most once per process" do
|
|
98
|
+
original_stderr = $stderr
|
|
99
|
+
stderr = StringIO.new
|
|
100
|
+
begin
|
|
101
|
+
$stderr = stderr
|
|
102
|
+
|
|
103
|
+
described_class.new(timeout: 5)
|
|
104
|
+
described_class.new(timeout: 5)
|
|
105
|
+
|
|
106
|
+
config = described_class.new
|
|
107
|
+
config.timeout = 5
|
|
108
|
+
config.validate!
|
|
109
|
+
ensure
|
|
110
|
+
$stderr = original_stderr
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
expect(stderr.string.scan("timeout:` option is deprecated").size).to eq(1)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "emits the warning under the :deprecated category when supported", if: WARN_CATEGORY_SUPPORTED do
|
|
117
|
+
captured = []
|
|
118
|
+
allow(Warning).to receive(:warn) do |message, category: nil|
|
|
119
|
+
captured << [message, category]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
described_class.new(timeout: 5)
|
|
123
|
+
|
|
124
|
+
expect(captured.size).to eq(1)
|
|
125
|
+
message, category = captured.first
|
|
126
|
+
expect(message).to match(/timeout.*deprecated.*Retriable 4\.0/i)
|
|
127
|
+
expect(category).to eq(:deprecated)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "is silenced by Warning[:deprecated] = false", if: WARN_CATEGORY_SUPPORTED do
|
|
131
|
+
original = Warning[:deprecated]
|
|
132
|
+
begin
|
|
133
|
+
Warning[:deprecated] = false
|
|
134
|
+
expect do
|
|
135
|
+
described_class.new(timeout: 5)
|
|
136
|
+
end.not_to output.to_stderr
|
|
137
|
+
ensure
|
|
138
|
+
Warning[:deprecated] = original
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "remains armed when silenced via Warning[:deprecated]", if: WARN_CATEGORY_SUPPORTED do
|
|
143
|
+
original = Warning[:deprecated]
|
|
144
|
+
begin
|
|
145
|
+
Warning[:deprecated] = false
|
|
146
|
+
described_class.new(timeout: 5)
|
|
147
|
+
ensure
|
|
148
|
+
Warning[:deprecated] = original
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
expect do
|
|
152
|
+
described_class.new(timeout: 5)
|
|
153
|
+
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
154
|
+
end
|
|
63
155
|
end
|
|
64
156
|
|
|
65
157
|
it "raises errors when intervals is not an array" do
|
data/spec/retriable_spec.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rbconfig"
|
|
4
|
+
require "stringio"
|
|
4
5
|
|
|
5
6
|
describe Retriable do
|
|
6
7
|
let(:time_table_handler) do
|
|
@@ -50,7 +51,9 @@ describe Retriable do
|
|
|
50
51
|
|
|
51
52
|
it "raises a LocalJumpError if not given a block" do
|
|
52
53
|
expect { described_class.retriable }.to raise_error(LocalJumpError)
|
|
53
|
-
expect
|
|
54
|
+
expect do
|
|
55
|
+
expect { described_class.retriable(timeout: 2) }.to raise_error(LocalJumpError)
|
|
56
|
+
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
54
57
|
end
|
|
55
58
|
|
|
56
59
|
it "stops at first try if the block does not raise an exception" do
|
|
@@ -169,7 +172,85 @@ describe Retriable do
|
|
|
169
172
|
end
|
|
170
173
|
|
|
171
174
|
it "will timeout after 1 second" do
|
|
172
|
-
expect
|
|
175
|
+
expect do
|
|
176
|
+
expect { described_class.retriable(timeout: 1) { sleep(1.1) } }.to raise_error(Timeout::Error)
|
|
177
|
+
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
context "timeout: deprecation" do
|
|
181
|
+
it "warns at most once per process across repeated retriable calls" do
|
|
182
|
+
original_stderr = $stderr
|
|
183
|
+
stderr = StringIO.new
|
|
184
|
+
begin
|
|
185
|
+
$stderr = stderr
|
|
186
|
+
|
|
187
|
+
described_class.retriable(timeout: 5) { :noop }
|
|
188
|
+
described_class.retriable(timeout: 5) { :noop }
|
|
189
|
+
described_class.retriable(timeout: 5) { :noop }
|
|
190
|
+
|
|
191
|
+
expect(stderr.string.scan("timeout:` option is deprecated").size).to eq(1)
|
|
192
|
+
ensure
|
|
193
|
+
$stderr = original_stderr
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "warns when timeout is passed to retriable" do
|
|
198
|
+
expect do
|
|
199
|
+
described_class.retriable(timeout: 5) { :noop }
|
|
200
|
+
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "keeps applying timeout while deprecated" do
|
|
204
|
+
original_stderr = $stderr
|
|
205
|
+
begin
|
|
206
|
+
$stderr = StringIO.new
|
|
207
|
+
expect do
|
|
208
|
+
described_class.retriable(timeout: 0.05, tries: 1) { sleep(0.5) }
|
|
209
|
+
end.to raise_error(Timeout::Error)
|
|
210
|
+
ensure
|
|
211
|
+
$stderr = original_stderr
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it "warns when timeout is supplied through with_override" do
|
|
216
|
+
expect do
|
|
217
|
+
described_class.with_override(timeout: 5) do
|
|
218
|
+
described_class.retriable { :noop }
|
|
219
|
+
end
|
|
220
|
+
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "warns when timeout is supplied through configure" do
|
|
224
|
+
original_config = described_class.config
|
|
225
|
+
begin
|
|
226
|
+
expect do
|
|
227
|
+
described_class.configure { |config| config.timeout = 5 }
|
|
228
|
+
described_class.retriable { :noop }
|
|
229
|
+
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
230
|
+
ensure
|
|
231
|
+
described_class.configure do |config|
|
|
232
|
+
original_config.to_h.each { |key, value| config.public_send("#{key}=", value) }
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it "is silenced by Warning[:deprecated] = false", if: WARN_CATEGORY_SUPPORTED do
|
|
238
|
+
original = Warning[:deprecated]
|
|
239
|
+
begin
|
|
240
|
+
Warning[:deprecated] = false
|
|
241
|
+
expect do
|
|
242
|
+
described_class.retriable(timeout: 5) { :noop }
|
|
243
|
+
end.not_to output.to_stderr
|
|
244
|
+
ensure
|
|
245
|
+
Warning[:deprecated] = original
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it "does not warn when timeout is absent" do
|
|
250
|
+
expect do
|
|
251
|
+
described_class.retriable { :noop }
|
|
252
|
+
end.not_to output.to_stderr
|
|
253
|
+
end
|
|
173
254
|
end
|
|
174
255
|
|
|
175
256
|
it "applies a randomized exponential backoff to each try" do
|
data/spec/spec_helper.rb
CHANGED
|
@@ -7,8 +7,21 @@ require "pry"
|
|
|
7
7
|
require_relative "../lib/retriable"
|
|
8
8
|
require_relative "support/exceptions"
|
|
9
9
|
|
|
10
|
+
# Make Retriable's deprecation notices observable to RSpec's
|
|
11
|
+
# `output().to_stderr` matcher. On Ruby 3.0+ the `:deprecated` warning category
|
|
12
|
+
# is suppressed by default, which would hide the notices we want to assert on.
|
|
13
|
+
WARNING_DEPRECATION_SUPPORTED = defined?(Warning) && Warning.respond_to?(:[])
|
|
14
|
+
Warning[:deprecated] = true if WARNING_DEPRECATION_SUPPORTED
|
|
15
|
+
|
|
16
|
+
# Used by deprecation specs that only make sense on Rubies where `Kernel#warn`
|
|
17
|
+
# supports the `category:` keyword (added in Ruby 2.7).
|
|
18
|
+
WARN_CATEGORY_SUPPORTED = WARNING_DEPRECATION_SUPPORTED &&
|
|
19
|
+
Kernel.method(:warn).parameters.include?(%i[key category])
|
|
20
|
+
|
|
10
21
|
RSpec.configure do |config|
|
|
11
22
|
config.before(:each) do
|
|
12
23
|
srand(0)
|
|
24
|
+
Retriable::Config.timeout_deprecation_warned = false
|
|
25
|
+
Warning[:deprecated] = true if WARNING_DEPRECATION_SUPPORTED
|
|
13
26
|
end
|
|
14
27
|
end
|