retriable 3.8.0 → 5.0.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/.github/workflows/main.yml +38 -9
- data/.hound.yml +1 -1
- data/.rubocop.yml +4 -1
- data/AGENTS.md +15 -0
- data/CHANGELOG.md +159 -0
- data/Gemfile +6 -1
- data/README.md +131 -50
- data/benchmark/config_publication.rb +79 -0
- data/docs/adr/0001-copy-on-write-config-publication.md +124 -0
- data/docs/agents/domain.md +38 -0
- data/docs/agents/issue-tracker.md +45 -0
- data/docs/agents/triage-labels.md +17 -0
- data/docs/migration.md +84 -0
- data/lib/retriable/config.rb +123 -51
- data/lib/retriable/core_ext/kernel.rb +6 -4
- data/lib/retriable/exponential_backoff.rb +13 -5
- data/lib/retriable/validation.rb +11 -7
- data/lib/retriable/version.rb +1 -1
- data/lib/retriable.rb +155 -45
- data/retriable.gemspec +2 -7
- data/sig/retriable.rbs +29 -1
- data/spec/config_spec.rb +212 -97
- data/spec/retriable_spec.rb +666 -104
- data/spec/spec_helper.rb +3 -14
- metadata +14 -53
data/lib/retriable/validation.rb
CHANGED
|
@@ -28,6 +28,13 @@ module Retriable
|
|
|
28
28
|
validate_non_negative_number(name, value)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def validate_callable(name, value)
|
|
32
|
+
return unless value # nil/false disable the callback
|
|
33
|
+
return if value.respond_to?(:call)
|
|
34
|
+
|
|
35
|
+
raise ArgumentError, "#{name} must respond to #call or be nil"
|
|
36
|
+
end
|
|
37
|
+
|
|
31
38
|
def validate_rand_factor
|
|
32
39
|
return if finite_number?(rand_factor) && rand_factor >= 0 && rand_factor <= 1
|
|
33
40
|
|
|
@@ -46,7 +53,7 @@ module Retriable
|
|
|
46
53
|
|
|
47
54
|
# Validates an `on:` value. Acceptable shapes:
|
|
48
55
|
# - a Class that descends from Exception
|
|
49
|
-
# - an Array whose elements are Classes that descend from Exception
|
|
56
|
+
# - an Array or Set whose elements are Classes that descend from Exception
|
|
50
57
|
# - a Hash whose keys are such Classes and whose values are nil,
|
|
51
58
|
# a Regexp, or an Array of Regexps
|
|
52
59
|
#
|
|
@@ -58,12 +65,12 @@ module Retriable
|
|
|
58
65
|
# Regexp values explicitly.
|
|
59
66
|
def validate_on(value)
|
|
60
67
|
case value
|
|
61
|
-
|
|
68
|
+
in Hash
|
|
62
69
|
value.each do |klass, pattern|
|
|
63
70
|
validate_on_class(klass)
|
|
64
71
|
validate_on_hash_value(klass, pattern)
|
|
65
72
|
end
|
|
66
|
-
|
|
73
|
+
in Array | Set
|
|
67
74
|
value.each { |klass| validate_on_class(klass) }
|
|
68
75
|
else
|
|
69
76
|
validate_on_class(value)
|
|
@@ -79,10 +86,7 @@ module Retriable
|
|
|
79
86
|
def validate_on_hash_value(klass, pattern)
|
|
80
87
|
return if pattern.nil?
|
|
81
88
|
return if pattern.is_a?(Regexp)
|
|
82
|
-
|
|
83
|
-
# rubocop:disable Style/PredicateWithKind
|
|
84
|
-
return if pattern.is_a?(Array) && pattern.all? { |p| p.is_a?(Regexp) }
|
|
85
|
-
# rubocop:enable Style/PredicateWithKind
|
|
89
|
+
return if pattern.is_a?(Array) && pattern.all?(Regexp)
|
|
86
90
|
|
|
87
91
|
raise ArgumentError,
|
|
88
92
|
"on[#{klass}] must be nil, a Regexp, or an Array of Regexps, got #{pattern.inspect}"
|
data/lib/retriable/version.rb
CHANGED
data/lib/retriable.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "timeout"
|
|
4
3
|
require_relative "retriable/config"
|
|
5
4
|
require_relative "retriable/exponential_backoff"
|
|
6
5
|
require_relative "retriable/version"
|
|
@@ -13,17 +12,75 @@ module Retriable
|
|
|
13
12
|
# break callers that use fiber-based concurrency.
|
|
14
13
|
OVERRIDE_THREAD_KEY = :retriable_override
|
|
15
14
|
|
|
15
|
+
# True thread-local storage marking the Config this thread is currently
|
|
16
|
+
# building inside #configure. It answers one question — "am I mid-configure?"
|
|
17
|
+
# Which snapshot a given #retriable/#with_context call resolves against is a
|
|
18
|
+
# separate question, answered by passing that snapshot as an argument (see
|
|
19
|
+
# #retriable_with_config). Keeping the two questions on two mechanisms is
|
|
20
|
+
# deliberate: a thread-local would otherwise leak the resolved snapshot across
|
|
21
|
+
# the caller's block and change what `Retriable.config` returns inside it.
|
|
22
|
+
CONFIGURING_THREAD_KEY = :retriable_configuring
|
|
23
|
+
private_constant :CONFIGURING_THREAD_KEY
|
|
24
|
+
|
|
16
25
|
RetryPlan = Struct.new(:max_tries, :interval_for)
|
|
17
26
|
private_constant :RetryPlan
|
|
18
27
|
|
|
28
|
+
# Serializes complete #configure transactions so concurrent read-modify-write
|
|
29
|
+
# swaps cannot drop one another's updates.
|
|
30
|
+
CONFIG_MUTEX = Mutex.new
|
|
31
|
+
private_constant :CONFIG_MUTEX
|
|
32
|
+
|
|
33
|
+
# Guards the single @config reference. Held only for a reference read or the
|
|
34
|
+
# publishing write, never across the #configure block, so a writer blocks a
|
|
35
|
+
# reader for no longer than a pointer swap.
|
|
36
|
+
#
|
|
37
|
+
# One path on every engine. MRI's GVL would make an unsynchronized read safe,
|
|
38
|
+
# but JRuby and TruffleRuby offer no such happens-before guarantee. The common
|
|
39
|
+
# no-options #retriable path uses the snapshot directly, so this mutex has a
|
|
40
|
+
# measurable cost. benchmark/config_publication.rb tracks that cost. Replace
|
|
41
|
+
# this mechanism only when measurements justify a portable alternative.
|
|
42
|
+
CONFIG_PUBLICATION_MUTEX = Mutex.new
|
|
43
|
+
private_constant :CONFIG_PUBLICATION_MUTEX
|
|
44
|
+
|
|
45
|
+
# Eagerly initialized at load time. `require` is serialized in MRI, so this runs
|
|
46
|
+
# exactly once before any thread can reach #config/#configure, closing the
|
|
47
|
+
# `@config ||= Config.new` check-then-act race. Frozen like every snapshot
|
|
48
|
+
# published after it, so reads are immutable from the very first one.
|
|
49
|
+
@config = Config.new.freeze
|
|
50
|
+
|
|
19
51
|
module_function
|
|
20
52
|
|
|
53
|
+
# Copy-on-write: dup the published config, let the caller mutate the copy, then
|
|
54
|
+
# atomically publish it, deeply frozen. Readers therefore always observe a
|
|
55
|
+
# consistent, fully-applied snapshot that nothing can mutate underneath them,
|
|
56
|
+
# and a failed/raising block leaves the old config intact. Does NOT validate
|
|
57
|
+
# (validation stays lazy at #retriable time).
|
|
58
|
+
# Nested calls on the configuring thread share the outer candidate. Only the
|
|
59
|
+
# outermost call takes CONFIG_MUTEX and publishes, so nesting remains safe even
|
|
60
|
+
# though the mutex is not reentrant.
|
|
21
61
|
def configure
|
|
22
|
-
|
|
62
|
+
candidate = configuring_config
|
|
63
|
+
return yield(candidate) if candidate
|
|
64
|
+
|
|
65
|
+
CONFIG_MUTEX.synchronize do
|
|
66
|
+
candidate = config.dup
|
|
67
|
+
Thread.current.thread_variable_set(CONFIGURING_THREAD_KEY, candidate)
|
|
68
|
+
begin
|
|
69
|
+
result = yield(candidate)
|
|
70
|
+
publish_config(candidate)
|
|
71
|
+
result
|
|
72
|
+
ensure
|
|
73
|
+
Thread.current.thread_variable_set(CONFIGURING_THREAD_KEY, nil)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
23
76
|
end
|
|
24
77
|
|
|
78
|
+
# The configuring thread sees its own candidate, still mutable and mid-build.
|
|
79
|
+
# Every other reader sees the last fully published snapshot, which is deeply
|
|
80
|
+
# frozen: mutating it raises FrozenError instead of silently corrupting the
|
|
81
|
+
# config other threads are reading. Use #configure to change configuration.
|
|
25
82
|
def config
|
|
26
|
-
|
|
83
|
+
configuring_config || published_config
|
|
27
84
|
end
|
|
28
85
|
|
|
29
86
|
def with_override(opts = {})
|
|
@@ -41,35 +98,44 @@ module Retriable
|
|
|
41
98
|
end
|
|
42
99
|
end
|
|
43
100
|
|
|
44
|
-
def with_context(context_key, options = {}, &
|
|
45
|
-
|
|
101
|
+
def with_context(context_key, options = {}, &)
|
|
102
|
+
raise ArgumentError, "with_context requires a block" unless block_given?
|
|
103
|
+
|
|
104
|
+
# Resolve the whole call against one snapshot and one traversal of its
|
|
105
|
+
# contexts. Re-reading `config` here would let a concurrent #configure pass
|
|
106
|
+
# the existence check on the old snapshot while options resolve against the
|
|
107
|
+
# new one, silently dropping the context's retry options.
|
|
108
|
+
config_snapshot = config
|
|
109
|
+
configured_contexts = config_contexts(config_snapshot)
|
|
110
|
+
contexts = configured_contexts.merge(override_contexts)
|
|
46
111
|
|
|
47
112
|
if !contexts.key?(context_key)
|
|
48
113
|
raise ArgumentError,
|
|
49
114
|
"#{context_key} not found in Retriable contexts (including overrides). Available contexts: #{contexts.keys}"
|
|
50
115
|
end
|
|
51
116
|
|
|
52
|
-
|
|
117
|
+
retriable_with_config(config_snapshot, context_options_for(context_key, configured_contexts, options), &)
|
|
118
|
+
end
|
|
53
119
|
|
|
54
|
-
|
|
120
|
+
def retriable(opts = {}, &)
|
|
121
|
+
retriable_with_config(config, opts, &)
|
|
55
122
|
end
|
|
56
123
|
|
|
57
|
-
def
|
|
124
|
+
def retriable_with_config(base_config, opts = {}, &)
|
|
58
125
|
override_config = current_override
|
|
59
126
|
local_config = if opts.empty? && !override_config
|
|
60
|
-
config
|
|
127
|
+
# A reused config may have been mutated inside configure.
|
|
128
|
+
base_config.validate!
|
|
129
|
+
base_config
|
|
61
130
|
else
|
|
62
|
-
Config.new(apply_override_options(
|
|
131
|
+
Config.new(apply_override_options(merge_layer(base_config.to_h, opts), override_config))
|
|
63
132
|
end
|
|
64
133
|
|
|
65
|
-
# Config is mutable through `configure`, so validate again immediately before use.
|
|
66
|
-
local_config.validate!
|
|
67
|
-
|
|
68
134
|
plan = retry_plan(local_config)
|
|
69
|
-
timeout = local_config.timeout
|
|
70
135
|
on = local_config.on
|
|
71
136
|
retry_if = local_config.retry_if
|
|
72
137
|
on_retry = local_config.on_retry
|
|
138
|
+
on_give_up = local_config.on_give_up
|
|
73
139
|
sleep_disabled = local_config.sleep_disabled
|
|
74
140
|
max_elapsed_time = local_config.max_elapsed_time
|
|
75
141
|
|
|
@@ -79,30 +145,39 @@ module Retriable
|
|
|
79
145
|
elapsed_time = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time }
|
|
80
146
|
|
|
81
147
|
execute_tries(
|
|
82
|
-
max_tries: plan.max_tries, interval_for: plan.interval_for,
|
|
148
|
+
max_tries: plan.max_tries, interval_for: plan.interval_for,
|
|
83
149
|
exception_list: exception_list, on: on, retry_if: retry_if, on_retry: on_retry,
|
|
84
|
-
elapsed_time: elapsed_time, max_elapsed_time: max_elapsed_time,
|
|
85
|
-
sleep_disabled: sleep_disabled, &
|
|
150
|
+
on_give_up: on_give_up, elapsed_time: elapsed_time, max_elapsed_time: max_elapsed_time,
|
|
151
|
+
sleep_disabled: sleep_disabled, &
|
|
86
152
|
)
|
|
87
153
|
end
|
|
88
154
|
|
|
89
155
|
def execute_tries( # rubocop:disable Metrics/ParameterLists
|
|
90
|
-
max_tries:, interval_for:,
|
|
91
|
-
on:, retry_if:, on_retry:, elapsed_time:, max_elapsed_time:, sleep_disabled
|
|
156
|
+
max_tries:, interval_for:, exception_list:,
|
|
157
|
+
on:, retry_if:, on_retry:, on_give_up:, elapsed_time:, max_elapsed_time:, sleep_disabled:
|
|
92
158
|
)
|
|
93
159
|
try = 0
|
|
94
|
-
loop
|
|
160
|
+
while true # rubocop:disable Style/InfiniteLoop -- Kernel#loop swallows StopIteration.
|
|
95
161
|
try += 1
|
|
96
162
|
begin
|
|
97
|
-
return
|
|
163
|
+
return yield(try)
|
|
98
164
|
rescue *exception_list => e
|
|
99
165
|
raise unless retriable_exception?(e, on, exception_list, retry_if)
|
|
100
166
|
|
|
167
|
+
# On the final attempt `interval_for` returns nil (no next retry), and
|
|
168
|
+
# `on_retry` intentionally fires before the give-up check below, so it
|
|
169
|
+
# receives `interval: nil`. See the on_retry/on_give_up README contract.
|
|
101
170
|
interval = interval_for.call(try - 1)
|
|
102
171
|
call_on_retry(on_retry, e, try, elapsed_time.call, interval)
|
|
103
172
|
|
|
104
173
|
elapsed_interval = sleep_disabled == true ? 0 : interval
|
|
105
|
-
|
|
174
|
+
# Snapshot elapsed_time once so the stop check and on_give_up see the same value.
|
|
175
|
+
current_elapsed_time = elapsed_time.call
|
|
176
|
+
stop_reason = retry_stop_reason(try, max_tries, current_elapsed_time, elapsed_interval, max_elapsed_time)
|
|
177
|
+
if stop_reason
|
|
178
|
+
call_on_give_up(on_give_up, e, try, current_elapsed_time, interval, stop_reason)
|
|
179
|
+
raise
|
|
180
|
+
end
|
|
106
181
|
|
|
107
182
|
sleep interval if sleep_disabled != true
|
|
108
183
|
end
|
|
@@ -132,23 +207,30 @@ module Retriable
|
|
|
132
207
|
).interval_provider
|
|
133
208
|
end
|
|
134
209
|
|
|
135
|
-
def call_with_timeout(timeout, try)
|
|
136
|
-
return Timeout.timeout(timeout) { yield(try) } if timeout
|
|
137
|
-
|
|
138
|
-
yield(try)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
210
|
def call_on_retry(on_retry, exception, try, elapsed_time, interval)
|
|
142
211
|
return unless on_retry
|
|
143
212
|
|
|
144
213
|
on_retry.call(exception, try, elapsed_time, interval)
|
|
145
214
|
end
|
|
146
215
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
|
|
216
|
+
def call_on_give_up( # rubocop:disable Metrics/ParameterLists
|
|
217
|
+
on_give_up, exception, try, elapsed_time, interval, reason
|
|
218
|
+
)
|
|
219
|
+
return unless on_give_up
|
|
220
|
+
|
|
221
|
+
on_give_up.call(exception, try, elapsed_time, interval, reason)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# `:tries_exhausted` is checked first, but the two conditions can't both hold
|
|
225
|
+
# on the same try in practice: `retry_plan` returns a nil interval whenever
|
|
226
|
+
# `try >= max_tries`, so `(elapsed_time + interval) > max_elapsed_time` is not
|
|
227
|
+
# evaluable on the exhausted-tries try. The early return guards against that
|
|
228
|
+
# nil and also pins precedence in case the plan ever changes.
|
|
229
|
+
def retry_stop_reason(try, max_tries, elapsed_time, interval, max_elapsed_time)
|
|
230
|
+
return :tries_exhausted if max_tries && try >= max_tries
|
|
231
|
+
return nil if max_elapsed_time.nil?
|
|
150
232
|
|
|
151
|
-
(elapsed_time + interval)
|
|
233
|
+
:max_elapsed_time if (elapsed_time + interval) > max_elapsed_time
|
|
152
234
|
end
|
|
153
235
|
|
|
154
236
|
# When `on` is a Hash, we need to verify the exception matches a pattern.
|
|
@@ -204,19 +286,26 @@ module Retriable
|
|
|
204
286
|
def apply_override_options(options, overrides)
|
|
205
287
|
return options unless overrides
|
|
206
288
|
|
|
207
|
-
options
|
|
208
|
-
options[:intervals] = nil if overrides.key?(:tries) && !overrides.key?(:intervals)
|
|
209
|
-
options
|
|
289
|
+
merge_layer(options, overrides)
|
|
210
290
|
end
|
|
211
291
|
|
|
212
|
-
|
|
213
|
-
|
|
292
|
+
# Merge a higher-precedence option layer onto a base layer. A higher layer
|
|
293
|
+
# that sets `tries` without `intervals` clears the base layer's inherited
|
|
294
|
+
# `intervals`, so a caller's `tries:` is never silently ignored. When the
|
|
295
|
+
# higher layer supplies its own `intervals`, those win (same-call override).
|
|
296
|
+
def merge_layer(base, higher)
|
|
297
|
+
merged = base.merge(higher)
|
|
298
|
+
merged[:intervals] = nil if higher.key?(:tries) && !higher.key?(:intervals)
|
|
299
|
+
merged
|
|
214
300
|
end
|
|
215
301
|
|
|
216
|
-
|
|
217
|
-
|
|
302
|
+
# Takes the already-resolved contexts hash rather than the config snapshot, so
|
|
303
|
+
# the snapshot travels exactly one hop (into #retriable_with_config) instead of
|
|
304
|
+
# through every private helper that happens to need a corner of it.
|
|
305
|
+
def context_options_for(context_key, contexts, options)
|
|
306
|
+
context_options = contexts.fetch(context_key, {})
|
|
218
307
|
context_options = {} unless context_options.is_a?(Hash)
|
|
219
|
-
context_options = context_options
|
|
308
|
+
context_options = merge_layer(context_options, options)
|
|
220
309
|
|
|
221
310
|
override_context_options = override_contexts[context_key]
|
|
222
311
|
return context_options unless override_context_options.is_a?(Hash)
|
|
@@ -224,8 +313,8 @@ module Retriable
|
|
|
224
313
|
apply_override_options(context_options, override_context_options)
|
|
225
314
|
end
|
|
226
315
|
|
|
227
|
-
def config_contexts
|
|
228
|
-
|
|
316
|
+
def config_contexts(config_snapshot)
|
|
317
|
+
config_snapshot.contexts.is_a?(Hash) ? config_snapshot.contexts : {}
|
|
229
318
|
end
|
|
230
319
|
|
|
231
320
|
def override_contexts
|
|
@@ -238,19 +327,40 @@ module Retriable
|
|
|
238
327
|
Thread.current.thread_variable_get(OVERRIDE_THREAD_KEY)
|
|
239
328
|
end
|
|
240
329
|
|
|
330
|
+
def configuring_config
|
|
331
|
+
Thread.current.thread_variable_get(CONFIGURING_THREAD_KEY)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def published_config
|
|
335
|
+
CONFIG_PUBLICATION_MUTEX.synchronize { @config }
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Publishes a deeply frozen deep copy of the candidate. The copy matters: it
|
|
339
|
+
# keeps the freeze off objects the caller still owns, so `c.on = my_array`
|
|
340
|
+
# inside a #configure block never leaves my_array frozen. The mutex covers the
|
|
341
|
+
# reference swap only; the copy and freeze happen outside it.
|
|
342
|
+
def publish_config(candidate)
|
|
343
|
+
snapshot = candidate.dup.freeze
|
|
344
|
+
CONFIG_PUBLICATION_MUTEX.synchronize { @config = snapshot }
|
|
345
|
+
end
|
|
346
|
+
|
|
241
347
|
private_class_method(
|
|
348
|
+
:retriable_with_config,
|
|
349
|
+
:configuring_config,
|
|
350
|
+
:published_config,
|
|
351
|
+
:publish_config,
|
|
242
352
|
:validate_override_options,
|
|
243
353
|
:validate_context_override_options,
|
|
244
354
|
:execute_tries,
|
|
245
355
|
:retry_plan,
|
|
246
356
|
:interval_provider,
|
|
247
|
-
:call_with_timeout,
|
|
248
357
|
:call_on_retry,
|
|
249
|
-
:
|
|
358
|
+
:call_on_give_up,
|
|
359
|
+
:retry_stop_reason,
|
|
250
360
|
:retriable_exception?,
|
|
251
361
|
:hash_exception_match?,
|
|
252
362
|
:apply_override_options,
|
|
253
|
-
:
|
|
363
|
+
:merge_layer,
|
|
254
364
|
:context_options_for,
|
|
255
365
|
:config_contexts,
|
|
256
366
|
:override_contexts,
|
data/retriable.gemspec
CHANGED
|
@@ -15,15 +15,10 @@ Gem::Specification.new do |spec|
|
|
|
15
15
|
"APIs/services or file system calls."
|
|
16
16
|
spec.homepage = "https://github.com/kamui/retriable"
|
|
17
17
|
spec.license = "MIT"
|
|
18
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
18
19
|
|
|
19
20
|
spec.files = `git ls-files -z`.split("\x0")
|
|
20
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
21
21
|
spec.require_paths = ["lib"]
|
|
22
22
|
|
|
23
|
-
spec.required_ruby_version = ">=
|
|
24
|
-
|
|
25
|
-
spec.add_development_dependency "bundler"
|
|
26
|
-
spec.add_development_dependency "rspec", "~> 3"
|
|
27
|
-
|
|
28
|
-
spec.add_development_dependency "listen", "~> 3.1"
|
|
23
|
+
spec.required_ruby_version = ">= 3.2"
|
|
29
24
|
end
|
data/sig/retriable.rbs
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
module Retriable
|
|
2
2
|
VERSION: String
|
|
3
|
-
|
|
3
|
+
OVERRIDE_THREAD_KEY: Symbol
|
|
4
|
+
|
|
5
|
+
def self.configure: [Result] () { (Config) -> Result } -> Result
|
|
6
|
+
def self.config: () -> Config
|
|
7
|
+
def self.with_override: (Hash[Symbol, untyped] options) { () -> untyped } -> untyped
|
|
8
|
+
def self.with_context: (Symbol context_key, ?Hash[Symbol, untyped] options) { (Integer) -> untyped } -> untyped
|
|
9
|
+
def self.retriable: (?Hash[Symbol, untyped] options) { (Integer) -> untyped } -> untyped
|
|
10
|
+
|
|
11
|
+
class Config
|
|
12
|
+
ATTRIBUTES: Array[Symbol]
|
|
13
|
+
|
|
14
|
+
attr_accessor tries: Numeric
|
|
15
|
+
attr_accessor base_interval: Numeric
|
|
16
|
+
attr_accessor max_interval: Numeric
|
|
17
|
+
attr_accessor rand_factor: Numeric
|
|
18
|
+
attr_accessor multiplier: Numeric
|
|
19
|
+
attr_accessor sleep_disabled: bool
|
|
20
|
+
attr_accessor max_elapsed_time: Numeric?
|
|
21
|
+
attr_accessor intervals: Array[Numeric]?
|
|
22
|
+
attr_accessor on: untyped
|
|
23
|
+
attr_accessor retry_if: untyped
|
|
24
|
+
attr_accessor on_retry: untyped
|
|
25
|
+
attr_accessor on_give_up: untyped
|
|
26
|
+
attr_accessor contexts: Hash[Symbol, untyped]
|
|
27
|
+
|
|
28
|
+
def initialize: (?Hash[Symbol, untyped] opts) -> void
|
|
29
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
30
|
+
def validate!: () -> void
|
|
31
|
+
end
|
|
4
32
|
end
|