-
Notifications
You must be signed in to change notification settings - Fork 180
/
RetryRequest.jl
75 lines (62 loc) · 2.51 KB
/
RetryRequest.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
module RetryRequest
import ..HTTP
import ..Layer, ..request
using ..Sockets
using ..IOExtras
using ..MessageRequest
using ..Messages
import ..@debug, ..DEBUG_LEVEL, ..sprintcompact
"""
request(RetryLayer, ::URI, ::Request, body) -> HTTP.Response
Retry the request if it throws a recoverable exception.
`Base.retry` and `Base.ExponentialBackOff` implement a randomised exponentially
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. `HTTP.IOError`, `Sockets.DNSError`, `Base.EOFError` and `HTTP.StatusError`
(if status is ``5xx`).
"""
abstract type RetryLayer{Next <: Layer} <: Layer end
export RetryLayer
function request(::Type{RetryLayer{Next}}, url, req, body;
retries::Int=4, retry_non_idempotent::Bool=false,
kw...) where Next
retry_request = Base.retry(request,
delays=ExponentialBackOff(n = retries),
check=(s,ex)->begin
retry = isrecoverable(ex, req, retry_non_idempotent)
if retry
@debug 1 "🔄 Retry $ex: $(sprintcompact(req))"
reset!(req.response)
else
@debug 1 "🚷 No Retry: $(no_retry_reason(ex, req))"
end
return s, retry
end)
retry_request(Next, url, req, body; kw...)
end
isrecoverable(e) = false
isrecoverable(e::IOError) = true
isrecoverable(e::Sockets.DNSError) = true
isrecoverable(e::HTTP.StatusError) = e.status == 403 || # Forbidden
e.status == 408 || # Timeout
e.status >= 500 # Server Error
isrecoverable(e, req, retry_non_idempotent) =
isrecoverable(e) &&
!(req.body === body_was_streamed) &&
!(req.response.body === body_was_streamed) &&
(retry_non_idempotent || req.txcount == 0 || isidempotent(req))
# "MUST NOT automatically retry a request with a non-idempotent method"
# https://tools.ietf.org/html/rfc7230#section-6.3.1
function no_retry_reason(ex, req)
buf = IOBuffer()
showcompact(buf, req)
print(buf, ", ",
ex isa HTTP.StatusError ? "HTTP $(ex.status): " :
!isrecoverable(ex) ? "$ex not recoverable, " : "",
(req.body === body_was_streamed) ? "request streamed, " : "",
(req.response.body === body_was_streamed) ? "response streamed, " : "",
!isidempotent(req) ? "$(req.method) non-idempotent" : "")
return String(take!(buf))
end
end # module RetryRequest