hur.st's bl.aagh

BSD, Ruby, Rust, Rambling

Reattempt

A jittery Enumerable retry and backoff library

[ruby]

Reattempt is a simple application of Ruby's Enumerators to provide a nice idiomatic interface to retries. At its most basic:

begin
  Reattempt::Retry.new.each do
    poke_remote_api
  end
rescue Reattempt::RetriesExceeded => e
  handle_repeated_failure(e.cause)
end

It's split into two Enumerables - Retry implements the retrying Exception-catching iterator, while Backoff implements the configurable jittered exponential backoff, allowing the API user to implement sleeping as they see fit:

# Start delay 0.075-0.125 seconds, increasing to 0.75-1.25 seconds
bo = Reattempt::Backoff.new(min_delay: 0.1, max_delay: 1.0, jitter: 0.5)

bo.take(4).map { |x| x.round(4) } # => [0.1138, 0.2029, 0.4227, 0.646]
bo.take(2).each { |delay| sleep delay }
bo.delay_for_attempt(4) # => 1.0403524624141058
bo[4] # => 0.8328055668923606

bo.each do |delay|
  printf("Sleeping for about %.2f seconds\n", delay)
  sleep delay
end

A more complete example:

bo = Reattempt::Backoff.new(min_delay: 0.1, max_delay: 1.0, jitter: 0.5)
try = Reattempt::Retry.new(tries: 5, rescue: TempError, backoff: bo)
begin
  try.each do |attempt|
    raise TempError, "Failed in attempt #{attempt}"
  end
rescue Reattempt::RetriesExceeded => e
  p e.cause # => #<TempError: "Failed in attempt 5">
end