async-ares: blocking resolve timeout handling, better

Perform the actual timeout calculation in the blocking resolv
loop each time in the same way, keeping the logic simpler.

The previous version calculated the timeout once, and then
reduced it by the elapsed time spent in polling/processing.
This is unnecessarily complicated.

Closes #20819
This commit is contained in:
Stefan Eissing 2026-03-05 10:20:58 +01:00 committed by Daniel Stenberg
parent bfb79de206
commit b803dc9f20
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

View file

@ -368,6 +368,32 @@ out:
return result;
}
static timediff_t async_ares_poll_timeout(struct async_ares_ctx *ares,
timediff_t timeout_ms)
{
struct timeval *ares_calced, time_buf, max_timeout;
int itimeout_ms;
#if TIMEDIFF_T_MAX > INT_MAX
itimeout_ms = (timeout_ms > INT_MAX) ? INT_MAX :
((timeout_ms < 0) ? -1 : (int)timeout_ms);
#else
itimeout_ms = (int)timeout_ms;
#endif
max_timeout.tv_sec = itimeout_ms / 1000;
max_timeout.tv_usec = (itimeout_ms % 1000) * 1000;
/* c-ares tells us the shortest timeout of any operation on channel */
ares_calced = ares_timeout(ares->channel, &max_timeout, &time_buf);
/* use the timeout period ares returned to us above if less than one
second is left, otherwise use 1000ms to make sure the progress callback
gets called frequent enough */
if(!ares_calced->tv_sec)
return (timediff_t)(ares_calced->tv_usec / 1000);
else
return 1000;
}
/*
* Curl_async_await()
*
@ -383,67 +409,44 @@ CURLcode Curl_async_await(struct Curl_easy *data,
struct Curl_dns_entry **dns)
{
struct async_ares_ctx *ares = &data->state.async.ares;
struct curltime start = *Curl_pgrs_now(data);
CURLcode result = CURLE_OK;
timediff_t timeout_ms;
DEBUGASSERT(dns);
*dns = NULL; /* clear on entry */
timeout_ms = Curl_timeleft_ms(data);
if(timeout_ms < 0) {
/* already expired! */
connclose(data->conn, "Timed out before name resolve started");
return CURLE_OPERATION_TIMEDOUT;
}
if(!timeout_ms)
timeout_ms = CURL_TIMEOUT_RESOLVE * 1000; /* default name resolve */
/* Wait for the name resolve query to complete. */
/* Wait for the name resolve query to complete or time out. */
while(!result) {
struct timeval *real_timeout, time_buf, max_timeout;
int itimeout_ms;
timediff_t call_timeout_ms;
timediff_t timeout_ms;
#if TIMEDIFF_T_MAX > INT_MAX
itimeout_ms = (timeout_ms > INT_MAX) ? INT_MAX : (int)timeout_ms;
#else
itimeout_ms = (int)timeout_ms;
#endif
timeout_ms = Curl_timeleft_ms(data);
if(!timeout_ms) { /* no applicable timeout from `data`*/
timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &start);
if(elapsed_ms < (CURL_TIMEOUT_RESOLVE * 1000))
timeout_ms = (CURL_TIMEOUT_RESOLVE * 1000) - elapsed_ms;
else
timeout_ms = -1;
}
max_timeout.tv_sec = itimeout_ms / 1000;
max_timeout.tv_usec = (itimeout_ms % 1000) * 1000;
if(timeout_ms < 0) {
result = CURLE_OPERATION_TIMEDOUT;
break;
}
real_timeout = ares_timeout(ares->channel, &max_timeout, &time_buf);
/* use the timeout period ares returned to us above if less than one
second is left, otherwise use 1000ms to make sure the progress callback
gets called frequent enough */
if(!real_timeout->tv_sec)
call_timeout_ms = (timediff_t)(real_timeout->tv_usec / 1000);
else
call_timeout_ms = 1000;
if(Curl_ares_perform(ares->channel, call_timeout_ms) < 0)
return CURLE_UNRECOVERABLE_POLL;
if(Curl_ares_perform(ares->channel,
async_ares_poll_timeout(ares, timeout_ms)) < 0) {
result = CURLE_UNRECOVERABLE_POLL;
break;
}
result = Curl_async_is_resolved(data, dns);
if(result || data->state.async.done)
break;
if(Curl_pgrsUpdate(data))
if(Curl_pgrsUpdate(data)) {
result = CURLE_ABORTED_BY_CALLBACK;
else {
struct curltime now = curlx_now(); /* update in loop */
timediff_t elapsed_ms = curlx_ptimediff_ms(&now, Curl_pgrs_now(data));
if(elapsed_ms <= 0)
timeout_ms -= 1; /* always deduct at least 1 */
else if(elapsed_ms > timeout_ms)
timeout_ms = -1;
else
timeout_ms -= elapsed_ms;
break;
}
if(timeout_ms < 0)
result = CURLE_OPERATION_TIMEDOUT;
}
/* Operation complete, if the lookup was successful we now have the entry