# WithRetry
[![Hex.pm](https://img.shields.io/hexpm/v/with_retry.svg "Hex")](https://hex.pm/with_retry/with_retry)
[![Hex.pm](https://img.shields.io/hexpm/l/with_retry.svg "License")](LICENSE.md)
`with_retry` is an additional code block used for writing
with statements that have retry logic.
[API Reference](https://hexdocs.pm/with_retry/)
## Getting started
### 1. Check requirements
- Elixir 1.7+
### 2. Install WithRetry
Edit `mix.exs` and add `with_retry` to your list of dependencies and applications:
```elixir
def deps do
[{:with_retry, "~> 0.0.1"}]
end
```
Then run `mix deps.get`.
### 3. Use
Configure hosts and queues:
```elixir
defmodule Example do
use WithRetry
def download(url, file) do
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data) do
data
end
end
end
```
## Capturing failures
The `with_retry` captures many possible failures including:
- Pattern mismatch.
- Raise
- Throw
- Exit
All none captured failures will either return in the case of no `else` or
bubble up.
### Pattern Mismatch (else)
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data) do
data
else
_error -> nil
end
```
### Raise (rescue)
```elixir
with_retry %{body: data} <- HTTPX.get(url),
:ok <- File.write!(file, data) do
data
rescue
_ -> nil
end
```
### Throw (catch)
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- might_throw(data),
:ok <- File.write(file, data) do
data
catch
_thrown -> nil
end
```
### Exit (catch)
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- might_exit(data),
:ok <- File.write(file, data) do
data
catch
{:exit, _code} -> nil
end
```
### Combined
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- might_throw(data),
:ok <- might_exit(data),
:ok <- File.write!(file, data) do
data
else
_error -> nil
rescue
_ -> nil
catch
{:exit, _code} -> nil
_thrown -> nil
end
```
## Back Off
The back off (timeouts) can be configured on the last last of the `with_retry`.
The default back off is 5 total tries with 1s in between attempts.
To update the configuration see the following examples.
### No Retry
Setting the `back_off` to `false` will disable retries and
function like a normal `with`.
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: false do
data
end
```
### Passing An Enumerable
The `back_off` accepts any enumerable that returns timeouts in milliseconds.
In this example we wait `250`ms after the first attempt,
`1_000` after the second, and `5_000` after the third.
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: [250, 1_000, 5_000] do
data
end
```
### Build In Back Off Strategies
#### Constant
To retry with a constant timeout use: `constant/1` passing the timeout in `ms`.
Retry endlessly every `5_000`ms.
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: constant(5_000) do
data
end
```
#### Linear
To retry with a linearly increasing timeout use: `linear/2` passing
the base timeout in `ms` and the increase in `ms`.
Retry endlessly starting with `1_000`ms and increasing the timeout with
`1_500` every wait. (e.g. `1_000`, `2_500`, `4_000`, ...)
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: linear(1_000, 1_500) do
data
end
```
#### Exponential
To retry with an exponentially increasing timeout use: `exponential/2` passing
the base timeout in `ms` and the factor.
Retry endlessly starting with `250`ms and doubling after every wait.
(e.g. `250`, `500`, `1_000`, `2_000`, ...)
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: exponential(250, 2) do
data
end
```
### Build In Back Off Modifiers
#### Cap
To cap the retry to a maximum timeout one can use `cap/2` and give a
`back_off` and a cap in ms.
Retry endlessly starting with `250`ms and doubling after every wait,
but capping at `1_500`.
(e.g. `250`, `500`, `1_000`, `1_500`, `1_500`, ...)
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: cap(exponential(250, 2), 1_500) do
data
end
```
#### Max Try
Limit the back off to a given amount of tries using `max_try/2` and give a
`back_off` and a count of tries.
(Including the first attempt.)
Try 4 times with exponential back off.
(e.g. `#1`, wait `250`, `#2`, wait `500`, `#3`, wait `1_000`, `#4`)
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: max_try(exponential(250, 2), 4) do
data
end
```
#### Max Retry
Limit the back off to a given amount of retries using `max_retry/2` and give a
`back_off` and a count of retries.
(Excluding the first attempt.)
Retry 3 times with exponential back off.
(e.g. `#1`, wait `250`, `#2`, wait `500`, `#3`, wait `1_000`, `#4`)
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: max_try(exponential(250, 2), 3) do
data
end
```
#### Limit
Limit execution of the `with_retry` to a given time limit in `ms`
using `limit/2`.
This includes the time spend doing processing the actually `with`.
See: `limit_wait/2` to limit the time spend waiting, excluding execution time.
The prediction is a best effort limitation and a long execution time might
bring the total time spend on executing the `with_try` over the set limit.
Retry as many times as fit within `4_000`ms with exponential back off.
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: limit(exponential(250, 2), 8_000) do
data
end
```
#### Limit Wait
Limit the waiting time of the `with_retry` to a given time limit in `ms`
using `limit_wait/2`.
This excludes the time spend doing processing the actually `with`.
See: `limit/2` to limit the total time, including execution time.
Retry as many times as fit within `8_000`ms with exponential back off.
(e.g. `250`, `500`, `1_000`, `2_000`, `4_000` for a total of `7_750`.)
```elixir
with_retry {:ok, %{body: data}} <- HTTPX.get(url),
:ok <- File.write(file, data),
back_off: limit_wait(exponential(250, 2), 8_000) do
data
end
```
## Copyright and License
Copyright (c) 2018, Ian Luites.
WithRetry code is licensed under the [MIT License](LICENSE.md).