Skip to content

retry: Use jitter by default #482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/req.ex
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,8 @@ defmodule Req do

* `:retry_delay` - if not set, which is the default, the retry delay is determined by
the value of the `Retry-After` header on HTTP 429/503 responses. If the header is not set,
the default delay follows a simple exponential backoff: 1s, 2s, 4s, 8s, ...
the default delay follows a simple exponential backoff with jitter, for example:
0.949s, 1.97s, 3.87s, 7.55s, ...

`:retry_delay` can be set to a function that receives the retry count (starting at 0)
and returns the delay, the number of milliseconds to sleep before making another attempt.
Expand Down
23 changes: 7 additions & 16 deletions lib/req/steps.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2081,7 +2081,8 @@ defmodule Req.Steps do

* `:retry_delay` - if not set, which is the default, the retry delay is determined by
the value of the `Retry-After` header on HTTP 429/503 responses. If the header is not set,
the default delay follows a simple exponential backoff: 1s, 2s, 4s, 8s, ...
the default delay follows a simple exponential backoff with jitter, for example:
0.949s, 1.97s, 3.87s, 7.55s, ...

`:retry_delay` can be set to a function that receives the retry count (starting at 0)
and returns the delay, the number of milliseconds to sleep before making another attempt.
Expand All @@ -2094,19 +2095,9 @@ defmodule Req.Steps do

## Examples

With default options:

iex> Req.get!("https://httpbin.org/status/500,200").status
# 19:02:08.463 [warning] retry: got response with status 500, will retry in 2000ms, 2 attempts left
# 19:02:10.710 [warning] retry: got response with status 500, will retry in 4000ms, 1 attempt left
200

Delay with jitter:

iex> delay = fn n -> trunc(Integer.pow(2, n) * 1000 * (1 - 0.1 * :rand.uniform())) end
iex> Req.get!("https://httpbin.org/status/500,200", retry_delay: delay).status
iex> Req.get!("https://httpbin.org/status/500,200")
# 08:43:19.101 [warning] retry: got response with status 500, will retry in 941ms, 2 attempts left
# 08:43:22.958 [warning] retry: got response with status 500, will retry in 1877s, 1 attempt left
# 08:43:22.958 [warning] retry: got response with status 500, will retry in 1877ms, 1 attempt left
200

"""
Expand Down Expand Up @@ -2232,7 +2223,7 @@ defmodule Req.Steps do
end

defp calculate_retry_delay(request, retry_count) do
case Req.Request.get_option(request, :retry_delay, &exp_backoff/1) do
case Req.Request.get_option(request, :retry_delay, &exp_backoff_with_jitter/1) do
delay when is_integer(delay) ->
{request, delay}

Expand All @@ -2248,8 +2239,8 @@ defmodule Req.Steps do
end
end

defp exp_backoff(n) do
Integer.pow(2, n) * 1000
defp exp_backoff_with_jitter(n) do
trunc(Integer.pow(2, n) * 1000 * (1 - 0.1 * :rand.uniform()))
end

defp log_retry(_, _, _, _, false), do: :ok
Expand Down
Loading