From: "Ravi Mistry via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Vaidas Pilkauskas <vaidas.pilkauskas@shopify.com>,
Patrick Steinhardt <ps@pks.im>,
"brian m. carlson" <sandals@crustytoothpaste.net>,
Ravi Mistry <rmistry@google.com>
Subject: [PATCH v3 0/5] http: exponential backoff, configurable status codes, and post_rpc() retries
Date: Sat, 07 Nov 2026 03:40:31 +0000 [thread overview]
Message-ID: <pull.2287.v3.git.1794022836.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2287.git.1793462224.gitgitgadget@gmail.com>
Commit 640657ffd0 (http: add support for HTTP 429 rate limit retries,
2026-03-17) added HTTP 429 retry support using http.maxRetries,
http.retryAfter, and http.maxRetryTime.
In practice, when running Git against hosting backends and reverse proxies
under high concurrency, four gaps remain:
1. Off-by-one retry count and Retry-After Trace2 timing: Because
http_request_recoverable() pre-decrements the retry counter
(--rate_limit_retries) in its loop condition, setting http.maxRetries=1
performs 0 retries (and N configured retries performs N-1 retries). In
addition, the Retry-After header is extracted after Trace2 logging has
already run, so the http/429-retry-after Trace2 event always logs -1.
2. No exponential backoff (and 0s default) when Retry-After is absent: When
a server does not send a Retry-After header, Git defaults
http.retryAfter to 0 (retrying immediately in a tight loop) and waits
the same fixed delay on every retry when configured. Doubling the delay
on each attempt and defaulting http.retryAfter to 10 seconds (10s, 20s,
40s, ...) gives overloaded servers breathing room while keeping
interactive commands reasonably responsive.
3. Transient 5xx errors cannot be retried: Reverse proxies, load balancers,
and Git backends frequently surface transient overload or connection
resets as 502 Bad Gateway, 503 Service Unavailable, or 504 Gateway
Timeout. Because 5xx errors are not universally transient across all
environments, 429 should remain the default, with an opt-in config to
specify additional status codes.
4. HTTP POST requests are not retried: Git's existing 429 retry logic only
covers HTTP GET requests (such as the initial /info/refs handshake).
However, the actual data transfer during fetch and clone, as well as
on-demand blob downloads in partial clones (during checkout, diff, or
blame), are sent as HTTP POST requests (/git-upload-pack) via
post_rpc(), which only retries authentication and immediately aborts on
429 or 5xx.
This 5-patch series addresses all four gaps:
* Patch 1/5 fixes the off-by-one retry count bug and extracts Retry-After
in finish_active_slot() before Trace2 logging runs.
* Patch 2/5 doubles http.retryAfter on each subsequent retry attempt
(capped at http.maxRetryTime) when Retry-After is absent.
* Patch 3/5 adds http.retryStatusCodes (and GIT_HTTP_RETRY_STATUS_CODES),
defaulting to "429", so environments can opt into retrying codes like
"429,502,503,504".
* Patch 4/5 extracts a shared http_retry_wait() helper and extends retry
support to HTTP POST requests in probe_rpc() and post_rpc() (while fixing
a missing Content-Encoding: gzip header on retry).
* Patch 5/5 changes the default of http.retryAfter from 0 to 10 seconds so
Git does not immediately hammer a rate-limiting server when no
Retry-After header is provided.
Prior discussions:
* https://lore.kernel.org/git/20201012184806.166251-3-smcallis@google.com/
* https://lore.kernel.org/git/pull.2008.v6.git.1773752435.gitgitgadget@gmail.com/
Changes since v2:
* Patches 1/5..4/5: Unchanged from v2.
* Added Patch 5/5 to default http.retryAfter to 10 seconds instead of 0
when Retry-After is absent (per feedback from Junio C Hamano and brian m.
carlson).
Changes since v1:
* Patch 1/4: Clarified in the commit message why the http_max_retries <= 0
check was moved after the retry loop.
* Patch 2/4: Passed http_max_retries - rate_limit_retries instead of adding
a separate attempt counter, and clarified the http.retryAfter and
http.maxRetryTime docs.
* Patch 3/4: Restored (Too Many Requests) and clarified
http.retryStatusCodes in the config docs, and moved the 400..599 range
check to the top of is_retryable_http_code().
* Patch 4/4: Added the attempt counter here when replacing
rate_limit_retries with http_retry_wait().
Ravi Mistry (5):
http: fix retry count and Retry-After timing
http: add exponential backoff without Retry-After
http: add http.retryStatusCodes config option
remote-curl: retry transient HTTP errors in post_rpc()
http: default http.retryAfter to 10 seconds
Documentation/config/http.adoc | 61 ++++++++---
http.c | 129 +++++++++++++++--------
http.h | 12 ++-
remote-curl.c | 75 +++++++-------
t/lib-httpd/http-429.sh | 63 ++++++++++--
t/t5584-http-429-retry.sh | 181 +++++++++++++++++++++++++++++++--
6 files changed, 412 insertions(+), 109 deletions(-)
base-commit: 8d1b215c01512b9658d4525c54ff9b9245f9a7e8
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2287%2Frmistry%2Fhttp-exponential-retries-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2287/rmistry/http-exponential-retries-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2287
Range-diff vs v2:
1: d53778dd46 = 1: d53778dd46 http: fix retry count and Retry-After timing
2: fe1e847ded = 2: fe1e847ded http: add exponential backoff without Retry-After
3: 6510dddc39 = 3: 6510dddc39 http: add http.retryStatusCodes config option
4: 786964ff00 = 4: 786964ff00 remote-curl: retry transient HTTP errors in post_rpc()
-: ---------- > 5: 61e3ffe9ab http: default http.retryAfter to 10 seconds
--
gitgitgadget
next prev parent reply other threads:[~2026-11-07 3:40 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 15:57 [PATCH 0/4] http: exponential backoff, configurable status codes, and post_rpc() retries Ravi Mistry via GitGitGadget
2026-10-31 15:57 ` [PATCH 1/4] http: fix retry count and Retry-After timing Ravi Mistry via GitGitGadget
2026-11-03 1:03 ` Junio C Hamano
2026-11-03 19:05 ` Ravi Mistry
2026-10-31 15:57 ` [PATCH 2/4] http: add exponential backoff without Retry-After Ravi Mistry via GitGitGadget
2026-11-03 13:21 ` Junio C Hamano
2026-11-03 19:05 ` Ravi Mistry
2026-10-31 15:57 ` [PATCH 3/4] http: add http.retryStatusCodes config option Ravi Mistry via GitGitGadget
2026-11-03 13:41 ` Junio C Hamano
2026-11-03 19:06 ` Ravi Mistry
2026-10-31 15:57 ` [PATCH 4/4] remote-curl: retry transient HTTP errors in post_rpc() Ravi Mistry via GitGitGadget
2026-11-03 14:47 ` Junio C Hamano
2026-11-03 18:57 ` [PATCH v2 0/4] http: exponential backoff, configurable status codes, and post_rpc() retries Ravi Mistry via GitGitGadget
2026-11-03 18:57 ` [PATCH v2 1/4] http: fix retry count and Retry-After timing Ravi Mistry via GitGitGadget
2026-11-03 18:57 ` [PATCH v2 2/4] http: add exponential backoff without Retry-After Ravi Mistry via GitGitGadget
2026-11-04 22:05 ` brian m. carlson
2026-11-05 0:10 ` Ravi Mistry
2026-11-05 17:45 ` Junio C Hamano
2026-11-05 22:32 ` brian m. carlson
2026-11-06 16:27 ` Ravi Mistry
2026-11-03 18:57 ` [PATCH v2 3/4] http: add http.retryStatusCodes config option Ravi Mistry via GitGitGadget
2026-11-03 20:56 ` Junio C Hamano
2026-11-03 18:57 ` [PATCH v2 4/4] remote-curl: retry transient HTTP errors in post_rpc() Ravi Mistry via GitGitGadget
2026-11-07 3:40 ` Ravi Mistry via GitGitGadget [this message]
2026-11-07 3:40 ` [PATCH v3 1/5] http: fix retry count and Retry-After timing Ravi Mistry via GitGitGadget
2026-11-07 3:40 ` [PATCH v3 2/5] http: add exponential backoff without Retry-After Ravi Mistry via GitGitGadget
2026-11-07 3:40 ` [PATCH v3 3/5] http: add http.retryStatusCodes config option Ravi Mistry via GitGitGadget
2026-11-07 3:40 ` [PATCH v3 4/5] remote-curl: retry transient HTTP errors in post_rpc() Ravi Mistry via GitGitGadget
2026-11-07 3:40 ` [PATCH v3 5/5] http: default http.retryAfter to 10 seconds Ravi Mistry via GitGitGadget
2026-11-07 4:37 ` [PATCH v3 0/5] http: exponential backoff, configurable status codes, and post_rpc() retries Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.2287.v3.git.1794022836.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=ps@pks.im \
--cc=rmistry@google.com \
--cc=sandals@crustytoothpaste.net \
--cc=vaidas.pilkauskas@shopify.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox