Module: Retriable

Defined in:
lib/retriable.rb,
lib/retriable/config.rb,
lib/retriable/version.rb,
lib/retriable/validation.rb,
lib/retriable/exponential_backoff.rb,
sig/retriable.rbs

Defined Under Namespace

Modules: Validation Classes: Config, ExponentialBackoff

Constant Summary collapse

OVERRIDE_THREAD_KEY =

Thread-local storage key for the active #with_override block. We deliberately use Thread#thread_variable_set/get (true thread-local) rather than Thread.current (fiber-local) so that fibers within a thread share the same override. Changing this to Thread.current would silently break callers that use fiber-based concurrency.

Returns:

  • (Symbol)
:retriable_override
VERSION =

Returns:

  • (String)
"5.0.0"

Class Method Summary collapse

Class Method Details

.configConfig

The configuring thread sees its own candidate, still mutable and mid-build. Every other reader sees the last fully published snapshot, which is deeply frozen: mutating it raises FrozenError instead of silently corrupting the config other threads are reading. Use #configure to change configuration.

Returns:



82
83
84
# File 'lib/retriable.rb', line 82

def config
  configuring_config || published_config
end

.configurevoid

This method returns an undefined value.

Copy-on-write: dup the published config, let the caller mutate the copy, then atomically publish it, deeply frozen. Readers therefore always observe a consistent, fully-applied snapshot that nothing can mutate underneath them, and a failed/raising block leaves the old config intact. Does NOT validate (validation stays lazy at #retriable time). Nested calls on the configuring thread share the outer candidate. Only the outermost call takes CONFIG_MUTEX and publishes, so nesting remains safe even though the mutex is not reentrant.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/retriable.rb', line 61

def configure
  candidate = configuring_config
  return yield(candidate) if candidate

  CONFIG_MUTEX.synchronize do
    candidate = config.dup
    Thread.current.thread_variable_set(CONFIGURING_THREAD_KEY, candidate)
    begin
      result = yield(candidate)
      publish_config(candidate)
      result
    ensure
      Thread.current.thread_variable_set(CONFIGURING_THREAD_KEY, nil)
    end
  end
end

.retriable(opts = {}) {|arg0| ... } ⇒ Object

Parameters:

  • options (Hash[Symbol, untyped])

Yields:

Yield Parameters:

  • arg0 (Integer)

Yield Returns:

  • (Object)

Returns:

  • (Object)


120
121
122
# File 'lib/retriable.rb', line 120

def retriable(opts = {}, &)
  retriable_with_config(config, opts, &)
end

.with_context(context_key, options = {}) {|arg0| ... } ⇒ Object

Parameters:

  • context_key (Symbol)
  • options (Hash[Symbol, untyped]) (defaults to: {})

Yields:

Yield Parameters:

  • arg0 (Integer)

Yield Returns:

  • (Object)

Returns:

  • (Object)

Raises:

  • (ArgumentError)


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/retriable.rb', line 101

def with_context(context_key, options = {}, &)
  raise ArgumentError, "with_context requires a block" unless block_given?

  # Resolve the whole call against one snapshot and one traversal of its
  # contexts. Re-reading `config` here would let a concurrent #configure pass
  # the existence check on the old snapshot while options resolve against the
  # new one, silently dropping the context's retry options.
  config_snapshot = config
  configured_contexts = config_contexts(config_snapshot)
  contexts = configured_contexts.merge(override_contexts)

  if !contexts.key?(context_key)
    raise ArgumentError,
          "#{context_key} not found in Retriable contexts (including overrides). Available contexts: #{contexts.keys}"
  end

  retriable_with_config(config_snapshot, context_options_for(context_key, configured_contexts, options), &)
end

.with_override(opts = {}) { ... } ⇒ Object

Parameters:

  • options (Hash[Symbol, untyped])

Yields:

Yield Returns:

  • (Object)

Returns:

  • (Object)

Raises:

  • (ArgumentError)


86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/retriable.rb', line 86

def with_override(opts = {})
  raise ArgumentError, "empty override options are not allowed" if opts.empty?
  raise ArgumentError, "with_override requires a block" unless block_given?

  validate_override_options(opts)

  previous = Thread.current.thread_variable_get(OVERRIDE_THREAD_KEY)
  Thread.current.thread_variable_set(OVERRIDE_THREAD_KEY, opts)
  begin
    yield
  ensure
    Thread.current.thread_variable_set(OVERRIDE_THREAD_KEY, previous)
  end
end