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

Make the number of retries configurable #595

Merged
merged 5 commits into from
Mar 20, 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 Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ julia = "1.6"

[extras]
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

Expand Down
29 changes: 27 additions & 2 deletions src/AWSConfig.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
abstract type AbstractAWSConfig end

# https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html
const AWS_MAX_RETRY_ATTEMPTS = 3

"""
max_attempts(::AbstractAWSConfig) -> Int

The number of AWS request retry attempts to make.
Each backend may perform an additional layer backend-specific retries.

`AbstractAWSConfig` subtypes should allow users to override the default.
The default is 3, per the recommendations at
https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html
"""
max_attempts(::AbstractAWSConfig) = AWS_MAX_RETRY_ATTEMPTS

mutable struct AWSConfig <: AbstractAWSConfig
credentials::Union{AWSCredentials,Nothing}
region::String
output::String
max_attempts::Int
end

# provide a default for the new field since people were using the default constructor
AWSConfig(creds, region, output) = AWSConfig(creds, region, output, AWS_MAX_RETRY_ATTEMPTS)

credentials(aws::AWSConfig) = aws.credentials
region(aws::AWSConfig) = aws.region
max_attempts(aws::AWSConfig) = aws.max_attempts

function AWSConfig(;
profile=nothing, creds=AWSCredentials(; profile=profile), region=nothing, output="json"
profile=nothing,
creds=AWSCredentials(; profile=profile),
region=nothing,
output="json",
max_attempts=AWS_MAX_RETRY_ATTEMPTS,
)
region = @something region aws_get_region(; profile=profile)
return AWSConfig(creds, region, output)
return AWSConfig(creds, region, output, max_attempts)
end

"""
Expand Down
3 changes: 2 additions & 1 deletion src/utilities/request.jl
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ function submit_request(aws::AbstractAWSConfig, request::Request; return_headers
"LimitExceededException",
"RequestThrottled",
"PriorRequestNotComplete",
"SlowDown",
]

request.headers["User-Agent"] = user_agent[]
Expand Down Expand Up @@ -190,7 +191,7 @@ function submit_request(aws::AbstractAWSConfig, request::Request; return_headers
return false
end

delays = AWSExponentialBackoff(; max_attempts=3)
delays = AWSExponentialBackoff(; max_attempts=max_attempts(aws))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making the whole delay strategy configurable?

Suggested change
delays = AWSExponentialBackoff(; max_attempts=max_attempts(aws))
delays = delays(aws)

to give more control, e.g. tweak the initial delay or delay between retries.

Perhaps this would require exporting AWSExponentialBackoff so it's easy for users to tweak only the max attempt.


retry(upgrade_error(get_response); check=check, delays=delays)()

Expand Down
2 changes: 1 addition & 1 deletion src/utilities/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ end
# https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html
# Default values for AWS's `standard` retry mode. Note: these can be overridden elsewhere.
Base.@kwdef struct AWSExponentialBackoff
max_attempts::Int = 3
max_attempts::Int = AWS_MAX_RETRY_ATTEMPTS
max_backoff::Float64 = 20.0
rng::AbstractRNG = Random.GLOBAL_RNG
end
Expand Down
59 changes: 59 additions & 0 deletions test/AWS.jl
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,65 @@ end
@test String(response.body) == Patches.body
end

@testset "Default throttling" begin
request = Request(;
service="s3",
api_version="api_version",
request_method="GET",
url="https://s3.us-east-1.amazonaws.com/sample-bucket",
use_response_type=true,
)

retries = Ref{Int}(0)
exception = apply(Patches._throttling_patch(retries)) do
try
AWS.submit_request(aws, request)
return nothing
catch e
if e isa AWSException
return e
else
rethrow()
end
end
end

@test exception isa AWSException
@test exception.code == "SlowDown"
@test retries[] == AWS.max_attempts(aws)
end

@testset "Custom throttling" begin
aws = AWS.AWSConfig(; max_attempts=1)
@test AWS.max_attempts(aws) == 1

request = Request(;
service="s3",
api_version="api_version",
request_method="GET",
url="https://s3.us-east-1.amazonaws.com/sample-bucket",
use_response_type=true,
)

retries = Ref{Int}(0)
exception = apply(Patches._throttling_patch(retries)) do
try
AWS.submit_request(aws, request)
return nothing
catch e
if e isa AWSException
return e
else
rethrow()
end
end
end

@test exception isa AWSException
@test exception.code == "SlowDown"
@test retries[] == AWS.max_attempts(aws)
end

@testset "Not authorized" begin
request = Request(;
service="s3",
Expand Down
21 changes: 21 additions & 0 deletions test/patch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@ function _aws_http_request_patch(response::AWS.Response=_response())
return p
end

function _throttling_patch(retries::Ref{Int})
status = 503
body = """
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n
<Error>
<Code>SlowDown</Code>
<Message>Please reduce your request rate</Message>
<Resource>/mybucket/myfoto.jpg</Resource>
<RequestId>4442587FB7D0A2F9</RequestId>
</Error>
"""

p = @patch function AWS._http_request(::AWS.AbstractBackend, request::Request, ::IO)
retries[] += 1
resp = _response(; status=status, body=body).response
err = HTTP.StatusError(status, request.request_method, request.resource, resp)
throw(AWS.AWSException(err, body))
end
return p
end

_cred_file_patch = @patch function dot_aws_credentials_file()
return ""
end
Expand Down