attempt_this

Exception-based retry policy mix-in for Ruby project. Its purpose it to retry a code block given number of times if it throws an exception.


attempt(3.times) do
    # Do something
end

This will retry the code block up to three times. If the last attempt will result in an exception, that exception will be thrown outside of the attempt block. If you don't like that behavior, you can specify a function to be called after all attempts have failed.

is_failed = false
attempt(3.times)
.and_default_to(->{is_failed = true}) do
    # Do something
end

You may want to retry on specific exception types:

attempt(3.times)
.with_filter(RecoverableError1, RecoverableError2) do
    # Do something
end

You may chose how to reset the environment between failed attempts. This is useful for transactions:

attempt(3.times)
.with_reset(->{rollback}) do
    start_transaction
    # Do something
    commit_transaction
end

You can specify delay between failed attempts:


# Wait for 5 seconds between failures.
attempt(3.times)
.with_delay(5) do
    # Do something
end

# Random delay between 30 and 60 seconds.
attempt(3.times)
.with_delay([30..60]) do
    # Do something
end

# Start with 10 seconds delay and double it after each failed attempt.
attempt(5.times)
.with_binary_backoff(10) do
    # Do something
end

Of course, you can combine multiple options together:

attempt(3.times)
.with_delay(30)
.with_reset(->{rollback})
.and_default_to(->{is_failed = true}) do
    # Do something
end

You can store various configurations as scenarios and use them by name. This is useful when you need different approaches for specific cases (handling HTTP failures, for example). But remember that you should register your scenarios before using them:

# Run this code when the application starts
AttemptThis.attempt(5.times).with_filter(*RECOVERABLE_HTTP_ERRORS).scenario(:http)


# And run this from your method:
attempt(:http) do
    # Make an HTTP call
end

Return values

Calling 'attempt' will return result of the code block if there was no exception; otherwise it will return result of the default handler.

Debugging

You may want to disable all retries and let everything fail on the very first attempt as if there was no rerty policy at all; this is useful for debugging (consider investigating an issue inside a code block that should be retried for 20 minutes before the exception surfaces). In this case you can simply disable all retry policies in your project by setting global "enabled" property:

# Disable all retry policies for now...
AttemptThis.enabled = false

puts AttemptThis.enabled?

Enjoy! And feel free to contribute; just make sure you haven't broken any tests by running 'rake' from project's root.