Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
R

retriable

> 编程语言
Open source

Retriable is an simple DSL to retry failed code blocks with randomized exponential backoff.

908 stars0 likes4 views
WebsiteGitHub

About

Retriable is an simple DSL to retry failed code blocks with randomized exponential backoff.

Retriable

Retriable is a simple DSL to retry failed code blocks with randomized exponential backoff time intervals. This is especially useful when interacting external APIs, remote services, or file system calls.

Table of Contents

  • Requirements
  • Upgrading
  • Installation
  • Usage
    • Defaults
    • Options
      • Configuring Which Options to Retry With :on
      • Advanced Retry Matching With :retry_if
    • Configuration
    • Override
    • Example Usage
    • Custom Interval Array
    • Unbounded Retries (Opt-in)
    • Turn off Exponential Backoff
    • Callbacks
      • Disabling a Configured Callback Per Call
    • Ensure/Else
  • Contexts
  • Kernel Extension
  • Testing
  • Credits
  • Development
    • Running Specs

Requirements

Ruby 3.2+

If you need Ruby 2.3.0-3.1.x support, use the 3.8.x branch by specifying ~> 3.8 in your Gemfile.

If you need Ruby 2.0.0-2.2.x support, use the 3.1 branch by specifying ~3.1 in your Gemfile.

If you need Ruby 1.9.3 support, use the 2.x branch by specifying ~2.1 in your Gemfile.

If you need Ruby 1.8.x to 1.9.2 support, use the 1.x branch by specifying ~1.4 in your Gemfile.

Upgrading

Retriable 5.0 changes one thing for existing users: Retriable.config is now a frozen snapshot, so direct writes raise FrozenError.

ruby
Retriable.config.sleep_disabled = true              # => FrozenError
Retriable.configure { |c| c.sleep_disabled = true } # do this instead

Test setup files such as spec_helper and rails_helper are the likeliest place this bites. For the full 4.x to 5.x and 3.x to 4.0 guides, see docs/migration.md.

Installation

Via command line:

ruby
gem install retriable

In your ruby script:

ruby
require 'retriable'

In your Gemfile:

ruby
gem 'retriable', '~> 5.0'

Usage

Code in a Retriable.retriable block will be retried if an exception is raised.

ruby
require 'retriable'

class Api
  # Use it in methods that interact with unreliable services
  def get
    Retriable.retriable do
      # code here...
    end
  end
end

Defaults

By default, Retriable will:

  • rescue any exception inherited from StandardError
  • make 3 tries (including the initial attempt) before raising the last exception
  • use randomized exponential backoff to calculate each succeeding try interval.

The default interval table with 10 tries looks like this (in seconds, rounded to the nearest millisecond):

Retry # Min Average Max
1 0.25 0.5 0.75
2 0.375 0.75 1.125
3 0.563 1.125 1.688
4 0.844 1.688 2.531
5 1.266 2.531 3.797
6 1.898 3.797 5.695
7 2.848 5.695 8.543
8 4.271 8.543 12.814
9 6.407 12.814 19.222
10 stop stop stop

Options

Here are the available options, in some vague order of relevance to most common use patterns:

Option Default Definition
tries 3 Number of attempts to make at running your code block (includes initial attempt). Pass Float::INFINITY to keep retrying until success or until max_elapsed_time is reached.
on [StandardError] Type of exceptions to retry. Read more.
retry_if nil Callable (for example a Proc or lambda) that receives the rescued exception and returns true/false to decide whether to retry. Read more.
on_retry nil Proc to call after each try is rescued. Pass false to disable a callback set in #configure for a single call. Read more.
on_give_up nil Proc to call when Retriable stops retrying after a rescued retriable exception. Read more.
sleep_disabled false When true, disable exponential backoff and attempt retries immediately.
base_interval 0.5 The initial interval in seconds between tries.
max_elapsed_time 900 (15 min) The maximum amount of total time in seconds that code is allowed to keep being retried. Set to nil to disable the time limit and retry based solely on tries.
max_interval 60 Caps the interval before rand_factor is applied. A randomized interval may reach max_interval * (1 + rand_factor).
multiplier 1.5 Each successive interval grows by this factor. A multipler of 1.5 means the next interval will be 1.5x the current interval.
rand_factor 0.5 The percentage to randomize the next retry interval time. The next interval calculation is randomized_interval = retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])
intervals nil Skip generated intervals and provide your own array of intervals in seconds. Read more.

