Module: Tins::Attempt
- Included in:
- Object
- Defined in:
- lib/tins/attempt.rb
Overview
A module that provides functionality for attempting operations with error handling and retry logic.
Instance Method Summary collapse
-
#attempt(opts = {}, &block) ⇒ Object
Attempts code in block attempts times, sleeping according to sleep between attempts and catching the exception(s) in exception_class.
-
#compute_duration_base(sleep, attempts) ⇒ Float
private
Computes the base for exponential backoff that results in a specific total sleep duration.
-
#interpret_sleep(sleep, attempts) ⇒ Proc?
private
The interpret_sleep method determines the sleep behavior for retry attempts.
-
#sleep_duration(duration, count) ⇒ Object
private
The sleep_duration method handles sleeping for a specified duration or based on a proc call.
Instance Method Details
#attempt(opts = {}, &block) ⇒ Object
Attempts code in block attempts times, sleeping according to sleep between attempts and catching the exception(s) in exception_class.
sleep is either a Proc returning a floating point number for duration as seconds or a Numeric >= 0 or < 0. In the former case this is the duration directly, in the latter case -sleep is the total number of seconds that is slept before giving up, and every attempt is retried after a exponentially increasing duration of seconds.
Iff reraise is true the caught exception is reraised after running out of attempts.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/tins/attempt.rb', line 16 def attempt(opts = {}, &block) sleep = nil exception_class = StandardError prev_exception = nil if Numeric === opts attempts = opts else attempts = opts[:attempts] || 1 attempts >= 1 or raise ArgumentError, 'at least one attempt is required' exception_class = opts[:exception_class] if opts.key?(:exception_class) sleep = interpret_sleep(opts[:sleep], attempts) reraise = opts[:reraise] end return if attempts <= 0 count = 0 if exception_class.nil? begin count += 1 if block.call(count, prev_exception) return true elsif count < attempts sleep_duration(sleep, count) end end until count == attempts false else begin count += 1 block.call(count, prev_exception) true rescue *exception_class if count < attempts prev_exception = $! sleep_duration(sleep, count) retry end case reraise when Proc reraise.($!) when Exception.class raise reraise, "reraised: #{$!.}" when true raise $!, "reraised: #{$!.}" else false end end end end |
#compute_duration_base(sleep, attempts) ⇒ Float (private)
Computes the base for exponential backoff that results in a specific total sleep duration.
This method solves for the base ‘x` in the geometric series:
x^0 + x^1 + x^2 + ... + x^(attempts-1) = sleep
The solution ensures that when using exponential delays with base ‘x`, the total time spent across all attempts equals approximately the specified sleep duration. This method of computation is used if a negative number of secondes was requested in the attempt method.
all attempts invalid iterations
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/tins/attempt.rb', line 102 def compute_duration_base(sleep, attempts) x1, x2 = 1, sleep attempts <= sleep or raise ArgumentError, "need less or equal number of attempts than sleep duration #{sleep}" x1 >= x2 and raise ArgumentError, "invalid sleep argument: #{sleep.inspect}" function = -> x { (0...attempts).inject { |s, i| s + x ** i } - sleep } f, fmid = function[x1], function[x2] f * fmid >= 0 and raise ArgumentError, "invalid sleep argument: #{sleep.inspect}" n = 1 << 16 epsilon = 1E-16 root = if f < 0 dx = x2 - x1 x1 else dx = x1 - x2 x2 end n.times do fmid = function[xmid = root + (dx *= 0.5)] fmid < 0 and root = xmid dx.abs < epsilon or fmid == 0 and return root end raise ArgumentError, "too many iterations (#{n})" result end |
#interpret_sleep(sleep, attempts) ⇒ Proc? (private)
The interpret_sleep method determines the sleep behavior for retry attempts.
between retries, nil if no sleep was requested no sleep is needed
than 3 attempts
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/tins/attempt.rb', line 140 def interpret_sleep(sleep, attempts) case sleep when nil when Numeric if sleep < 0 if attempts > 2 sleep = -sleep duration_base = compute_duration_base sleep, attempts sleep = lambda { |i| duration_base ** i } else raise ArgumentError, "require > 2 attempts for negative sleep value" end end sleep when Proc sleep else raise TypeError, "require Proc or Numeric sleep argument" end end |
#sleep_duration(duration, count) ⇒ Object (private)
The sleep_duration method handles sleeping for a specified duration or based on a proc call.
returns the duration
73 74 75 76 77 78 79 80 |
# File 'lib/tins/attempt.rb', line 73 def sleep_duration(duration, count) case duration when Numeric sleep duration when Proc sleep duration.call(count) end end |