Optimize database decref by avoiding locking with refs > 1

Previously, this function always acquires a node write lock if it
might need node cleanup in case the reference decrements to 0.  In
fact, the lock is unnecessary if the reference is larger than 1 and it
can be optimized as an "easy" case. This optimization could even be
"necessary". In some extreme cases, many worker threads could repeat
acquring and releasing the reference on the same node, resulting in
severe lock contention for nothing (as the ref wouldn't decrement to 0
in most cases). This change would prevent noticeable performance
drop like query timeout for such cases.

Co-authored-by: JINMEI Tatuya <jtatuya@infoblox.com>
Co-authored-by: Ondřej Surý <ondrej@isc.org>

(cherry picked from commit 7f4471594d)
This commit is contained in:
JINMEI Tatuya 2025-01-17 16:54:19 -08:00 committed by Ondřej Surý
parent 57187b2c4f
commit 065ffb2eb8
No known key found for this signature in database
GPG key ID: 2820F37E873DEA41

View file

@ -2046,28 +2046,35 @@ decrement_reference(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
isc_result_t result;
bool write_locked;
bool locked = tlock != isc_rwlocktype_none;
rbtdb_nodelock_t *nodelock;
int bucket = node->locknum;
rbtdb_nodelock_t *nodelock = &rbtdb->node_locks[bucket];
bool no_reference = true;
uint_fast32_t refs;
nodelock = &rbtdb->node_locks[bucket];
#define KEEP_NODE(n, r, l) \
((n)->data != NULL || ((l) && (n)->down != NULL) || \
(n) == (r)->origin_node || (n) == (r)->nsec3_origin_node)
/* Handle easy and/or typical case first. */
if (isc_refcount_decrement(&node->references) > 1) {
return false;
}
refs = isc_refcount_decrement(&nodelock->references);
INSIST(refs > 0);
/* Handle easy and typical case first. */
if (!node->dirty && KEEP_NODE(node, rbtdb, locked)) {
if (isc_refcount_decrement(&node->references) == 1) {
refs = isc_refcount_decrement(&nodelock->references);
INSIST(refs > 0);
return true;
} else {
return false;
}
return true;
}
/*
* Node lock ref has decremented to 0 and we may need to clean up the
* node. To clean it up, the node ref needs to decrement to 0 under the
* node write lock, so we regain the ref and try again.
*/
new_reference(rbtdb, node, nlock);
/* Upgrade the lock? */
if (nlock == isc_rwlocktype_read) {
NODE_UNLOCK(&nodelock->lock, isc_rwlocktype_read);