lib/retriable.rb
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
retriable(config.contexts[context_key].merge(options), &block) if block
end
def retriable(opts = {})
local_config = opts.empty? ? config : Config.new(config.to_h.merge(opts))
tries = local_config.tries
base_interval = local_config.base_interval
max_interval = local_config.max_interval
rand_factor = local_config.rand_factor
multiplier = local_config.multiplier
max_elapsed_time = local_config.max_elapsed_time
intervals = local_config.intervals
timeout = local_config.timeout
on = local_config.on
on_retry = local_config.on_retry
sleep_disabled = local_config.sleep_disabled
exception_list = on.is_a?(Hash) ? on.keys : on
start_time = Time.now
elapsed_time = -> { Time.now - start_time }
if intervals
tries = intervals.size + 1
else
intervals = ExponentialBackoff.new(
tries: tries - 1,
base_interval: base_interval,
multiplier: multiplier,
max_interval: max_interval,
rand_factor: rand_factor
).intervals
end
tries.times do |index|
try = index + 1
begin
return Timeout.timeout(timeout) { return yield(try) } if timeout
return yield(try)
rescue *[*exception_list] => exception
if on.is_a?(Hash)
raise unless exception_list.any? do |e|
exception.is_a?(e) && ([*on[e]].empty? || [*on[e]].any? { |pattern| exception.message =~ pattern })
end
end
interval = intervals[index]
on_retry.call(exception, try, elapsed_time.call, interval) if on_retry
raise if try >= tries || (elapsed_time.call + interval) > max_elapsed_time
sleep interval if sleep_disabled != true
end
end
end
end