From 7b9dea5a7c43fd91bcd23d1585d39a726d098285 Mon Sep 17 00:00:00 2001 From: Felix Moessbauer Date: Fri, 8 Nov 2024 15:04:17 +0100 Subject: http: use Retry-After HTTP header to optimize retries Some mirrors like snapshot.debian.org apply strict http rate limits. While apt already has an exponential backoff mechanism implemented, this is not sufficient due to the following reasons: 1. all retries happen roughly at the same time 2. the retry-after information from the server is not used We fix this by improving the algorithm: First, if present, the timestamp or duration of the Retry-After header is added to the exponential backoff, optimizing for success-on-second-try. Second, a random delay is added (delay += [0, delay]) to distribute the retries to not immediately run into the limit again. To avoid super-long delays, this is capped by the configured maximum delay. This logic only becomes active if the option Acquire::Retries::HandleRetryAfter is enabled. Signed-off-by: Felix Moessbauer --- methods/basehttp.cc | 29 ++++++++++++++++++++++++++++- methods/basehttp.h | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) (limited to 'methods') diff --git a/methods/basehttp.cc b/methods/basehttp.cc index 26b633cba..d335e6fa0 100644 --- a/methods/basehttp.cc +++ b/methods/basehttp.cc @@ -263,6 +263,21 @@ bool RequestState::HeaderLine(string const &Line) /*{{{*/ return true; } + if (stringcasecmp(Tag, "Retry-After:") == 0) + { + unsigned long _retry_after_s; + if (RFC1123StrToTime(Val, RetryAfter)) + { + return true; + } + if (StrToNum(Val.c_str(), _retry_after_s, 4, 10) == 1) + { + RetryAfter = time(nullptr) + _retry_after_s; + return true; + } + return _error->Error(_("Unknown date format")); + } + if (Server->RangesAllowed && stringcasecmp(Tag, "Via:") == 0) { auto const parts = VectorizeString(Val, ' '); @@ -884,7 +899,19 @@ int BaseHttpMethod::Loop() 599, // Network Connect Timeout Error }; if (std::find(std::begin(TransientCodes), std::end(TransientCodes), Req.Result) != std::end(TransientCodes)) - Fail(true); + { + if (Req.RetryAfter) + { + std::unordered_map fields; + fields["Retry-After"] = std::to_string(Req.RetryAfter); + SetFailReason("TooManyRequests"); + FailWithContext(std::string("TooManyRequests"), true, fields); + } + else + { + Fail(true); + } + } else Fail(); break; diff --git a/methods/basehttp.h b/methods/basehttp.h index 080ea527e..c077ea384 100644 --- a/methods/basehttp.h +++ b/methods/basehttp.h @@ -57,6 +57,7 @@ struct RequestState enum {Chunked,Stream,Closes} Encoding = Closes; enum {Header, Data} State = Header; std::string Location; + time_t RetryAfter = 0; FileFd File; -- cgit v1.2.3-70-g09d2