git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH 1/4] http: fix retry count and Retry-After timing

From
Junio C Hamano <gitster@pobox.com>
Date
Nov 3, 2026, 01:03 UTC
Message-ID
<xmqqpkwmenf3.fsf@gitster.g>
In-Reply-To
<80b7f207f42cff0a3dfe23181ee315fdc90e50ff.1793462224.git.gitgitgadget@gmail.com>
"Ravi Mistry via GitGitGadget" <gitgitgadget@gmail.com> writes:
Show 11 quoted lines
> From: Ravi Mistry <rmistry@google.com>
>
> Commit 640657ffd0 (http: add support for HTTP 429 rate limit retries,
> 2026-03-17) introduced two small bugs in HTTP 429 retry handling:
>
> 1. In http_request_recoverable(), rate_limit_retries is initialized to
>    http_max_retries (the number of retries to perform after the initial
>    failed request) and decremented with pre-decrement
>    (--rate_limit_retries) in the while condition. Because of the
>    pre-decrement, setting http.maxRetries=1 performs 0 retries, and
>    more generally N configured retries only retries N-1 times. In
The new test exactly demonstrates it, which is fun.
>    In
>    addition, if the first request returns 401 (HTTP_REAUTH) and the
>    re-authenticated request returns 429 when http.maxRetries is 0,
>    --rate_limit_retries wraps from 0 to -1 (truthy in C).

Hmph, which would mean we would retry essentially forever until the integer wraps around again? That sounds, eh, nasty.

>    Fix this by checking http_max_retries > 0 && rate_limit_retries-- > 0
>    in the loop condition and returning HTTP_ERROR when
>    http_max_retries <= 0.
OK.
Show 53 quoted lines
> 2. In http_request(), CURLINFO_RETRY_AFTER is queried after
>    run_one_slot() has already called handle_curl_result(), which logs
>    results->retry_after to Trace2 ("http/429-retry-after") while it is
>    still -1. Move the CURLINFO_RETRY_AFTER extraction into
>    finish_active_slot() (initializing slot->results->retry_after = -1
>    first) so that both handle_curl_result() and callers of
>    run_one_slot() observe the parsed Retry-After value.
>
> Signed-off-by: Ravi Mistry <rmistry@google.com>
> ---
>  http.c                    | 27 +++++++++++++--------------
>  t/lib-httpd/http-429.sh   | 31 ++++++++++++++++++++++++++-----
>  t/t5584-http-429-retry.sh | 21 +++++++++++++++++++++
>  3 files changed, 60 insertions(+), 19 deletions(-)
>
> diff --git a/http.c b/http.c
> index d79678d01f..0308f973a6 100644
> --- a/http.c
> +++ b/http.c
> @@ -345,11 +345,20 @@ static void finish_active_slot(struct active_request_slot *slot)
>  	if (slot->results) {
>  		slot->results->curl_result = slot->curl_result;
>  		slot->results->http_code = slot->http_code;
> +		slot->results->retry_after = -1;
>  		curl_easy_getinfo(slot->curl, CURLINFO_HTTPAUTH_AVAIL,
>  				  &slot->results->auth_avail);
>  
>  		curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CONNECTCODE,
>  			&slot->results->http_connectcode);
> +#ifdef GIT_CURL_HAVE_CURLINFO_RETRY_AFTER
> +		{
> +			curl_off_t retry_after;
> +			if (curl_easy_getinfo(slot->curl, CURLINFO_RETRY_AFTER,
> +					      &retry_after) == CURLE_OK && retry_after > 0)
> +				slot->results->retry_after = (long)retry_after;
> +		}
> +#endif
>  	}
>  
>  	/* Run callback if appropriate */
> @@ -2286,15 +2295,6 @@ static int http_request(const char *url,
>  
>  	ret = run_one_slot(slot, &results);
>  
> -#ifdef GIT_CURL_HAVE_CURLINFO_RETRY_AFTER
> -	if (ret == HTTP_RATE_LIMITED) {
> -		curl_off_t retry_after;
> -		if (curl_easy_getinfo(slot->curl, CURLINFO_RETRY_AFTER,
> -				      &retry_after) == CURLE_OK && retry_after > 0)
> -			results.retry_after = (long)retry_after;
> -	}
> -#endif
> -
OK, this is the fix #2 explained in the log message.
Show 8 quoted lines
> @@ -2461,10 +2461,6 @@ static int http_request_recoverable(const char *url,
>  	if (ret != HTTP_OK && ret != HTTP_REAUTH && ret != HTTP_RATE_LIMITED)
>  		return ret;
>  
> -	/* If retries are disabled and we got a 429, fail immediately */
> -	if (ret == HTTP_RATE_LIMITED && !http_max_retries)
> -		return HTTP_ERROR;
> -

I am not sure I get this removal. If it is because the change to the next hunk (the condition to loop with the while statement) already covers it, I guess it makes sense, because we never enter the while() loop and we will return HTTP_ERROR more or less the same way as the above if http_max_retries is false.

Show 16 quoted lines
>  	while ((ret == HTTP_REAUTH && --i) ||
> -	       (ret == HTTP_RATE_LIMITED && --rate_limit_retries)) {
> +	       (ret == HTTP_RATE_LIMITED && http_max_retries > 0 &&
> +		rate_limit_retries-- > 0)) {
>  		long retry_delay = -1;
>  		/*
>  		 * The previous request may have put cruft into our output stream; we
> @@ -2518,6 +2515,8 @@ static int http_request_recoverable(const char *url,
>  		ret = http_request(url, result, target, options);
>  	}
>  	if (ret == HTTP_RATE_LIMITED) {
> +		if (http_max_retries <= 0)
> +			return HTTP_ERROR;
>  		trace2_data_string("http", the_repository,
>  				   "http/429-error", "retries-exhausted");
>  		return HTTP_RATE_LIMITED;
Previous: Ravi Mistry via GitGitGadgetNext: Ravi Mistry via GitGitGadget
Message 3 of 14 in “http: exponential backoff, configurable status codes, and post_rpc() retries”
  1. 0/4 http: exponential backoff, configurable status codes, and post_rpc() retriesRavi Mistry via GitGitGadget, Oct 31, 2026
  2. 1/4 http: fix retry count and Retry-After timingRavi Mistry via GitGitGadget, Oct 31, 2026
  3. Junio C HamanoNov 3, 2026
  4. 2/4 http: add exponential backoff without Retry-AfterRavi Mistry via GitGitGadget, Oct 31, 2026
  5. Junio C HamanoNov 3, 2026
  6. 3/4 http: add http.retryStatusCodes config optionRavi Mistry via GitGitGadget, Oct 31, 2026
  7. Junio C HamanoNov 3, 2026
  8. 4/4 remote-curl: retry transient HTTP errors in post_rpc()Ravi Mistry via GitGitGadget, Oct 31, 2026
  9. Junio C HamanoNov 3, 2026
  10. 0/4 http: exponential backoff, configurable status codes, and post_rpc() retriesRavi Mistry via GitGitGadget, Nov 3, 2026
  11. 1/4 http: fix retry count and Retry-After timingRavi Mistry via GitGitGadget, Nov 3, 2026
  12. 2/4 http: add exponential backoff without Retry-AfterRavi Mistry via GitGitGadget, Nov 3, 2026
  13. 3/4 http: add http.retryStatusCodes config optionRavi Mistry via GitGitGadget, Nov 3, 2026
  14. 4/4 remote-curl: retry transient HTTP errors in post_rpc()Ravi Mistry via GitGitGadget, Nov 3, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.