tool: simplify retrycheck()

- By making retry_sleep() a separate funtion that determines how long to
  wait until the next retry.

- switch the retry timer to uint32 to make it uniform across platforms

Closes #21279
This commit is contained in:
Daniel Stenberg 2026-04-09 17:41:27 +02:00
parent 008aa2b38f
commit bb3670f929
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
4 changed files with 105 additions and 86 deletions

View File

@ -185,8 +185,8 @@ struct OperationConfig {
long httpversion; long httpversion;
unsigned long socks5_auth;/* auth bitmask for socks5 proxies */ unsigned long socks5_auth;/* auth bitmask for socks5 proxies */
long req_retry; /* number of retries */ long req_retry; /* number of retries */
long retry_delay_ms; /* delay between retries (in milliseconds), uint32_t retry_delay_ms; /* delay between retries (in milliseconds), 0 means
0 means increase exponentially */ increase exponentially */
long retry_maxtime_ms; /* maximum time to keep retrying */ long retry_maxtime_ms; /* maximum time to keep retrying */
unsigned long mime_options; /* Mime option flags. */ unsigned long mime_options; /* Mime option flags. */

View File

@ -2429,7 +2429,9 @@ static ParameterError opt_secs(struct OperationConfig *config,
config->connecttimeout_ms = val; config->connecttimeout_ms = val;
break; break;
case C_RETRY_DELAY: /* --retry-delay */ case C_RETRY_DELAY: /* --retry-delay */
config->retry_delay_ms = val; if(val >= INT32_MAX)
val = INT32_MAX;
config->retry_delay_ms = (uint32_t)val;
break; break;
case C_RETRY_MAX_TIME: /* --retry-max-time */ case C_RETRY_MAX_TIME: /* --retry-max-time */
config->retry_maxtime_ms = val; config->retry_maxtime_ms = val;

View File

@ -356,35 +356,102 @@ static bool is_outfile_auto_resumable(struct OperationConfig *config,
result != CURLE_WRITE_ERROR && result != CURLE_RANGE_ERROR; result != CURLE_WRITE_ERROR && result != CURLE_RANGE_ERROR;
} }
enum retryreason {
RETRY_NO,
RETRY_ALL_ERRORS,
RETRY_TIMEOUT,
RETRY_CONNREFUSED,
RETRY_HTTP,
RETRY_FTP
};
/* figure out how long to wait until retry */
static uint32_t retry_sleep(struct OperationConfig *config,
struct per_transfer *per,
enum retryreason retry,
bool *retryp)
{
static const char * const m[] = {
"(retrying all errors)",
": timeout",
": connection refused",
": HTTP error",
": FTP error"
};
CURL *curl = per->curl;
uint32_t sleeptime = 0;
if(RETRY_HTTP == retry) {
curl_off_t retry_after = 0;
curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
if(retry_after) {
/* make sure it does not overflow */
if(retry_after > (curl_off_t)(UINT32_MAX / 1000))
sleeptime = UINT32_MAX;
else
sleeptime = (uint32_t)retry_after * 1000U; /* milliseconds */
/* if adding retry_after seconds to the process would exceed the
maximum time allowed for retrying, then exit the retries right
away */
if(config->retry_maxtime_ms) {
timediff_t ms = curlx_timediff_ms(curlx_now(), per->retrystart);
if((CURL_OFF_T_MAX - sleeptime < ms) ||
(ms + sleeptime > config->retry_maxtime_ms)) {
warnf("The Retry-After: time would "
"make this command line exceed the maximum allowed time "
"for retries.");
*retryp = FALSE;
return 0; /* no retry */
}
}
}
}
if(!sleeptime && !config->retry_delay_ms) {
if(!per->retry_sleep)
per->retry_sleep = RETRY_SLEEP_DEFAULT;
else
per->retry_sleep *= 2;
if(per->retry_sleep > RETRY_SLEEP_MAX)
per->retry_sleep = RETRY_SLEEP_MAX;
}
if(!sleeptime)
sleeptime = per->retry_sleep;
warnf("Problem %s. Retrying in %u%s%.*u second%s. "
"%ld retr%s left.",
m[retry - 1], sleeptime / 1000,
(sleeptime % 1000 ? "." : ""),
(sleeptime % 1000 ? 3 : 0),
sleeptime % 1000,
(sleeptime == 1000 ? "" : "s"),
per->retry_remaining,
(per->retry_remaining > 1 ? "ies" : "y"));
return sleeptime;
}
static CURLcode retrycheck(struct OperationConfig *config, static CURLcode retrycheck(struct OperationConfig *config,
struct per_transfer *per, struct per_transfer *per,
CURLcode result, CURLcode result,
bool *retryp, bool *retryp,
long *delayms) uint32_t *delayms)
{ {
CURL *curl = per->curl; CURL *curl = per->curl;
struct OutStruct *outs = &per->outs; struct OutStruct *outs = &per->outs;
enum { enum retryreason reason = RETRY_NO;
RETRY_NO,
RETRY_ALL_ERRORS,
RETRY_TIMEOUT,
RETRY_CONNREFUSED,
RETRY_HTTP,
RETRY_FTP,
RETRY_LAST /* not used */
} retry = RETRY_NO;
if((result == CURLE_OPERATION_TIMEDOUT) || if((result == CURLE_OPERATION_TIMEDOUT) ||
(result == CURLE_COULDNT_RESOLVE_HOST) || (result == CURLE_COULDNT_RESOLVE_HOST) ||
(result == CURLE_COULDNT_RESOLVE_PROXY) || (result == CURLE_COULDNT_RESOLVE_PROXY) ||
(result == CURLE_FTP_ACCEPT_TIMEOUT)) (result == CURLE_FTP_ACCEPT_TIMEOUT))
/* retry timeout always */ /* retry timeout always */
retry = RETRY_TIMEOUT; reason = RETRY_TIMEOUT;
else if(config->retry_connrefused && else if(config->retry_connrefused &&
(result == CURLE_COULDNT_CONNECT)) { (result == CURLE_COULDNT_CONNECT)) {
long oserrno = 0; long oserrno = 0;
curl_easy_getinfo(curl, CURLINFO_OS_ERRNO, &oserrno); curl_easy_getinfo(curl, CURLINFO_OS_ERRNO, &oserrno);
if(SOCKECONNREFUSED == oserrno) if(SOCKECONNREFUSED == oserrno)
retry = RETRY_CONNREFUSED; reason = RETRY_CONNREFUSED;
} }
else if((result == CURLE_OK) || else if((result == CURLE_OK) ||
(config->fail && (result == CURLE_HTTP_RETURNED_ERROR))) { (config->fail && (result == CURLE_HTTP_RETURNED_ERROR))) {
@ -408,7 +475,7 @@ static CURLcode retrycheck(struct OperationConfig *config,
case 504: /* Gateway Timeout */ case 504: /* Gateway Timeout */
case 522: /* Connection Timed Out (Cloudflare) */ case 522: /* Connection Timed Out (Cloudflare) */
case 524: /* Proxy Read Timeout (Cloudflare) */ case 524: /* Proxy Read Timeout (Cloudflare) */
retry = RETRY_HTTP; reason = RETRY_HTTP;
/* /*
* At this point, we have already written data to the output * At this point, we have already written data to the output
* file (or terminal). If we write to a file, we must rewind * file (or terminal). If we write to a file, we must rewind
@ -437,72 +504,24 @@ static CURLcode retrycheck(struct OperationConfig *config,
* amount of users and we are not one of them. All 4xx codes * amount of users and we are not one of them. All 4xx codes
* are transient. * are transient.
*/ */
retry = RETRY_FTP; reason = RETRY_FTP;
} }
if(result && !retry && config->retry_all_errors) if(result && !reason && config->retry_all_errors)
retry = RETRY_ALL_ERRORS; reason = RETRY_ALL_ERRORS;
if(retry) { if(reason) {
long sleeptime = 0;
curl_off_t retry_after = 0;
static const char * const m[] = {
NULL,
"(retrying all errors)",
": timeout",
": connection refused",
": HTTP error",
": FTP error"
};
bool truncate = TRUE; /* truncate output file */ bool truncate = TRUE; /* truncate output file */
if(RETRY_HTTP == retry) { /* how long to wait until next attempt */
curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after); uint32_t sleeptime;
if(retry_after) {
/* store in a 'long', make sure it does not overflow */
if(retry_after > LONG_MAX / 1000)
sleeptime = LONG_MAX;
else if((retry_after * 1000) > sleeptime)
sleeptime = (long)retry_after * 1000; /* milliseconds */
/* if adding retry_after seconds to the process would exceed the *retryp = TRUE;
maximum time allowed for retrying, then exit the retries right
away */
if(config->retry_maxtime_ms) {
timediff_t ms = curlx_timediff_ms(curlx_now(), per->retrystart);
if((CURL_OFF_T_MAX - sleeptime < ms) ||
(ms + sleeptime > config->retry_maxtime_ms)) {
warnf("The Retry-After: time would "
"make this command line exceed the maximum allowed time "
"for retries.");
*retryp = FALSE;
return CURLE_OK; /* no retry */
}
}
}
}
if(!sleeptime && !config->retry_delay_ms) {
if(!per->retry_sleep)
per->retry_sleep = RETRY_SLEEP_DEFAULT;
else
per->retry_sleep *= 2;
if(per->retry_sleep > RETRY_SLEEP_MAX)
per->retry_sleep = RETRY_SLEEP_MAX;
}
if(!sleeptime)
sleeptime = per->retry_sleep;
warnf("Problem %s. "
"Retrying in %ld%s%.*ld second%s. "
"%ld retr%s left.",
m[retry], sleeptime / 1000L,
(sleeptime % 1000L ? "." : ""),
(sleeptime % 1000L ? 3 : 0),
sleeptime % 1000L,
(sleeptime == 1000L ? "" : "s"),
per->retry_remaining,
(per->retry_remaining > 1 ? "ies" : "y"));
sleeptime = retry_sleep(config, per, reason, retryp);
if(!*retryp)
/* no retry */
return CURLE_OK;
per->retry_remaining--; per->retry_remaining--;
/* Skip truncation of outfile if auto-resume is enabled for download and /* Skip truncation of outfile if auto-resume is enabled for download and
@ -576,7 +595,6 @@ static CURLcode retrycheck(struct OperationConfig *config,
outs->bytes = 0; /* clear for next round */ outs->bytes = 0; /* clear for next round */
} }
} }
*retryp = TRUE;
per->num_retries++; per->num_retries++;
*delayms = sleeptime; *delayms = sleeptime;
result = CURLE_OK; result = CURLE_OK;
@ -710,7 +728,7 @@ static CURLcode post_close_output(struct per_transfer *per,
static CURLcode post_per_transfer(struct per_transfer *per, static CURLcode post_per_transfer(struct per_transfer *per,
CURLcode result, CURLcode result,
bool *retryp, bool *retryp,
long *delay) /* milliseconds! */ uint32_t *delay) /* milliseconds! */
{ {
struct OutStruct *outs = &per->outs; struct OutStruct *outs = &per->outs;
struct OperationConfig *config = per->config; struct OperationConfig *config = per->config;
@ -1804,7 +1822,7 @@ static CURLcode check_finished(struct parastate *s)
msg = curl_multi_info_read(s->multi, &rc); msg = curl_multi_info_read(s->multi, &rc);
if(msg) { if(msg) {
bool retry; bool retry;
long delay; uint32_t delay;
struct per_transfer *ended; struct per_transfer *ended;
CURL *easy = msg->easy_handle; CURL *easy = msg->easy_handle;
CURLcode tres = msg->data.result; CURLcode tres = msg->data.result;
@ -1993,7 +2011,7 @@ static CURLcode serial_transfers(CURLSH *share)
} }
for(per = transfers; per;) { for(per = transfers; per;) {
bool retry; bool retry;
long delay_ms; uint32_t delay_ms;
bool bailout = FALSE; bool bailout = FALSE;
struct curltime start; struct curltime start;
@ -2256,7 +2274,7 @@ static CURLcode run_all_transfers(CURLSH *share,
/* cleanup if there are any left */ /* cleanup if there are any left */
for(per = transfers; per;) { for(per = transfers; per;) {
bool retry; bool retry;
long delay; uint32_t delay;
CURLcode result2 = post_per_transfer(per, result, &retry, &delay); CURLcode result2 = post_per_transfer(per, result, &retry, &delay);
if(!result) if(!result)
/* do not overwrite the original error */ /* do not overwrite the original error */

View File

@ -30,15 +30,16 @@
#include "tool_cfgable.h" #include "tool_cfgable.h"
struct per_transfer { struct per_transfer {
char errorbuffer[CURL_ERROR_SIZE];
/* double linked */ /* double linked */
struct per_transfer *next; struct per_transfer *next;
struct per_transfer *prev; struct per_transfer *prev;
struct OperationConfig *config; /* for this transfer */ struct OperationConfig *config; /* for this transfer */
const struct curl_certinfo *certinfo; const struct curl_certinfo *certinfo;
CURL *curl; CURL *curl;
/* NULL or malloced */
char *uploadfile;
long retry_remaining; long retry_remaining;
long retry_sleep_default;
long retry_sleep;
long num_retries; /* counts the performed retries */ long num_retries; /* counts the performed retries */
struct curltime start; /* start of this transfer */ struct curltime start; /* start of this transfer */
struct curltime retrystart; struct curltime retrystart;
@ -62,12 +63,10 @@ struct per_transfer {
curl_off_t ulnow; curl_off_t ulnow;
curl_off_t uploadfilesize; /* expected total amount */ curl_off_t uploadfilesize; /* expected total amount */
curl_off_t uploadedsofar; /* amount delivered from the callback */ curl_off_t uploadedsofar; /* amount delivered from the callback */
uint32_t retry_sleep_default;
uint32_t retry_sleep;
BIT(dltotal_added); /* if the total has been added from this */ BIT(dltotal_added); /* if the total has been added from this */
BIT(ultotal_added); BIT(ultotal_added);
/* NULL or malloced */
char *uploadfile;
char errorbuffer[CURL_ERROR_SIZE];
BIT(infdopen); /* TRUE if infd needs closing */ BIT(infdopen); /* TRUE if infd needs closing */
BIT(noprogress); BIT(noprogress);
BIT(was_last_header_empty); BIT(was_last_header_empty);