/ Tags: RUBY-CODE / Categories: SOLUTIONS

Retry A Block N Times With Exponential Backoff In Ruby

Network calls, external APIs, and flaky I/O fail intermittently. Retrying immediately usually fails again — the remote service needs a moment. Exponential backoff spaces retries out so each attempt has a better chance than the last.

Description

Wrap the risky work in a block, catch the exceptions you consider retryable, and sleep for a delay that doubles on each attempt. Add jitter — a small random offset — so that many clients retrying at once don’t all hit the service on the same tick. Rescue a specific list of exception classes rather than StandardError. Retrying a TypeError just wastes time and hides a bug; retrying a timeout is what you want. Re-raise on the final attempt so the caller sees the real failure rather than a nil.

Sample input:

  with_retries(attempts: 4, base: 0.5) { Faraday.get("https://api.example.com/orders") }


Sample Output:

  # attempt 1 fails -> sleeps ~0.5s
  # attempt 2 fails -> sleeps ~1.0s
  # attempt 3 fails -> sleeps ~2.0s
  # attempt 4 raises Faraday::TimeoutError to the caller

Answer

  RETRYABLE = [Errno::ECONNRESET, Net::OpenTimeout, Net::ReadTimeout].freeze

  def with_retries(attempts: 3, base: 0.5, max: 30.0, on: RETRYABLE)
    tries = 0
    begin
      tries += 1
      yield(tries)
    rescue *on => e
      raise if tries >= attempts

      delay = [base * (2**(tries - 1)), max].min
      delay += delay * rand * 0.25   # jitter, up to +25%
      warn "retry #{tries}/#{attempts} after #{e.class}: sleeping #{delay.round(2)}s"
      sleep delay
      retry
    end
  end

  # Usage
  response = with_retries(attempts: 4, base: 0.5) do |attempt|
    Faraday.get("https://api.example.com/orders")
  end

  # Retry only on a condition rather than an exception
  def with_retries_until(attempts: 3, base: 0.5)
    attempts.times do |i|
      result = yield
      return result if result

      sleep [base * (2**i), 30.0].min
    end
    nil
  end

Learn More

cdrrazan

Rajan Bhattarai

Full Stack Software Developer! 💻 🏡 Grad. Student, MCS. 🎓 Class of '23. GitKraken Ambassador 🇳🇵 2021/22. Works with Ruby / Rails. Photography when no coding. Also tweets a lot at TW / @cdrrazan!

Read More