Class: Wait

Inherits:
Object
  • Object
show all
Defined in:
lib/wait.rb,
lib/version.rb,
lib/loggers/base.rb,
lib/raisers/base.rb,
lib/testers/base.rb,
lib/counters/base.rb,
lib/delayers/base.rb,
lib/loggers/debug.rb,
lib/rescuers/base.rb,
lib/raisers/signal.rb,
lib/testers/truthy.rb,
lib/raisers/passive.rb,
lib/testers/passive.rb,
lib/delayers/regular.rb,
lib/delayers/exponential.rb

Defined Under Namespace

Classes: BaseCounter, BaseDelayer, BaseLogger, BaseRaiser, BaseRescuer, BaseTester, DebugLogger, ExponentialDelayer, PassiveRaiser, PassiveTester, RegularDelayer, ResultInvalid, SignalRaiser, TimeoutError, TruthyTester

Constant Summary collapse

DEFAULT =
{
  :attempts => 5,
  :timeout  => 15,
  :delay    => 1,
  :counter  => BaseCounter,
  :delayer  => RegularDelayer,
  :rescuer  => BaseRescuer,
  :tester   => TruthyTester,
  :raiser   => SignalRaiser,
  :logger   => BaseLogger
}
VERSION =
'0.5.3'

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Wait

Creates a new Wait instance.

Basic Options

:attempts

Number of times to attempt the block. Default is 5.

:timeout

Seconds until the block times out. Default is 15.

:delay

Seconds to delay in between attempts. Default is 1.

:rescue

One or an array of exceptions to rescue. Default is nil.

:debug

If true, debug logging is enabled. Default is false.

Advanced Options

:logger

Ruby logger used. Default is Wait::BaseLogger.

:counter

Strategy used to count attempts. Default is Wait::BaseCounter.

:delayer

Strategy used to delay in between attempts. Default is Wait::RegularDelayer.

:rescuer

Strategy used to rescue exceptions. Default is Wait::BaseRescuer.

:tester

Strategy used to test the result. Default is Wait::TruthyTester.

:raiser

Strategy used to raise specific exceptions. Default is Wait::SignalRaiser.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/wait.rb', line 48

def initialize(options = {})
  # Assign defaults.
  debug      = options[:debug]    || false
  @logger    = options[:logger]   || (debug ? DebugLogger : DEFAULT[:logger]).new
  attempts   = options[:attempts] || DEFAULT[:attempts]
  @counter   = options[:counter]  || DEFAULT[:counter].new(attempts)
  @timeout   = options[:timeout]  || DEFAULT[:timeout]
  delay      = options[:delay]    || DEFAULT[:delay]
  @delayer   = options[:delayer]  || DEFAULT[:delayer].new(delay)
  exceptions = options[:rescue]
  @rescuer   = options[:rescuer]  || DEFAULT[:rescuer].new(exceptions)
  @tester    = options[:tester]   || DEFAULT[:tester].new
  @raiser    = options[:raiser]   || DEFAULT[:raiser].new

  # Assign the logger to each of the strategies.
  [@counter, @delayer, @rescuer, @tester, @raiser].each do |strategy|
    strategy.logger = @logger if strategy.respond_to?(:logger=)
  end
end

Instance Method Details

#until(&block) ⇒ Object

Description

Wait#until executes a block until there’s a valid (by default, truthy) result. Useful for blocking script execution until:

  • an HTTP request was successful

  • a port has opened

  • a process has started

  • etc.

Examples

wait = Wait.new
# => #<Wait>
wait.until { Time.now.sec.even? }
# [Counter] attempt 1/5
# [Tester]  result: false
# [Rescuer] rescued: Wait::ResultInvalid
# [Raiser]  not raising: Wait::ResultInvalid
# [Delayer] delaying for 1s
# [Counter] attempt 2/5
# [Tester]  result: true
# => true

If you wish to handle an exception by attempting the block again, pass one or an array of exceptions with the :rescue option.

wait = Wait.new(:rescue => RuntimeError)
# => #<Wait>
wait.until do |attempt|
  case attempt
  when 1 then nil
  when 2 then raise RuntimeError
  when 3 then "foo"
  end
end
# [Counter] attempt 1/5
# [Tester]  result: nil
# [Rescuer] rescued: Wait::ResultInvalid
# [Raiser]  not raising: Wait::ResultInvalid
# [Delayer] delaying for 1s
# [Counter] attempt 2/5
# [Rescuer] rescued: RuntimeError
# [Raiser]  not raising: RuntimeError
# [Delayer] delaying for 1s
# [Counter] attempt 3/5
# [Tester]  result: "foo"
# => "foo"

Returns

The result of the block if valid (by default, truthy).

Raises

If no results are valid, the exception from the last attempt made.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/wait.rb', line 124

def until(&block)
  # Reset the attempt counter.
  @counter.reset
  begin
    # Increment the attempt counter.
    @counter.increment
    # Wrap the given block in a timeout.
    result = Timeout.timeout(@timeout, TimeoutError) do
      # Execute the block and pass the attempt count (an +Integer+) to it.
      yield(@counter.attempt)
    end
    # Raise an exception unless the result is valid.
    @tester.valid?(result) ? result : raise(ResultInvalid)
  rescue TimeoutError, ResultInvalid, *@rescuer.exceptions => exception
    # Log the exception.
    @rescuer.log(exception)
    # Raise the exception if it ought to be.
    raise(exception) if @raiser.raise?(exception)
    # Raise the exception if this was the last attempt.
    raise(exception) if @counter.last_attempt?
    # Sleep before the next attempt.
    @delayer.sleep
    # Try the block again.
    retry
  end
end