retriable 3.3.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0e8971cb9e6833e2795f2537ee69950714f7eb85989b497d15fad7f2e2552b6
4
- data.tar.gz: 222c0e1664bf327dfb22cf161c42a3f3bec07a54ea3787af24a86ebd86abcd50
3
+ metadata.gz: 1c6c4929adafa9b223f3fbe0ee530ef8a7d1e764cac8f5605be9ce33e3d1b0cc
4
+ data.tar.gz: 5d7013b0d5ce9ab8514a8deab7a32d77b0422cc02ead4aceb5a8c9b5ba28bff1
5
5
  SHA512:
6
- metadata.gz: b768d200c1dd25f9a5218096e5bba26b0ee815833fb87ed6a004d90d7f94208f4520df0fdb1172fc6fdea55209cf1eeb15d6efc39a52bd2f32c0222b5aecca5a
7
- data.tar.gz: 0a095f55fcdbe49adf71bb06682c062ed98a4328ef0e4648926e6782b93152c337cd78b2f30a2bf5a79affc37c31c3f2b39730600c3c63b5dcb41d5c09b59380
6
+ metadata.gz: 9a83302cfe426348ae0a35e6410edce33c8985d49289ab06100be7c5d49a586f93a429632814b2129e67b20dde8963eb2f5941bcc59d12f4c1db56ce05fd6784
7
+ data.tar.gz: 792c54b8b8805eb11c93d9ac100671b357b91a465424939a5c3995a2a50a8449033a0e94138bdf2510b2a7f89b4dc35787b0c490c53d63c9a0d2b85f044c80a9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
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
+
3
7
  ## 3.3.0
4
8
 
5
9
  - Refactor `Retriable.retriable` internals into focused private helpers to improve readability while preserving behavior.
data/README.md CHANGED
@@ -32,7 +32,7 @@ require 'retriable'
32
32
  In your Gemfile:
33
33
 
34
34
  ```ruby
35
- gem 'retriable', '~> 3.1'
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:
@@ -4,14 +4,15 @@ require_relative "exponential_backoff"
4
4
 
5
5
  module Retriable
6
6
  class Config
7
- ATTRIBUTES = (ExponentialBackoff::ATTRIBUTES + [
8
- :sleep_disabled,
9
- :max_elapsed_time,
10
- :intervals,
11
- :timeout,
12
- :on,
13
- :on_retry,
14
- :contexts,
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Retriable
4
- VERSION = "3.3.0"
4
+ VERSION = "3.4.0"
5
5
  end
data/lib/retriable.rb CHANGED
@@ -18,7 +18,8 @@ module Retriable
18
18
 
19
19
  def with_context(context_key, options = {}, &block)
20
20
  if !config.contexts.key?(context_key)
21
- raise ArgumentError, "#{context_key} not found in Retriable.config.contexts. Available contexts: #{config.contexts.keys}"
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?
@@ -33,6 +34,7 @@ module Retriable
33
34
  intervals = build_intervals(local_config, tries)
34
35
  timeout = local_config.timeout
35
36
  on = local_config.on
37
+ retry_if = local_config.retry_if
36
38
  on_retry = local_config.on_retry
37
39
  sleep_disabled = local_config.sleep_disabled
38
40
  max_elapsed_time = local_config.max_elapsed_time
@@ -46,7 +48,7 @@ module Retriable
46
48
 
47
49
  execute_tries(
48
50
  tries: tries, intervals: intervals, timeout: timeout,
49
- exception_list: exception_list, on: on, on_retry: on_retry,
51
+ exception_list: exception_list, on: on, retry_if: retry_if, on_retry: on_retry,
50
52
  elapsed_time: elapsed_time, max_elapsed_time: max_elapsed_time,
51
53
  sleep_disabled: sleep_disabled, &block
52
54
  )
@@ -54,7 +56,7 @@ module Retriable
54
56
 
55
57
  def execute_tries( # rubocop:disable Metrics/ParameterLists
56
58
  tries:, intervals:, timeout:, exception_list:,
57
- on:, on_retry:, elapsed_time:, max_elapsed_time:, sleep_disabled:, &block
59
+ on:, retry_if:, on_retry:, elapsed_time:, max_elapsed_time:, sleep_disabled:, &block
58
60
  )
59
61
  tries.times do |index|
60
62
  try = index + 1
@@ -62,7 +64,7 @@ module Retriable
62
64
  begin
63
65
  return call_with_timeout(timeout, try, &block)
64
66
  rescue *exception_list => e
65
- raise unless retriable_exception?(e, on, exception_list)
67
+ raise unless retriable_exception?(e, on, exception_list, retry_if)
66
68
 
67
69
  interval = intervals[index]
68
70
  call_on_retry(on_retry, e, try, elapsed_time.call, interval)
@@ -105,9 +107,11 @@ module Retriable
105
107
  # When `on` is a Hash, we need to verify the exception matches a pattern.
106
108
  # For any non-Hash `on` value (e.g., Array of classes, single Exception class,
107
109
  # or Module), the `rescue *exception_list` clause already guarantees the
108
- # exception is retriable, so we return true unconditionally.
109
- def retriable_exception?(exception, on, exception_list)
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)
110
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)
111
115
 
112
116
  true
113
117
  end
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
@@ -259,6 +259,50 @@ describe Retriable do
259
259
  end
260
260
  end
261
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
+
262
306
  it "runs for a max elapsed time of 2 seconds" do
263
307
  described_class.configure { |c| c.sleep_disabled = false }
264
308
 
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.3.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack Chu