retriable 3.4.0 → 3.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c6c4929adafa9b223f3fbe0ee530ef8a7d1e764cac8f5605be9ce33e3d1b0cc
4
- data.tar.gz: 5d7013b0d5ce9ab8514a8deab7a32d77b0422cc02ead4aceb5a8c9b5ba28bff1
3
+ metadata.gz: 86086d1d868b9f609e96d2b5e8e5f6e5910a9822b7c21dd86a4014a12105d7f3
4
+ data.tar.gz: a7324a228f6b0565428cb10afe6ecf3a98b7a6984ced4f83c32595f97767b09b
5
5
  SHA512:
6
- metadata.gz: 9a83302cfe426348ae0a35e6410edce33c8985d49289ab06100be7c5d49a586f93a429632814b2129e67b20dde8963eb2f5941bcc59d12f4c1db56ce05fd6784
7
- data.tar.gz: 792c54b8b8805eb11c93d9ac100671b357b91a465424939a5c3995a2a50a8449033a0e94138bdf2510b2a7f89b4dc35787b0c490c53d63c9a0d2b85f044c80a9
6
+ metadata.gz: fc0f71e125a40e52fdb8e4cb99b71b066fc84a067e9011b166a14d08a9f98f20a15a32402818705172275ea8b0a0f814963265eabba9fcae6d7ac40cdfc9e520
7
+ data.tar.gz: d13f82d53fdc1c2be693a056fd4d164cddc4a7b34096c6c7367213f899881687854a3df5a20a07ad3678c3aa257297bd42a68cb36e9cf0718a2fa8be54a3bd56
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # HEAD
2
2
 
3
+ ## 3.4.1
4
+
5
+ - Fix: Use `Process.clock_gettime(CLOCK_MONOTONIC)` for elapsed time tracking so retry timing is immune to wall-clock adjustments (NTP, manual changes).
6
+ - Fix: Handle `max_elapsed_time: nil` gracefully instead of raising `NoMethodError`.
7
+ - Remove dead `* 1.0` float coercion in `ExponentialBackoff#randomize`.
8
+
3
9
  ## 3.4.0
4
10
 
5
11
  - Add `retry_if` option to support custom retry predicates, including checks against wrapped `exception.cause` values.
data/README.md CHANGED
@@ -87,7 +87,7 @@ Here are the available options, in some vague order of relevance to most common
87
87
  | **`on_retry`** | `nil` | `Proc` to call after each try is rescued. [Read more](#callbacks). |
88
88
  | **`sleep_disabled`** | `false` | When true, disable exponential backoff and attempt retries immediately. |
89
89
  | **`base_interval`** | `0.5` | The initial interval in seconds between tries. |
90
- | **`max_elapsed_time`** | `900` (15 min) | The maximum amount of total time in seconds that code is allowed to keep being retried. |
90
+ | **`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`. |
91
91
  | **`max_interval`** | `60` | The maximum interval in seconds that any individual retry can reach. |
92
92
  | **`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. |
93
93
  | **`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])` |
@@ -28,7 +28,7 @@ module Retriable
28
28
 
29
29
  def intervals
30
30
  intervals = Array.new(tries) do |iteration|
31
- [base_interval * multiplier**iteration, max_interval].min
31
+ [base_interval * (multiplier**iteration), max_interval].min
32
32
  end
33
33
 
34
34
  return intervals if rand_factor.zero?
@@ -39,7 +39,7 @@ module Retriable
39
39
  private
40
40
 
41
41
  def randomize(interval)
42
- delta = rand_factor * interval * 1.0
42
+ delta = rand_factor * interval.to_f
43
43
  min = interval - delta
44
44
  max = interval + delta
45
45
  rand(min..max)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Retriable
4
- VERSION = "3.4.0"
4
+ VERSION = "3.4.1"
5
5
  end
data/lib/retriable.rb CHANGED
@@ -41,8 +41,8 @@ module Retriable
41
41
 
42
42
  exception_list = on.is_a?(Hash) ? on.keys : on
43
43
  exception_list = [*exception_list]
44
- start_time = Time.now
45
- elapsed_time = -> { Time.now - start_time }
44
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
45
+ elapsed_time = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time }
46
46
 
47
47
  tries = intervals.size + 1
48
48
 
@@ -101,7 +101,10 @@ module Retriable
101
101
  end
102
102
 
103
103
  def can_retry?(try, tries, elapsed_time, interval, max_elapsed_time)
104
- try < tries && (elapsed_time + interval) <= max_elapsed_time
104
+ return false unless try < tries
105
+ return true if max_elapsed_time.nil?
106
+
107
+ (elapsed_time + interval) <= max_elapsed_time
105
108
  end
106
109
 
107
110
  # When `on` is a Hash, we need to verify the exception matches a pattern.
@@ -315,6 +315,38 @@ describe Retriable do
315
315
  expect(@tries).to eq(2)
316
316
  end
317
317
 
318
+ it "retries up to tries limit when max_elapsed_time is nil" do
319
+ expect do
320
+ described_class.retriable(tries: 4, max_elapsed_time: nil) { increment_tries_with_exception }
321
+ end.to raise_error(StandardError)
322
+
323
+ expect(@tries).to eq(4)
324
+ end
325
+
326
+ it "uses monotonic clock for elapsed time tracking" do
327
+ # Stub Process.clock_gettime to return controlled values so we can
328
+ # verify elapsed_time passed to on_retry is derived from the monotonic clock.
329
+ clock_calls = 0
330
+ allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC) do
331
+ value = clock_calls.to_f
332
+ clock_calls += 1
333
+ value
334
+ end
335
+
336
+ elapsed_times = []
337
+ on_retry = ->(_exception, _try, elapsed_time, _next_interval) { elapsed_times << elapsed_time }
338
+
339
+ expect do
340
+ described_class.retriable(tries: 3, on_retry: on_retry) { increment_tries_with_exception }
341
+ end.to raise_error(StandardError)
342
+
343
+ # start_time (call 0) + at least one elapsed_time computation per retry
344
+ expect(clock_calls).to be >= 3
345
+ # elapsed_time values should be positive and non-decreasing
346
+ expect(elapsed_times).to all(be > 0)
347
+ expect(elapsed_times).to eq(elapsed_times.sort)
348
+ end
349
+
318
350
  it "raises ArgumentError on invalid options" do
319
351
  expect { described_class.retriable(does_not_exist: 123) { increment_tries } }.to raise_error(ArgumentError)
320
352
  end
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.0
4
+ version: 3.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack Chu