From: Daniel Stenberg Date: Mon, 4 Aug 2025 12:15:03 +0000 (+0200) Subject: hostip: do DNS cache pruning in milliseconds X-Git-Tag: curl-8_16_0~267 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=854b0e230cd5f84ae6fce3cefe12ee6de12788f3;p=thirdparty%2Fcurl.git 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 --- diff --git a/lib/hostip.c b/lib/hostip.c index 95c2c62242..e01f111a64 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -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) diff --git a/lib/hostip.h b/lib/hostip.h index 56c5e9cad1..5548a6f125 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -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. */ diff --git a/lib/setopt.c b/lib/setopt.c index 93f26d285f..ac1e8177ae 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -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)) { diff --git a/lib/url.c b/lib/url.c index 79d4c52716..9cf920ed3a 100644 --- a/lib/url.c +++ b/lib/url.c @@ -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; diff --git a/lib/urldata.h b/lib/urldata.h index 2ae52840f5..83d7b50e44 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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 */ diff --git a/tests/unit/unit1607.c b/tests/unit/unit1607.c index 26d1457df6..ad0df33be8 100644 --- a/tests/unit/unit1607.c +++ b/tests/unit/unit1607.c @@ -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);