Timing options are validated before retrying. tries must be a positive integer when Retriable generates intervals, or Float::INFINITY for unbounded retries. base_interval, max_interval, multiplier, and max_elapsed_time must be non-negative numbers, with max_elapsed_time also accepting nil. rand_factor must be a number from 0 through 1. If provided, intervals must be an array of non-negative numbers; because it replaces generated intervals, it also overrides tries, base_interval, max_interval, rand_factor, and multiplier validation. intervals cannot be combined with tries: Float::INFINITY.

Configuring Which Options to Retry With :on

:on Can take the form:

  • An Exception class (retry every exception of this type, including subclasses)
  • An Array or Set of Exception classes (retry any exception of one of these types, including subclasses)
  • A Hash where the keys are Exception classes and the values are one of:
    • nil (retry every exception of the key's type, including subclasses)
    • A single Regexp pattern (retries exceptions ONLY if their message matches the pattern)
    • An array of patterns (retries exceptions ONLY if their message matches at least one of the patterns)

Advanced Retry Matching With :retry_if

Use :retry_if when retry logic depends on details that :on does not cover. The Proc receives the rescued exception and should return true to retry or false to re-raise immediately.

ruby
def caused_by?(error, klass)
  current = error
  while current
    return true if current.is_a?(klass)

    current = current.cause
  end

  false
end

Retriable.retriable(
  on: [Faraday::ConnectionFailed],
  retry_if: ->(exception) { caused_by?(exception, Errno::ECONNRESET) }
) do
  # code here...
end

:retry_if runs after the exception type has matched :on.

Configuration

You can change the global defaults with a #configure block:

ruby
Retriable.configure do |c|
  c.tries = 5
  c.max_elapsed_time = 3600 # 1 hour
end

#configure sets defaults only. Per-call options passed to Retriable.retriable and Retriable.with_context still take precedence.

When a higher-precedence layer sets tries: without intervals:, it clears any intervals: inherited from a lower layer (so retriable(tries: 1) runs once even if intervals was configured). Within a single call, passing intervals: still overrides tries:.

Thread safety

#configure is the only supported way to change configuration, and it is safe to call from multiple threads.

Configuration is copy-on-write. #configure duplicates the current config, hands your block the copy, and publishes it only if the block returns without raising. So a reader in another thread always sees either the whole previous config or the whole new one, never a half-applied mix, and a block that raises leaves the existing config in place.

Configuration blocks are serialized. Keep them short, and do not wait inside one for work that may call #configure, because that work cannot begin until the current block returns. Readers are unaffected and continue using the last published config while a block runs.

The published config is deeply frozen. Reaching around #configure to mutate it raises FrozenError:

ruby
Retriable.config.tries = 5              # => FrozenError
Retriable.config.contexts[:api] = {}    # => FrozenError

Retriable.configure { |c| c.tries = 5 } # this is the supported path

That is deliberate. A published config is shared by every thread reading it, so an in-place write is a data race that used to corrupt other threads' retry behavior silently. Retriable.config remains fine to read.

Two more details:

  • #configure calls can nest. Nested calls on the configuring thread, including calls from its fibers, share the outer working copy. Only the outermost call publishes. If its block raises, none of the nested changes are published. A nested call does not create an independent commit or savepoint.
  • Inside a #configure block, every fiber on the configuring thread sees the in-progress config. Other threads keep seeing the last published one until the block completes.

Thread safety covers the config structure. User-supplied callbacks such as retry_if, on_retry, and on_give_up can still hold mutable state. The caller must synchronize that state if the callback can run from multiple threads.

Override

#with_override is a block-scoped API for forcing retry options that should take precedence over both `#c

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Ruby

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言