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
-
options->retry_after = results.retry_after;
if (options->content_type) {@@ -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;
-
if (options->effective_url && options->base_url) {
if (update_url_from_redirect(options->base_url,
url, options->effective_url)) {@@ -2474,7 +2470,8 @@ static int http_request_recoverable(const char *url,
}
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;diff --git a/t/lib-httpd/http-429.sh b/t/lib-httpd/http-429.sh
index 1a5d7987db..5537fb7ac4 100644
--- a/t/lib-httpd/http-429.sh
+++ b/t/lib-httpd/http-429.sh
@@ -32,12 +32,33 @@ repo_name="${repo_path%%/*}"
safe_name=$(echo "${test_context}-${retry_after}-${repo_name}" | tr '/' '_' | tr -cd 'a-zA-Z0-9_-')
state="http-429-state-${safe_name}"
-# Check if this is the first call (no state directory exists), or if
-# the retry-after-value is "permanent", which indicates a 429 must be
-# returned for every request (even if the state directory exists).
-if test "$retry_after" != permanent && ! mkdir "$state" 2>/dev/null
+max_fails=1
+case "$test_context" in
+ *-fail-2*) max_fails=2 ;;
+ *-fail-3*) max_fails=3 ;;
+esac
+
+should_fail=0
+if test "$retry_after" = permanent
+then
+ should_fail=1
+else
+ mkdir -p "$state" 2>/dev/null
+ i=1
+ while test $i -le $max_fails
+ do
+ if mkdir "$state/$i" 2>/dev/null
+ then
+ should_fail=1
+ break
+ fi
+ i=$((i + 1))
+ done
+fi
+
+if test $should_fail -eq 0
then
- # Already returned 429 once, forward to git-http-backend
+ # Already returned 429 max_fails times, forward to git-http-backend
# Set PATH_INFO to just the repo path (without retry-after value)
# Set GIT_PROJECT_ROOT so git-http-backend can find the repository
# Use exec to replace this process so git-http-backend gets the updated environmentdiff --git a/t/t5584-http-429-retry.sh b/t/t5584-http-429-retry.sh
index a22007b2cf..d92a26893d 100755
--- a/t/t5584-http-429-retry.sh
+++ b/t/t5584-http-429-retry.sh
@@ -51,6 +51,27 @@ test_expect_success 'HTTP 429 with Retry-After is retried and succeeds' '
test_grep "refs/heads/" output
'
+test_expect_success 'http.maxRetries=1 retries once and succeeds' '
+ test_config http.maxRetries 1 &&
+ git ls-remote "$HTTPD_URL/http_429/max-retries-one/none/repo.git" >output 2>err &&
+ test_grep "refs/heads/" output
+'
+
+test_expect_success 'http.maxRetries=2 fails after 3 failures and succeeds on 2 failures' '
+ test_config http.maxRetries 2 &&
+ test_must_fail git ls-remote "$HTTPD_URL/http_429/max-retries-two-fail-3/none/repo.git" 2>err &&
+ test_grep "rate limited" err &&
+ git ls-remote "$HTTPD_URL/http_429/max-retries-two-fail-2/none/repo.git" >output 2>err &&
+ test_grep "refs/heads/" output
+'
+
+test_expect_success 'Trace2 logs actual CURLINFO_RETRY_AFTER value instead of -1' '
+ test_config http.maxRetries 1 &&
+ GIT_TRACE2_EVENT="$(pwd)/trace-retry-after.event" \
+ git ls-remote "$HTTPD_URL/http_429/trace2-retry-after/1/repo.git" >output 2>err &&
+ test_grep "\"key\":\"http/429-retry-after\",\"value\":\"1\"" trace-retry-after.event
+'
+
test_expect_success 'HTTP 429 without Retry-After uses configured default' '
# Enable retries and configure default delay
test_config http.maxRetries 3 &&
--
gitgitgadget