diff --git a/src/http_client_retry.jl b/src/http_client_retry.jl index 56c26b6ee..11c9c2645 100644 --- a/src/http_client_retry.jl +++ b/src/http_client_retry.jl @@ -206,7 +206,7 @@ function _arm_request_retry!( _sleep_retry_delay!(request, delay_ns) || return false, nothing, delay_ns ok = true finally - ok || (bucket !== nothing && token !== nothing && release(bucket::RetryBucket, token, nothing)) + ok || (bucket !== nothing && token !== nothing && release(bucket::RetryBucket, token, _retry_bucket_failure_cost(nothing))) end controller.remaining -= 1 return true, token, delay_ns diff --git a/test/http_retry_tests.jl b/test/http_retry_tests.jl index 86e72ea8e..b495fb166 100644 --- a/test/http_retry_tests.jl +++ b/test/http_retry_tests.jl @@ -511,6 +511,39 @@ end end end +@testset "retry arming refunds the bucket when the deadline preempts the backoff" begin + listener = ND.listen("tcp", "127.0.0.1:0"; backlog = 8) + address = ND.join_host_port("127.0.0.1", Int((NC.addr(listener)::NC.SocketAddrV4).port)) + base_url = "http://$(address)" + seen = Tuple{String, String, String}[] + server_task = _serve_retry_sequence(listener, [ + (status = 503, reason = "Service Unavailable"), + ], seen) + try + # A backoff far beyond the request deadline means the armed retry is + # abandoned before it sleeps. The bucket reservation must be refunded + # rather than the release erroring (it used to be called with `nothing` + # as the failure cost, which has no method). + bucket = HT.RetryBucket(capacity = 15, backoff_scale_factor_ms = 60_000, max_backoff_secs = 60) + response = HT.get( + "$(base_url)/deadline"; + retries = 2, + retry_bucket = bucket, + request_timeout = 1, + status_exception = false, + ) + @test response.status == 503 + _wait_task_retry!(server_task) + @test seen == [("GET", "/deadline", "")] + # Full refund: with capacity 15 a second reservation (cost 10) only + # succeeds if the abandoned one returned its 10. + token = Base.acquire(bucket, only(keys(bucket.partitions))) + Base.release(bucket, token, 0) + finally + HTTP.@try_ignore NC.close(listener) + end +end + @testset "HTTP.open retries idempotent buffered requests" begin listener = ND.listen("tcp", "127.0.0.1:0"; backlog = 8) address = ND.join_host_port("127.0.0.1", Int((NC.addr(listener)::NC.SocketAddrV4).port))