From 8e5bd864160ef9637bff061568fe030819e9cd94 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 23 Nov 2023 15:47:35 +1100 Subject: [PATCH] Process the combined LRU lists in LRU order Only cleanup headers that are less than equal to the rbt's last_used time. Adjust the rbt's last_used time when the target cleaning was not achieved to the oldest value of the remaining set of headers. When updating delegating NS and glue records last_used was not being updated when it should have been. When adding zero TTL records to the tail of the LRU lists set last_used to rbtdb->last_used + 1 rather than now. This appoximately preserves the lists LRU order. (cherry picked from commit 5e8f0e9ceb02ae3ffdabe1b3fbd9cc66b4b506d3) --- lib/dns/rbtdb.c | 78 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index 89b853ec45..55776d5a74 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -477,6 +477,17 @@ struct dns_rbtdb { */ rdatasetheaderlist_t *rdatasets; + /* + * Start point % node_lock_count for next LRU cleanup. + */ + atomic_uint lru_sweep; + + /* + * When performing LRU cleaning limit cleaning to headers that were + * last used at or before this. + */ + _Atomic(isc_stdtime_t) last_used; + /*% * Temporary storage for stale cache nodes and dynamically deleted * nodes that await being cleaned up. @@ -561,8 +572,7 @@ static void expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked, expire_t reason); static void -overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize, - bool tree_locked); +overmem_purge(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked); static void resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader); static void @@ -6444,6 +6454,9 @@ find_header: if (header->rdh_ttl > newheader->rdh_ttl) { set_ttl(rbtdb, header, newheader->rdh_ttl); } + if (header->last_used != now) { + update_header(rbtdb, header, now); + } if (header->noqname == NULL && newheader->noqname != NULL) { @@ -6496,6 +6509,9 @@ find_header: if (header->rdh_ttl > newheader->rdh_ttl) { set_ttl(rbtdb, header, newheader->rdh_ttl); } + if (header->last_used != now) { + update_header(rbtdb, header, now); + } if (header->noqname == NULL && newheader->noqname != NULL) { @@ -6523,6 +6539,8 @@ find_header: idx = newheader->node->locknum; if (IS_CACHE(rbtdb)) { if (ZEROTTL(newheader)) { + newheader->last_used = + rbtdb->last_used + 1; ISC_LIST_APPEND(rbtdb->rdatasets[idx], newheader, link); } else { @@ -6564,6 +6582,8 @@ find_header: INSIST(rbtdb->heaps != NULL); isc_heap_insert(rbtdb->heaps[idx], newheader); if (ZEROTTL(newheader)) { + newheader->last_used = + rbtdb->last_used + 1; ISC_LIST_APPEND(rbtdb->rdatasets[idx], newheader, link); } else { @@ -6969,8 +6989,7 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, } if (cache_is_overmem) { - overmem_purge(rbtdb, rbtnode->locknum, rdataset_size(newheader), - tree_locked); + overmem_purge(rbtdb, newheader, tree_locked); } NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, @@ -10147,7 +10166,9 @@ expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, size_t purgesize, size_t purged = 0; for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]); - header != NULL && purged <= purgesize; header = header_prev) + header != NULL && header->last_used <= rbtdb->last_used && + purged <= purgesize; + header = header_prev) { header_prev = ISC_LIST_PREV(header, link); /* @@ -10171,30 +10192,55 @@ expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, size_t purgesize, * entries under the overmem condition. To recover from this condition quickly, * we cleanup entries up to the size of newly added rdata (passed as purgesize). * - * This process is triggered while adding a new entry, and we specifically avoid - * purging entries in the same LRU bucket as the one to which the new entry will - * belong. Otherwise, we might purge entries of the same name of different RR - * types while adding RRsets from a single response (consider the case where - * we're adding A and AAAA glue records of the same NS name). + * The LRU lists tails are processed in LRU order to the nearest second. + * + * A write lock on the tree must be held. */ static void -overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize, +overmem_purge(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader, bool tree_locked) { - unsigned int locknum; + uint32_t locknum_start = rbtdb->lru_sweep++ % rbtdb->node_lock_count; + uint32_t locknum = locknum_start; + size_t purgesize = rdataset_size(newheader); size_t purged = 0; + isc_stdtime_t min_last_used = 0; + size_t max_passes = 8; - for (locknum = (locknum_start + 1) % rbtdb->node_lock_count; - locknum != locknum_start && purged <= purgesize; - locknum = (locknum + 1) % rbtdb->node_lock_count) - { +again: + do { NODE_LOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write); purged += expire_lru_headers(rbtdb, locknum, purgesize - purged, tree_locked); + /* + * Work out the oldest remaining last_used values of the list + * tails as we walk across the array of lru lists. + */ + rdatasetheader_t *header = + ISC_LIST_TAIL(rbtdb->rdatasets[locknum]); + if (header != NULL && + (min_last_used == 0 || header->last_used < min_last_used)) + { + min_last_used = header->last_used; + } NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write); + locknum = (locknum + 1) % rbtdb->node_lock_count; + } while (locknum != locknum_start && purged <= purgesize); + + /* + * Update rbtdb->last_used if we have walked all the list tails and have + * not freed the required amount of memory. + */ + if (purged < purgesize) { + if (min_last_used != 0) { + rbtdb->last_used = min_last_used; + if (max_passes-- > 0) { + goto again; + } + } } }