hostip: do DNS cache pruning in milliseconds

Instead of using integer seconds. Also: if the cache contains over
30,000 entries after first pruning, it makes anoter round and removes
all entries that are older than half the age of the oldest entry until
it goes below 30,000.

Closes #18160
This commit is contained in:
Daniel Stenberg 2025-08-04 14:15:03 +02:00
parent be71475b13
commit 854b0e230c
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
6 changed files with 51 additions and 45 deletions

View file

@ -180,9 +180,9 @@ create_dnscache_id(const char *name,
}
struct dnscache_prune_data {
time_t now;
time_t oldest; /* oldest time in cache not pruned. */
int max_age_sec;
struct curltime now;
timediff_t oldest_ms; /* oldest time in cache not pruned. */
timediff_t max_age_ms;
};
/*
@ -199,36 +199,36 @@ dnscache_entry_is_stale(void *datap, void *hc)
(struct dnscache_prune_data *) datap;
struct Curl_dns_entry *dns = (struct Curl_dns_entry *) hc;
if(dns->timestamp) {
/* age in seconds */
time_t age = prune->now - dns->timestamp;
if(age >= (time_t)prune->max_age_sec)
if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) {
/* get age in milliseconds */
timediff_t age = curlx_timediff(prune->now, dns->timestamp);
if(age >= prune->max_age_ms)
return TRUE;
if(age > prune->oldest)
prune->oldest = age;
if(age > prune->oldest_ms)
prune->oldest_ms = age;
}
return FALSE;
}
/*
* Prune the DNS cache. This assumes that a lock has already been taken.
* Returns the 'age' of the oldest still kept entry.
* Returns the 'age' of the oldest still kept entry - in milliseconds.
*/
static time_t
dnscache_prune(struct Curl_hash *hostcache, int cache_timeout,
time_t now)
static timediff_t
dnscache_prune(struct Curl_hash *hostcache, int cache_timeout_ms,
struct curltime now)
{
struct dnscache_prune_data user;
user.max_age_sec = cache_timeout;
user.max_age_ms = cache_timeout_ms;
user.now = now;
user.oldest = 0;
user.oldest_ms = 0;
Curl_hash_clean_with_criterium(hostcache,
(void *) &user,
dnscache_entry_is_stale);
return user.oldest;
return user.oldest_ms;
}
static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
@ -261,31 +261,35 @@ static void dnscache_unlock(struct Curl_easy *data,
void Curl_dnscache_prune(struct Curl_easy *data)
{
struct Curl_dnscache *dnscache = dnscache_get(data);
time_t now;
struct curltime now;
/* the timeout may be set -1 (forever) */
int timeout = data->set.dns_cache_timeout;
int timeout_ms = data->set.dns_cache_timeout_ms;
if(!dnscache)
if(!dnscache || (timeout_ms == -1))
/* NULL hostcache means we cannot do it */
return;
dnscache_lock(data, dnscache);
now = time(NULL);
now = curlx_now();
do {
/* Remove outdated and unused entries from the hostcache */
time_t oldest = dnscache_prune(&dnscache->entries, timeout, now);
timediff_t oldest_ms = dnscache_prune(&dnscache->entries, timeout_ms, now);
if(oldest < INT_MAX)
timeout = (int)oldest; /* we know it fits */
if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE) {
if(oldest_ms < INT_MAX)
/* prune the ones over half this age */
timeout_ms = (int)oldest_ms / 2;
else
timeout_ms = INT_MAX/2;
}
else
timeout = INT_MAX - 1;
break;
/* if the cache size is still too big, use the oldest age as new
prune limit */
} while(timeout &&
(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE));
/* if the cache size is still too big, use the oldest age as new prune
limit */
} while(timeout_ms);
dnscache_unlock(data, dnscache);
}
@ -337,13 +341,13 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
}
if(dns && (data->set.dns_cache_timeout != -1)) {
if(dns && (data->set.dns_cache_timeout_ms != -1)) {
/* See whether the returned entry is stale. Done before we release lock */
struct dnscache_prune_data user;
user.now = time(NULL);
user.max_age_sec = data->set.dns_cache_timeout;
user.oldest = 0;
user.now = curlx_now();
user.max_age_ms = data->set.dns_cache_timeout_ms;
user.oldest_ms = 0;
if(dnscache_entry_is_stale(&user, dns)) {
infof(data, "Hostname in DNS cache was stale, zapped");
@ -530,12 +534,12 @@ Curl_dnscache_mk_entry(struct Curl_easy *data,
dns->refcount = 1; /* the cache has the first reference */
dns->addr = addr; /* this is the address(es) */
if(permanent)
dns->timestamp = 0; /* an entry that never goes stale */
if(permanent) {
dns->timestamp.tv_sec = 0; /* an entry that never goes stale */
dns->timestamp.tv_usec = 0; /* an entry that never goes stale */
}
else {
dns->timestamp = time(NULL);
if(dns->timestamp == 0)
dns->timestamp = 1;
dns->timestamp = curlx_now();
}
dns->hostport = port;
if(hostlen)

View file

@ -67,7 +67,7 @@ struct Curl_dns_entry {
struct Curl_https_rrinfo *hinfo;
#endif
/* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */
time_t timestamp;
struct curltime timestamp;
/* reference counter, entry is freed on reaching 0 */
size_t refcount;
/* hostname port number that resolved to addr. */

View file

@ -868,10 +868,12 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option,
case CURLOPT_DNS_CACHE_TIMEOUT:
if(arg < -1)
return CURLE_BAD_FUNCTION_ARGUMENT;
else if(arg > INT_MAX)
arg = INT_MAX;
else if(arg > INT_MAX/1000)
arg = INT_MAX/1000;
s->dns_cache_timeout = (int)arg;
if(arg > 0)
arg *= 1000;
s->dns_cache_timeout_ms = (int)arg;
break;
case CURLOPT_CA_CACHE_TIMEOUT:
if(Curl_ssl_supports(data, SSLSUPP_CA_CACHE)) {

View file

@ -388,7 +388,7 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
set->ftp_filemethod = FTPFILE_MULTICWD;
set->ftp_skip_ip = TRUE; /* skip PASV IP by default */
#endif
set->dns_cache_timeout = 60; /* Timeout every 60 seconds by default */
set->dns_cache_timeout_ms = 60000; /* Timeout every 60 seconds by default */
/* Timeout every 24 hours by default */
set->general_ssl.ca_cache_timeout = 24 * 60 * 60;

View file

@ -1431,7 +1431,7 @@ struct UserDefined {
unsigned char socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */
#endif
struct ssl_general_config general_ssl; /* general user defined SSL stuff */
int dns_cache_timeout; /* DNS cache timeout (seconds) */
int dns_cache_timeout_ms; /* DNS cache timeout (milliseconds) */
unsigned int buffer_size; /* size of receive buffer to use */
unsigned int upload_buffer_size; /* size of upload buffer to use,
keep it >= CURL_MAX_WRITE_SIZE */

View file

@ -193,7 +193,7 @@ static CURLcode test_unit1607(const char *arg)
break;
}
if(dns->timestamp && tests[i].permanent) {
if(dns->timestamp.tv_sec && tests[i].permanent) {
curl_mfprintf(stderr,
"%s:%d tests[%d] failed. the timestamp is not zero "
"but tests[%d].permanent is TRUE\n",
@ -202,7 +202,7 @@ static CURLcode test_unit1607(const char *arg)
break;
}
if(dns->timestamp == 0 && !tests[i].permanent) {
if(dns->timestamp.tv_sec == 0 && !tests[i].permanent) {
curl_mfprintf(stderr, "%s:%d tests[%d] failed. the timestamp is zero "
"but tests[%d].permanent is FALSE\n",
__FILE__, __LINE__, i, i);