Skip to content
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

Ensure we account for response status if present when retrying #990

Merged
merged 3 commits into from
Jan 11, 2023
Merged
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: 1 addition & 1 deletion src/Messages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ module Messages

export Message, Request, Response,
reset!, status, method, headers, uri, body, resource,
iserror, isredirect, retryablebody, retryable, ischunked, issafe, isidempotent,
iserror, isredirect, retryablebody, retryable, retrylimitreached, ischunked, issafe, isidempotent,
header, hasheader, headercontains, setheader, defaultheader!, appendheader,
removeheader, mkheaders, readheaders, headerscomplete,
readchunksize,
Expand Down
20 changes: 16 additions & 4 deletions src/clientlayers/RetryRequest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ Retry the request if it throws a recoverable exception.
increasing delay is introduced between attempts to avoid exacerbating network
congestion.

Methods of `isrecoverable(e)` define which exception types lead to a retry.
e.g. `Sockets.DNSError`, `Base.EOFError` and `HTTP.StatusError`
(if status is `5xx`).
By default, requests that have a retryable body, where the request wasn't written
or is idempotent will be retries. If the request is made and a response is received
with a status code of 403, 408, 409, 429, or 5xx, the request will be retried.

`retries` controls the # of total retries that will be attempted.

`retry_check` allows passing a custom retry check in the case where the default
retry check _wouldn't_ retry, if `retry_check` returns true, then the request
will be retried anyway.
"""
function retrylayer(handler)
return function(req::Request; retry::Bool=true, retries::Int=4,
Expand All @@ -45,7 +51,10 @@ function retrylayer(handler)
check=(s, ex) -> begin
retryattempt[] += 1
req.context[:retryattempt] = retryattempt[]
retry = retryable(req) || retryablebody(req) && _retry_check(s, ex, req, retry_check)
retry = (
(isrecoverable(ex) && retryable(req)) ||
(retryablebody(req) && !retrylimitreached(req) && _retry_check(s, ex, req, retry_check))
)
if retryattempt[] == retries
req.context[:retrylimitreached] = true
end
Expand All @@ -67,6 +76,9 @@ function retrylayer(handler)
end
end

isrecoverable(ex) = true
isrecoverable(ex::StatusError) = retryable(ex.status)
nickrobinson251 marked this conversation as resolved.
Show resolved Hide resolved

function _retry_check(s, ex, req, check)
resp = req.response
resp_body = get(req.context, :response_body, nothing)
Expand Down
4 changes: 4 additions & 0 deletions test/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,10 @@ end
shouldfail[] = false
status[] = 404
seekstart(req_body)
checked = Ref(0)
@test !HTTP.retryable(404)
check = (s, ex, req, resp, resp_body) -> begin
checked[] += 1
str = String(resp_body)
if str != "404 unexpected error" || resp.status != 404
@error "unexpected response body" str
Expand All @@ -591,6 +594,7 @@ end
resp = HTTP.get("http://localhost:8080/retry"; body=req_body, response_stream=res_body, retry_check=check)
@test isok(resp)
@test String(take!(res_body)) == "hey there sailor"
@test checked[] >= 1
finally
close(server)
HTTP.ConnectionPool.closeall()
Expand Down