Class: Stud::Try
- Inherits:
-
Object
- Object
- Stud::Try
- Defined in:
- lib/stud/try.rb
Overview
A class implementing ‘retry-on-failure’
Example:
Try.new.try(5.times) { your_code }
A failure is indicated by any exception being raised. On success, the return value of the block is the return value of the try call.
On final failure (ran out of things to try), the last exception is raised.
Defined Under Namespace
Classes: Forever
Constant Summary collapse
- FOREVER =
class Forever
Forever.new
- BACKOFF_SCHEDULE =
[0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.0]
Instance Method Summary collapse
-
#failure(exception, fail_count) ⇒ Object
This method is called when a try attempt fails.
-
#log_failure(exception, fail_count, message) ⇒ Object
Log a failure.
-
#try(enumerable = FOREVER, &block) ⇒ Object
Public: try a block of code until either it succeeds or we give up.
Instance Method Details
#failure(exception, fail_count) ⇒ Object
This method is called when a try attempt fails.
The default implementation will sleep with exponential backoff up to a maximum of 2 seconds (see BACKOFF_SCHEDULE)
exception - the exception causing the failure fail_count - how many times we have failed.
42 43 44 45 46 |
# File 'lib/stud/try.rb', line 42 def failure(exception, fail_count) backoff = BACKOFF_SCHEDULE[fail_count] || BACKOFF_SCHEDULE.last log_failure(exception, fail_count, "Sleeping for #{backoff}") sleep(backoff) end |
#log_failure(exception, fail_count, message) ⇒ Object
Log a failure.
You should override this method if you want a better logger.
31 32 33 |
# File 'lib/stud/try.rb', line 31 def log_failure(exception, fail_count, ) puts "Failed (#{exception}). #{}" end |
#try(enumerable = FOREVER, &block) ⇒ Object
Public: try a block of code until either it succeeds or we give up.
enumerable - an Enumerable or omitted/nil, #each is invoked and is tried
that number of times. If this value is omitted or nil, we will try until
success with no limit on the number of tries.
Returns the return value of the block once the block succeeds. Raises the last seen exception if we run out of tries.
Examples
# Try 10 times to fetch http://google.com/
response = try(10.times) { Net::HTTP.get_response("google.com", "/") }
# Try many times, yielding the value of the enumeration to the block.
# This allows you to try different inputs.
response = try([0, 2, 4, 6]) { |val| 50 / val }
Output:
Failed (divided by 0). Retrying in 0.01 seconds...
=> 25
# Try forever
return_value = try { ... }
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/stud/try.rb', line 72 def try(enumerable=FOREVER, &block) if block.arity == 0 # If the block takes no arguments, give none procedure = lambda { |val| return block.call } else # Otherwise, pass the current 'enumerable' value to the block. procedure = lambda { |val| return block.call(val) } end # Track the last exception so we can reraise it on failure. last_exception = nil # When 'enumerable' runs out of things, if we still haven't succeeded, # we'll reraise fail_count = 0 enumerable.each do |val| begin # If the 'procedure' (the block, really) succeeds, we'll break # and return the return value of the block. Win! return procedure.call(val) rescue NoMethodError, NameError # Abort immediately on exceptions that are unlikely to recover. raise rescue => exception last_exception = exception fail_count += 1 # Note: Since we can't reliably detect the 'end' of an enumeration, we # will call 'failure' for the final iteration (if it failed) and sleep # even though there's no strong reason to backoff on the last error. failure(exception, fail_count) end end # enumerable.each # generally make the exception appear from the 'try' method itself, not from # any deeply nested enumeration/begin/etc # It is my hope that this makes the backtraces easier to read, not more # difficult. If you find this is not the case, please please please let me # know. last_exception.set_backtrace(StandardError.new.backtrace) raise last_exception end |