lib/attempt.rb in attempt-0.3.2 vs lib/attempt.rb in attempt-0.4.0
- old
+ new
@@ -1,14 +1,14 @@
-require 'timeout'
+require 'safe_timeout'
require 'structured_warnings'
# The Attempt class encapsulates methods related to multiple attempts at
# running the same method before actually failing.
class Attempt
# The version of the attempt library.
- VERSION = '0.3.2'.freeze
+ VERSION = '0.4.0'.freeze
# Warning raised if an attempt fails before the maximum number of tries
# has been reached.
class Warning < StructuredWarnings::StandardWarning; end
@@ -36,25 +36,38 @@
# Determines which exception level to check when looking for errors to
# retry. The default is 'Exception' (i.e. all errors).
attr_accessor :level
# :call-seq:
- # Attempt.new{ |a| ... }
+ # Attempt.new(**kwargs)
#
- # Creates and returns a new +Attempt+ object. Use a block to set the
- # accessors.
+ # Creates and returns a new +Attempt+ object. The supported keyword options
+ # are as follows:
#
- def initialize
- @tries = 3 # Reasonable default
- @interval = 60 # Reasonable default
- @log = nil # Should be an IO handle, if provided
- @increment = nil # Should be an int, if provided
- @timeout = nil # Wrap the code in a timeout block if provided
- @level = Exception # Level of exception to be caught
- @warnings = true # Errors are sent to STDERR as warnings if true
-
- yield self if block_given?
+ # * tries - The number of attempts to make before giving up. The default is 3.
+ # * interval - The delay in seconds between each attempt. The default is 60.
+ # * log - An IO handle or Logger instance where warnings/errors are logged to. The default is nil.
+ # * increment - The amount to increment the interval between tries. The default is 0.
+ # * level - The level of exception to be caught. The default is everything, i.e. Exception.
+ # * warnings - Boolean value that indicates whether or not errors are treated as warnings
+ # until the maximum number of attempts has been made. The default is true.
+ # * timeout - Boolean value to indicate whether or not to automatically wrap your
+ # proc in a SafeTimeout block. The default is false.
+ #
+ # Example:
+ #
+ # a = Attempt.new(tries: 5, increment: 10, timeout: true)
+ # a.attempt{ http.get("http://something.foo.com") }
+ #
+ def initialize(**options)
+ @tries = options[:tries] || 3 # Reasonable default
+ @interval = options[:interval] || 60 # Reasonable default
+ @log = options[:log] # Should be an IO handle, if provided
+ @increment = options[:increment] || 0 # Should be an integer, if provided
+ @timeout = options[:timeout] || false # Wrap the code in a timeout block if provided
+ @level = options[:level] || Exception # Level of exception to be caught
+ @warnings = options[:warnings] || true # Errors are sent to STDERR as warnings if true
end
# Attempt to perform the operation in the provided block up to +tries+
# times, sleeping +interval+ between each try.
#
@@ -63,21 +76,25 @@
#
def attempt
count = 1
begin
if @timeout
- Timeout.timeout(@timeout){ yield }
+ SafeTimeout.timeout(@timeout){ yield }
else
yield
end
rescue @level => error
@tries -= 1
if @tries > 0
msg = "Error on attempt # #{count}: #{error}; retrying"
count += 1
warn Warning, msg if @warnings
- @log.puts msg if @log
+
+ if @log # Accept an IO or Logger object
+ @log.respond_to?(:puts) ? @log.puts(msg) : @log.warn(msg)
+ end
+
@interval += @increment if @increment
sleep @interval
retry
end
raise
@@ -85,36 +102,30 @@
end
end
module Kernel
# :call-seq:
- # attempt(tries = 3, interval = 60, timeout = nil){ # some op }
+ # attempt(tries: 3, interval: 60, timeout: 10){ # some op }
#
# Attempt to perform the operation in the provided block up to +tries+
- # times, sleeping +interval+ between each try. By default the number
+ # times, sleeping +interval+ between each try. By default the number
# of tries defaults to 3, the interval defaults to 60 seconds, and there
# is no timeout specified.
#
# If +timeout+ is provided then the operation is wrapped in a Timeout
- # block as well. This is handy for those rare occasions when an IO
+ # block as well. This is handy for those rare occasions when an IO
# connection could hang indefinitely, for example.
#
# If the operation still fails the (last) error is then re-raised.
#
- # This is really just a wrapper for Attempt.new where the simple case is
- # good enough i.e. you don't care about warnings, increments or logging,
- # and you want a little added convenience.
+ # This is really just a convenient wrapper for Attempt.new + Attempt#attempt.
#
# Example:
#
# # Make 3 attempts to connect to the database, 60 seconds apart.
# attempt{ DBI.connect(dsn, user, passwd) }
#
- def attempt(tries = 3, interval = 60, timeout = nil, &block)
- raise 'no block given' unless block_given?
- Attempt.new{ |a|
- a.tries = tries
- a.interval = interval
- a.timeout = timeout if timeout
- }.attempt(&block)
+ def attempt(**kwargs, &block)
+ object = Attempt.new(kwargs)
+ object.attempt(&block)
end
end