fix: usr: Avoid unnecessary locking in the zone/cache database

Prevent lock contention among many worker threads referring to the same database node at the same time.  This would improve zone and cache database performance for the heavily contended database nodes.

Closes #5130

Merge branch '5130-reduce-lock-contention-in-decrement-reference' into 'main'

See merge request isc-projects/bind9!9963
This commit is contained in:
Ondřej Surý 2025-01-22 13:27:40 +00:00
commit 48471fd50c
2 changed files with 102 additions and 90 deletions

View file

@ -616,12 +616,9 @@ delete_node(qpcache_t *qpdb, qpcnode_t *node) {
* then the caller must be holding at least one lock.
*/
static void
newref(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t nlocktype,
isc_rwlocktype_t tlocktype DNS__DB_FLARG) {
uint_fast32_t refs;
qpcnode_ref(node);
refs = isc_refcount_increment0(&node->erefs);
qpcnode_newref(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t nlocktype,
isc_rwlocktype_t tlocktype DNS__DB_FLARG) {
uint_fast32_t refs = isc_refcount_increment0(&node->erefs);
#if DNS_DB_NODETRACE
fprintf(stderr, "incr:node:%s:%s:%u:%p->erefs = %" PRIuFAST32 "\n",
@ -654,6 +651,13 @@ newref(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t nlocktype,
}
}
static void
newref(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t nlocktype,
isc_rwlocktype_t tlocktype DNS__DB_FLARG) {
qpcnode_ref(node);
qpcnode_newref(qpdb, node, nlocktype, tlocktype DNS__DB_FLARG_PASS);
}
static void
cleanup_deadnodes(void *arg);
@ -679,45 +683,45 @@ decref(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t *nlocktypep,
isc_result_t result;
bool locked = *tlocktypep != isc_rwlocktype_none;
bool write_locked = false;
db_nodelock_t *nodelock = NULL;
int bucket = node->locknum;
db_nodelock_t *nodelock = &qpdb->node_locks[bucket];
uint_fast32_t refs;
REQUIRE(*nlocktypep != isc_rwlocktype_none);
nodelock = &qpdb->node_locks[bucket];
refs = isc_refcount_decrement(&node->erefs);
#if DNS_DB_NODETRACE
fprintf(stderr, "decr:node:%s:%s:%u:%p->erefs = %" PRIuFAST32 "\n",
func, file, line, node, refs - 1);
#endif
if (refs > 1) {
qpcnode_unref(node);
return false;
}
#define KEEP_NODE(n, r) ((n)->data != NULL || (n) == (r)->origin_node)
refs = isc_refcount_decrement(&nodelock->references);
#if DNS_DB_NODETRACE
fprintf(stderr,
"decr:nodelock:%s:%s:%u:%p:%p->references = "
"%" PRIuFAST32 "\n",
func, file, line, node, nodelock, refs - 1);
#else
UNUSED(refs);
#endif
/* Handle easy and typical case first. */
if (!node->dirty && KEEP_NODE(node, qpdb)) {
bool no_reference = false;
refs = isc_refcount_decrement(&node->erefs);
#if DNS_DB_NODETRACE
fprintf(stderr,
"decr:node:%s:%s:%u:%p->erefs = %" PRIuFAST32 "\n",
func, file, line, node, refs - 1);
#else
UNUSED(refs);
#endif
if (refs == 1) {
refs = isc_refcount_decrement(&nodelock->references);
#if DNS_DB_NODETRACE
fprintf(stderr,
"decr:nodelock:%s:%s:%u:%p:%p->references = "
"%" PRIuFAST32 "\n",
func, file, line, node, nodelock, refs - 1);
#else
UNUSED(refs);
#endif
no_reference = true;
}
if (!node->dirty && (node->data != NULL || node == qpdb->origin_node)) {
qpcnode_unref(node);
return no_reference;
return false;
}
/*
* 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.
*/
qpcnode_newref(qpdb, node, *nlocktypep, *tlocktypep DNS__DB_FLARG_PASS);
/* Upgrade the lock? */
if (*nlocktypep == isc_rwlocktype_read) {
NODE_FORCEUPGRADE(&nodelock->lock, nlocktypep);
@ -734,8 +738,6 @@ decref(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t *nlocktypep,
return false;
}
INSIST(refs == 1);
if (node->dirty) {
clean_cache_node(qpdb, node);
}
@ -782,7 +784,7 @@ decref(qpcache_t *qpdb, qpcnode_t *node, isc_rwlocktype_t *nlocktypep,
UNUSED(refs);
#endif
if (KEEP_NODE(node, qpdb)) {
if (node->data != NULL || node == qpdb->origin_node) {
goto restore_locks;
}

View file

@ -727,16 +727,11 @@ dns__qpzone_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
}
static void
newref(qpzonedb_t *qpdb, qpznode_t *node DNS__DB_FLARG) {
uint_fast32_t refs;
qpznode_ref(node);
refs = isc_refcount_increment0(&node->erefs);
qpznode_newref(qpzonedb_t *qpdb, qpznode_t *node DNS__DB_FLARG) {
uint_fast32_t refs = isc_refcount_increment0(&node->erefs);
#if DNS_DB_NODETRACE
fprintf(stderr, "incr:node:%s:%s:%u:%p->erefs = %" PRIuFAST32 "\n",
func, file, line, node, refs + 1);
#else
UNUSED(refs);
#endif
if (refs == 0) {
@ -755,6 +750,12 @@ newref(qpzonedb_t *qpdb, qpznode_t *node DNS__DB_FLARG) {
}
}
static void
newref(qpzonedb_t *qpdb, qpznode_t *node DNS__DB_FLARG) {
qpznode_ref(node);
qpznode_newref(qpdb, node DNS__DB_FLARG_PASS);
}
static void
clean_zone_node(qpznode_t *node, uint32_t least_serial) {
dns_slabheader_t *current = NULL, *dcurrent = NULL;
@ -890,40 +891,47 @@ clean_zone_node(qpznode_t *node, uint32_t least_serial) {
static void
decref(qpzonedb_t *qpdb, qpznode_t *node, uint32_t least_serial,
isc_rwlocktype_t *nlocktypep DNS__DB_FLARG) {
db_nodelock_t *nodelock = NULL;
int bucket = node->locknum;
db_nodelock_t *nodelock = &qpdb->node_locks[bucket];
uint_fast32_t refs;
REQUIRE(*nlocktypep != isc_rwlocktype_none);
nodelock = &qpdb->node_locks[bucket];
refs = isc_refcount_decrement(&node->erefs);
#if DNS_DB_NODETRACE
fprintf(stderr, "decr:node:%s:%s:%u:%p->erefs = %" PRIuFAST32 "\n",
func, file, line, node, refs - 1);
#endif
if (refs > 1) {
qpznode_unref(node);
return;
}
refs = isc_refcount_decrement(&nodelock->references);
#if DNS_DB_NODETRACE
fprintf(stderr,
"decr:nodelock:%s:%s:%u:%p:%p->references = "
"%" PRIuFAST32 "\n",
func, file, line, node, nodelock, refs - 1);
#else
UNUSED(refs);
#endif
/* Handle easy and typical case first. */
if (!node->dirty && (node->data != NULL || node == qpdb->origin ||
node == qpdb->nsec3_origin))
{
refs = isc_refcount_decrement(&node->erefs);
#if DNS_DB_NODETRACE
fprintf(stderr,
"decr:node:%s:%s:%u:%p->erefs = %" PRIuFAST32 "\n",
func, file, line, node, refs - 1);
#else
UNUSED(refs);
#endif
if (refs == 1) {
refs = isc_refcount_decrement(&nodelock->references);
#if DNS_DB_NODETRACE
fprintf(stderr,
"decr:nodelock:%s:%s:%u:%p:%p->references = "
"%" PRIuFAST32 "\n",
func, file, line, node, nodelock, refs - 1);
#else
UNUSED(refs);
#endif
}
goto done;
qpznode_unref(node);
return;
}
/*
* 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.
*/
qpznode_newref(qpdb, node DNS__DB_FLARG_PASS);
/* Upgrade the lock? */
if (*nlocktypep == isc_rwlocktype_read) {
NODE_FORCEUPGRADE(&nodelock->lock, nlocktypep);
@ -934,32 +942,34 @@ decref(qpzonedb_t *qpdb, qpznode_t *node, uint32_t least_serial,
fprintf(stderr, "decr:node:%s:%s:%u:%p->erefs = %" PRIuFAST32 "\n",
func, file, line, node, refs - 1);
#endif
if (refs == 1) {
if (node->dirty) {
if (least_serial == 0) {
/*
* Caller doesn't know the least serial.
* Get it.
*/
RWLOCK(&qpdb->lock, isc_rwlocktype_read);
least_serial = qpdb->least_serial;
RWUNLOCK(&qpdb->lock, isc_rwlocktype_read);
}
clean_zone_node(node, least_serial);
}
refs = isc_refcount_decrement(&nodelock->references);
#if DNS_DB_NODETRACE
fprintf(stderr,
"decr:nodelock:%s:%s:%u:%p:%p->references = "
"%" PRIuFAST32 "\n",
func, file, line, node, nodelock, refs - 1);
#else
UNUSED(refs);
#endif
if (refs > 1) {
qpznode_unref(node);
return;
}
done:
if (node->dirty) {
if (least_serial == 0) {
/*
* Caller doesn't know the least serial.
* Get it.
*/
RWLOCK(&qpdb->lock, isc_rwlocktype_read);
least_serial = qpdb->least_serial;
RWUNLOCK(&qpdb->lock, isc_rwlocktype_read);
}
clean_zone_node(node, least_serial);
}
refs = isc_refcount_decrement(&nodelock->references);
#if DNS_DB_NODETRACE
fprintf(stderr,
"decr:nodelock:%s:%s:%u:%p:%p->references = "
"%" PRIuFAST32 "\n",
func, file, line, node, nodelock, refs - 1);
#else
UNUSED(refs);
#endif
qpznode_unref(node);
}