Skip to content
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
2 changes: 2 additions & 0 deletions lib/req.ex
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ defmodule Req do

* `{:delay, milliseconds}` - retry with the given delay.

* `{:delay, milliseconds, request_retry_modifier}` - retry with the given delay. The request object is passed to the request_retry_modifier function immediately before each retry. This can be helpful, for example, if you need to update an auth header that is based on the current time.

* `false/nil` - don't retry.

* `false` - don't retry.
Expand Down
21 changes: 17 additions & 4 deletions lib/req/steps.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1491,7 +1491,7 @@ defmodule Req.Steps do
## Options

* `:raw` - if set to `true`, disables response body decompression. Defaults to `false`.

Note: setting `raw: true` also disables response body decoding in the `decode_body/1` step.

## Examples
Expand Down Expand Up @@ -2151,6 +2151,14 @@ defmodule Req.Steps do
"expected :retry_delay not to be set when the :retry function is returning `{:delay, milliseconds}`"
end

{:delay, delay, request_retry_modifier} when is_function(request_retry_modifier) ->
if !Req.Request.get_option(request, :retry_delay) do
retry(request, response_or_exception, delay, request_retry_modifier)
else
raise ArgumentError,
"expected :retry_delay not to be set when the :retry function is returning `{:delay, milliseconds, request_retry_modifier}`"
end

true ->
retry(request, response_or_exception)

Expand Down Expand Up @@ -2194,14 +2202,18 @@ defmodule Req.Steps do
defp retry(request, response_or_exception, delay_or_nil \\ nil)

defp retry(request, response_or_exception, nil) do
do_retry(request, response_or_exception, &get_retry_delay/3)
do_retry(request, response_or_exception, &get_retry_delay/3, fn request -> request end)
end

defp retry(request, response_or_exception, delay) when is_integer(delay) do
do_retry(request, response_or_exception, fn request, _, _ -> {request, delay} end)
do_retry(request, response_or_exception, fn request, _, _ -> {request, delay} end, fn request -> request end)
end

defp retry(request, response_or_exception, delay, request_retry_modifier) when is_integer(delay) and is_function(request_retry_modifier) do
do_retry(request, response_or_exception, fn request, _, _ -> {request, delay} end, request_retry_modifier)
end

defp do_retry(request, response_or_exception, delay_getter) do
defp do_retry(request, response_or_exception, delay_getter, request_retry_modifier) when is_function(request_retry_modifier) do
retry_count = Req.Request.get_private(request, :req_retry_count, 0)
{request, delay} = delay_getter.(request, response_or_exception, retry_count)
max_retries = Req.Request.get_option(request, :max_retries, 3)
Expand All @@ -2210,6 +2222,7 @@ defmodule Req.Steps do
if retry_count < max_retries do
log_retry(response_or_exception, retry_count, max_retries, delay, log_level)
Process.sleep(delay)
request = request_retry_modifier.(request)
request = Req.Request.put_private(request, :req_retry_count, retry_count + 1)
{request, response_or_exception} = Req.Request.run_request(%{request | halted: false})
Req.Request.halt(request, response_or_exception)
Expand Down
31 changes: 31 additions & 0 deletions test/req/steps_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,37 @@ defmodule Req.StepsTest do
refute_received _
end

@tag :capture_log
test "custom function returning {:delay, milliseconds, request_modifier_fun}", c do
pid = self()

Bypass.expect(c.bypass, "GET", "/", fn conn ->
case Plug.Conn.get_req_header(conn, "x-is-retry") do
[] -> send(pid, :ping)
["true"] -> send(pid, :pong)
end
Plug.Conn.send_resp(conn, 500, "oops")
end)

fun = fn _request, response ->
assert response.status == 500
request_retry_modifier = fn request ->
request |> Req.Request.put_header("x-is-retry", "true")
end
{:delay, 1, request_retry_modifier}
end

request = Req.new(url: c.url, retry: fun)

assert Req.get!(request).status == 500
assert_received :ping
assert_received :pong
assert_received :pong
assert_received :pong
refute_received _
end


@tag :capture_log
test "raise on custom function returning {:delay, milliseconds} when `:retry_delay` is provided",
c do
Expand Down