You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/vendor/bundle/ruby/4.0.0/gems/retriable-3.4.1/lib/retriable.rb

141 lines
4.2 KiB

# frozen_string_literal: true
require "timeout"
require_relative "retriable/config"
require_relative "retriable/exponential_backoff"
require_relative "retriable/version"
module Retriable
module_function
def configure
yield(config)
end
def config
@config ||= Config.new
end
def with_context(context_key, options = {}, &block)
if !config.contexts.key?(context_key)
raise ArgumentError,
"#{context_key} not found in Retriable.config.contexts. Available contexts: #{config.contexts.keys}"
end
return unless block_given?
retriable(config.contexts[context_key].merge(options), &block)
end
def retriable(opts = {}, &block)
local_config = opts.empty? ? config : Config.new(config.to_h.merge(opts))
tries = local_config.tries
intervals = build_intervals(local_config, tries)
timeout = local_config.timeout
on = local_config.on
retry_if = local_config.retry_if
on_retry = local_config.on_retry
sleep_disabled = local_config.sleep_disabled
max_elapsed_time = local_config.max_elapsed_time
exception_list = on.is_a?(Hash) ? on.keys : on
exception_list = [*exception_list]
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
elapsed_time = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time }
tries = intervals.size + 1
execute_tries(
tries: tries, intervals: intervals, timeout: timeout,
exception_list: exception_list, on: on, retry_if: retry_if, on_retry: on_retry,
elapsed_time: elapsed_time, max_elapsed_time: max_elapsed_time,
sleep_disabled: sleep_disabled, &block
)
end
def execute_tries( # rubocop:disable Metrics/ParameterLists
tries:, intervals:, timeout:, exception_list:,
on:, retry_if:, on_retry:, elapsed_time:, max_elapsed_time:, sleep_disabled:, &block
)
tries.times do |index|
try = index + 1
begin
return call_with_timeout(timeout, try, &block)
rescue *exception_list => e
raise unless retriable_exception?(e, on, exception_list, retry_if)
interval = intervals[index]
call_on_retry(on_retry, e, try, elapsed_time.call, interval)
raise unless can_retry?(try, tries, elapsed_time.call, interval, max_elapsed_time)
sleep interval if sleep_disabled != true
end
end
end
def build_intervals(local_config, tries)
return local_config.intervals if local_config.intervals
ExponentialBackoff.new(
tries: tries - 1,
base_interval: local_config.base_interval,
multiplier: local_config.multiplier,
max_interval: local_config.max_interval,
rand_factor: local_config.rand_factor,
).intervals
end
def call_with_timeout(timeout, try)
return Timeout.timeout(timeout) { yield(try) } if timeout
yield(try)
end
def call_on_retry(on_retry, exception, try, elapsed_time, interval)
return unless on_retry
on_retry.call(exception, try, elapsed_time, interval)
end
def can_retry?(try, tries, elapsed_time, interval, max_elapsed_time)
return false unless try < tries
return true if max_elapsed_time.nil?
(elapsed_time + interval) <= max_elapsed_time
end
# When `on` is a Hash, we need to verify the exception matches a pattern.
# For any non-Hash `on` value (e.g., Array of classes, single Exception class,
# or Module), the `rescue *exception_list` clause already guarantees the
# exception is retriable with respect to `on`; `retry_if`, if provided, is an
# additional gate that can still cause this method to return false.
def retriable_exception?(exception, on, exception_list, retry_if)
return false if on.is_a?(Hash) && !hash_exception_match?(exception, on, exception_list)
return false if retry_if && !retry_if.call(exception)
true
end
def hash_exception_match?(exception, on, exception_list)
exception_list.any? do |error_class|
next false unless exception.is_a?(error_class)
patterns = [*on[error_class]]
patterns.empty? || patterns.any? { |pattern| exception.message =~ pattern }
end
end
private_class_method(
:execute_tries,
:build_intervals,
:call_with_timeout,
:call_on_retry,
:can_retry?,
:retriable_exception?,
:hash_exception_match?,
)
end