Retry (retry v0.19.0)
View SourceProvides a convenient interface to retrying behavior. All durations are specified in milliseconds.
Examples
use Retry
import Stream
retry with: exponential_backoff |> randomize |> cap(1_000) |> expiry(10_000) do
# interact with external service
end
retry with: linear_backoff(10, 2) |> cap(1_000) |> take(10) do
# interact with external service
end
retry with: cycle([500]) |> take(10) do
# interact with external service
endThe first retry will exponentially increase the delay, fudging each delay up to 10%, until the delay reaches 1 second and then give up after 10 seconds.
The second retry will linearly increase the retry by a factor of 2 from 10ms giving up after 10 attempts.
The third example shows how we can produce a delay stream using standard
Stream functionality. Any stream of integers may be used as the value of
with:.
Summary
Functions
Retry a block of code delaying between each attempt the duration specified by
the next item in the with delay stream.
Retry a block of code until halt is emitted delaying between each attempt
the duration specified by the next item in the with delay stream.
Wait for a block of code to be truthy delaying between each attempt the duration specified by the next item in the delay stream.
Functions
Retry a block of code delaying between each attempt the duration specified by
the next item in the with delay stream.
Both the values and exceptions that will be retried can be customized. To control which values
will be retried, provide the atoms option. To control which exceptions are retried, provide
the rescue_only option. For example:
retry with: ..., atoms: [:not_ok], rescue_only: [CustomError] do
...
endBoth atoms and rescue_only can accept a number of different types:
- An atom (for example:
:not_okay,SomeStruct, orCustomError). In this case, thedoblock will be retried in any of the following cases:- The atom itself is returned
- The atom is returned in the first position of a two-tuple (for example,
{:not_okay, _}) - A struct of that type is returned/raised
- The special atom
:all. In this case, all values/exceptions will be retried. - A function (for example:
fn val -> String.starts_with?(val, "ok") end) or partial function (for example:fn {:error, %SomeStruct{reason: "busy"}} -> true). The function will be called with the return value and thedoblock will be retried if the function returns a truthy value. If the function returns a falsy value or if no function clause matches, thedoblock will not be retried. - A list of any of the above. The
doblock will be retried if any of the items in the list matches.
The after block evaluates only when the do block returns a valid value before timeout.
On the other hand, the else block evaluates only when the do block remains erroneous after timeout.
Example
use Retry
retry with: exponential_backoff() |> cap(1_000) |> expiry(1_000), rescue_only: [CustomError] do
# interact with external service
after
result -> result
else
error -> error
endThe after and else clauses are optional. By default, a successful value is just returned. If
the timeout expires, the last erroneous value is returned or the last exception is re-raised.
Essentially, this:
retry with: ... do
...
endIs equivalent to:
retry with: ... do
...
after
result -> result
else
e when is_exception(e) -> raise e
e -> e
end
Retry a block of code until halt is emitted delaying between each attempt
the duration specified by the next item in the with delay stream.
The return value for block is expected to be {:cont, result}, return
{:halt, result} to end the retry early.
An accumulator can also be specified which might be handy if subsequent retries are dependent on the previous ones.
The initial value of the accumulator is given as a keyword argument acc:.
When the :acc key is given, its value is used as the initial accumulator
and the do block must be changed to use -> clauses, where the left side
of -> receives the accumulated value of the previous iteration and
the expression on the right side must return the :cont/:halt tuple
with new accumulator value as the second element.
Once :halt is returned from the block, or there are no more elements,
the accumulated value is returned.
Example
retry_while with: linear_backoff(500, 1) |> take(5) do
call_service
|> case do
result = %{"errors" => true} -> {:cont, result}
result -> {:halt, result}
end
endExample with acc:
retry_while acc: 0, with: linear_backoff(500, 1) |> take(5) do
acc ->
call_service
|> case do
%{"errors" => true} -> {:cont, acc + 1}
result -> {:halt, result}
end
end
Wait for a block of code to be truthy delaying between each attempt the duration specified by the next item in the delay stream.
The after block evaluates only when the do block returns a truthy
value. On the other hand, the else block evaluates only when the
do block remains falsy after timeout.Both are optional. By default,
a success value will be returned as {:ok, value} and an erroneous
value will be returned as {:error, value}.
Example
wait linear_backoff(500, 1) |> take(5) do
we_there_yet?
after
_ ->
{:ok, "We have arrived!"}
else
_ ->
{:error, "We're still on our way :("}
end