From 5e8f0e9ceb02ae3ffdabe1b3fbd9cc66b4b506d3 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. --- lib/dns/rbt-cachedb.c | 49 +++++++++++++++++++++++++++++++------------ lib/dns/rbtdb.c | 24 ++++++++++++++++++++- lib/dns/rbtdb_p.h | 18 ++++++++++++---- 3 files changed, 73 insertions(+), 18 deletions(-) diff --git a/lib/dns/rbt-cachedb.c b/lib/dns/rbt-cachedb.c index d57eb2a6cc..51d56c4ede 100644 --- a/lib/dns/rbt-cachedb.c +++ b/lib/dns/rbt-cachedb.c @@ -1635,7 +1635,9 @@ expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, size_t purged = 0; for (header = ISC_LIST_TAIL(rbtdb->lru[locknum]); - header != NULL && purged <= purgesize; header = header_prev) + header != NULL && header->last_used <= rbtdb->last_used && + purged <= purgesize; + header = header_prev) { size_t header_size = rdataset_size(header); header_prev = ISC_LIST_PREV(header, link); @@ -1662,25 +1664,22 @@ expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, * we clean up entries up to the size of newly added rdata that triggered * the overmem; this is accessible via newheader. * - * 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. */ void dns__cachedb_overmem(dns_rbtdb_t *rbtdb, dns_slabheader_t *newheader, - unsigned int locknum_start, isc_rwlocktype_t *tlocktypep DNS__DB_FLARG) { - 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 { isc_rwlocktype_t nlocktype = isc_rwlocktype_none; NODE_WRLOCK(&rbtdb->node_locks[locknum].lock, &nlocktype); @@ -1688,6 +1687,30 @@ dns__cachedb_overmem(dns_rbtdb_t *rbtdb, dns_slabheader_t *newheader, purgesize - purged DNS__DB_FLARG_PASS); + /* + * Work out the oldest remaining last_used values of the list + * tails as we walk across the array of lru lists. + */ + dns_slabheader_t *header = ISC_LIST_TAIL(rbtdb->lru[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, &nlocktype); + 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; + } + } } } diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index 78464170b0..900b580058 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -2757,6 +2757,15 @@ find_header: if (header->ttl > newheader->ttl) { dns__rbtdb_setttl(header, newheader->ttl); } + if (header->last_used != now) { + ISC_LIST_UNLINK( + rbtdb->lru[HEADER_NODE(header)->locknum], + header, link); + header->last_used = now; + ISC_LIST_PREPEND( + rbtdb->lru[HEADER_NODE(header)->locknum], + header, link); + } if (header->noqname == NULL && newheader->noqname != NULL) { @@ -2811,6 +2820,15 @@ find_header: if (header->ttl > newheader->ttl) { dns__rbtdb_setttl(header, newheader->ttl); } + if (header->last_used != now) { + ISC_LIST_UNLINK( + rbtdb->lru[HEADER_NODE(header)->locknum], + header, link); + header->last_used = now; + ISC_LIST_PREPEND( + rbtdb->lru[HEADER_NODE(header)->locknum], + header, link); + } if (header->noqname == NULL && newheader->noqname != NULL) { @@ -2839,6 +2857,8 @@ find_header: idx = HEADER_NODE(newheader)->locknum; if (IS_CACHE(rbtdb)) { if (ZEROTTL(newheader)) { + newheader->last_used = + rbtdb->last_used + 1; ISC_LIST_APPEND(rbtdb->lru[idx], newheader, link); } else { @@ -2882,6 +2902,8 @@ find_header: isc_heap_insert(rbtdb->heaps[idx], newheader); newheader->heap = rbtdb->heaps[idx]; if (ZEROTTL(newheader)) { + newheader->last_used = + rbtdb->last_used + 1; ISC_LIST_APPEND(rbtdb->lru[idx], newheader, link); } else { @@ -3263,7 +3285,7 @@ dns__rbtdb_addrdataset(dns_db_t *db, dns_dbnode_t *node, } if (cache_is_overmem) { - dns__cachedb_overmem(rbtdb, newheader, rbtnode->locknum, + dns__cachedb_overmem(rbtdb, newheader, &tlocktype DNS__DB_FLARG_PASS); } diff --git a/lib/dns/rbtdb_p.h b/lib/dns/rbtdb_p.h index ddc98fe7f2..1aa5672533 100644 --- a/lib/dns/rbtdb_p.h +++ b/lib/dns/rbtdb_p.h @@ -273,12 +273,23 @@ struct dns_rbtdb { uint32_t serve_stale_refresh; /* - * This is a linked list used to implement the LRU cache. There will - * be node_lock_count linked lists here. Nodes in bucket 1 will be - * placed on the linked list lru[1]. + * This is an array of linked lists used to implement the LRU cache. + * There will be node_lock_count linked lists here. Nodes in bucket 1 + * will be placed on the linked list lru[1]. */ dns_slabheaderlist_t *lru; + /* + * 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. @@ -607,7 +618,6 @@ dns__cachedb_expireheader(dns_slabheader_t *header, dns_expire_t reason DNS__DB_FLARG); void dns__cachedb_overmem(dns_rbtdb_t *rbtdb, dns_slabheader_t *newheader, - unsigned int locknum_start, isc_rwlocktype_t *tlocktypep DNS__DB_FLARG); ISC_LANG_ENDDECLS