Module: Patiently::Helpers

Defined in:
lib/patiently/helpers.rb

Overview

Mix this into a class (or configure RSpec to include it) to gain the #patiently and #patiently_until helpers:

include Patiently::Helpers

Constant Summary collapse

IN_PATIENTLY =

Thread-local flag marking that we are already inside a patiently block. Nested blocks are run once and let the outer block do the retrying.

:patiently_in_progress
RetryUntilTrue =

Internal sentinel used by #patiently_until to signal "not truthy yet".

Class.new(StandardError)

Instance Method Summary collapse

Instance Method Details

#patiently(timeout = nil, &block) ⇒ Object

Runs block, retrying it whenever it raises, until it succeeds or the patience window (timeout + min/max retries) is exhausted. Returns the block's return value on success; re-raises the last exception on failure.

When called while another patiently block is already running on the current thread, the block is simply run once: only the outermost block is retried.

rubocop:disable Lint/RescueException

Parameters:

  • timeout (Numeric, nil) (defaults to: nil)

    overrides Patiently.config.timeout



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
# File 'lib/patiently/helpers.rb', line 26

def patiently(timeout = nil, &block)
  return block.call if Thread.current[IN_PATIENTLY]

  config = Patiently.config
  timeout ||= config.timeout
  started_at = monotonic_time
  retries = 0

  begin
    Thread.current[IN_PATIENTLY] = true
    attempt_started_at = monotonic_time
    block.call
  rescue Exception => e
    elapsed = attempt_started_at - started_at
    timed_out = elapsed > timeout && retries >= config.min_retries
    retries_exhausted = config.max_retries && retries >= config.max_retries
    raise e if timed_out || retries_exhausted

    sleep(config.retry_interval(retries))

    if monotonic_time == started_at
      raise Patiently::FrozenInTime, "time appears to be frozen, consider time travelling instead"
    end

    retries += 1
    retry
  ensure
    Thread.current[IN_PATIENTLY] = false
  end
end

#patiently_until(timeout = nil, &block) ⇒ Object Also known as: patiently_wait_until

Retries block until it returns a truthy value or the patience window elapses, then returns the final result as a boolean.

Because patiently only retries on a raise, we raise an internal sentinel while the block is falsey and translate a timed-out sentinel back into false. A real exception inside the block is not the sentinel, so it still propagates.

Parameters:

  • timeout (Numeric, nil) (defaults to: nil)

    overrides Patiently.config.timeout



67
68
69
70
71
72
# File 'lib/patiently/helpers.rb', line 67

def patiently_until(timeout = nil, &block)
  patiently(timeout) { block.call || raise(RetryUntilTrue) }
  true
rescue RetryUntilTrue
  false
end