lib/dns/rbtdb.c
/*
 * Caller must be holding the node lock; either the "strong", read or write
 * lock.  Note that the lock must be held even when node references are
 * atomically modified; in that case the decrement operation itself does not
 * have to be protected, but we must avoid a race condition where multiple
 * threads are decreasing the reference to zero simultaneously and at least
 * one of them is going to free the node.
 * This function returns ISC_TRUE if and only if the node reference decreases
 * to zero.
 */
Caller must be holding the node lock; either the "strong", read or write
lock.  Note that the lock must be held even when node references are
atomically modified; in that case the decrement operation itself does not
have to be protected, but we must avoid a race condition where multiple
threads are decreasing the reference to zero simultaneously and at least
one of them is going to free the node.
This function returns ISC_TRUE if and only if the node reference decreases
to zero.
Caller must be holding the node lock ; either the " strong " read or write lock . Note that the lock must be held even when node references are atomically modified ; in that case the decrement operation itself does not have to be protected but we must avoid a race condition where multiple threads are decreasing the reference to zero simultaneously and at least one of them is going to free the node . This function returns ISC_TRUE if and only if the node reference decreases to zero .
static isc_boolean_t
decrement_reference(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
		    rbtdb_serial_t least_serial,
		    isc_rwlocktype_t nlock, isc_rwlocktype_t tlock,
		    isc_boolean_t pruning)
{
	isc_result_t result;
	isc_boolean_t write_locked;
	rbtdb_nodelock_t *nodelock;
	unsigned int refs, nrefs;
	int bucket = node->locknum;
	isc_boolean_t no_reference;
	nodelock = &rbtdb->node_locks[bucket];
	if (!node->dirty && (node->data != NULL || node->down != NULL)) {
		dns_rbtnode_refdecrement(node, &nrefs);
		INSIST((int)nrefs >= 0);
		if (nrefs == 0) {
			isc_refcount_decrement(&nodelock->references, &refs);
			INSIST((int)refs >= 0);
		}
		return ((nrefs == 0) ? ISC_TRUE : ISC_FALSE);
	}
	if (nlock == isc_rwlocktype_read) {
		NODE_WEAKUNLOCK(&nodelock->lock, isc_rwlocktype_read);
		NODE_WEAKLOCK(&nodelock->lock, isc_rwlocktype_write);
	}
	dns_rbtnode_refdecrement(node, &nrefs);
	INSIST((int)nrefs >= 0);
	if (nrefs > 0) {
		if (nlock == isc_rwlocktype_read)
			NODE_WEAKDOWNGRADE(&nodelock->lock);
		return (ISC_FALSE);
	}
	if (node->dirty && dns_rbtnode_refcurrent(node) == 0) {
		if (IS_CACHE(rbtdb))
			clean_cache_node(rbtdb, node);
		else {
			if (least_serial == 0) {
				RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
				least_serial = rbtdb->least_serial;
				RBTDB_UNLOCK(&rbtdb->lock,
					     isc_rwlocktype_read);
			}
			clean_zone_node(rbtdb, node, least_serial);
		}
	}
	isc_refcount_decrement(&nodelock->references, &refs);
	INSIST((int)refs >= 0);
	if (node->data != NULL || node->down != NULL) {
		if (nlock == isc_rwlocktype_read)
			NODE_WEAKDOWNGRADE(&nodelock->lock);
		return (ISC_TRUE);
	}
	if (tlock != isc_rwlocktype_write) {
		if (tlock == isc_rwlocktype_read)
			result = isc_rwlock_tryupgrade(&rbtdb->tree_lock);
		else
			result = isc_rwlock_trylock(&rbtdb->tree_lock,
						    isc_rwlocktype_write);
		RUNTIME_CHECK(result == ISC_R_SUCCESS ||
			      result == ISC_R_LOCKBUSY);
		write_locked = ISC_TF(result == ISC_R_SUCCESS);
	} else
		write_locked = ISC_TRUE;
	no_reference = ISC_TRUE;
	if (write_locked && dns_rbtnode_refcurrent(node) == 0) {
		if (!pruning && node->parent != NULL &&
		    node->parent->down == node && node->left == NULL &&
		    node->right == NULL && rbtdb->task != NULL) {
			isc_event_t *ev;
			dns_db_t *db;
			ev = isc_event_allocate(rbtdb->common.mctx, NULL,
						DNS_EVENT_RBTPRUNE,
						prune_tree, node,
						sizeof(isc_event_t));
			if (ev != NULL) {
				new_reference(rbtdb, node);
				db = NULL;
				attach((dns_db_t *)rbtdb, &db);
				ev->ev_sender = db;
				isc_task_send(rbtdb->task, &ev);
				no_reference = ISC_FALSE;
			} else {
				isc_log_write(dns_lctx,
					      DNS_LOGCATEGORY_DATABASE,
					      DNS_LOGMODULE_CACHE,
					      ISC_LOG_INFO,
					      "decrement_reference: failed to "
					      "allocate pruning event");
				INSIST(!ISC_LINK_LINKED(node, deadlink));
				ISC_LIST_APPEND(rbtdb->deadnodes[bucket], node,
						deadlink);
			}
		} else {
			if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
				char printname[DNS_NAME_FORMATSIZE];
				isc_log_write(dns_lctx,
					      DNS_LOGCATEGORY_DATABASE,
					      DNS_LOGMODULE_CACHE,
					      ISC_LOG_DEBUG(1),
					      "decrement_reference: "
					      "delete from rbt: %p %s",
					      node,
					      dns_rbt_formatnodename(node,
							printname,
							sizeof(printname)));
			}
			delete_node(rbtdb, node);
		}
	} else if (dns_rbtnode_refcurrent(node) == 0) {
		INSIST(!ISC_LINK_LINKED(node, deadlink));
		ISC_LIST_APPEND(rbtdb->deadnodes[bucket], node, deadlink);
	} else
		no_reference = ISC_FALSE;
	if (nlock == isc_rwlocktype_read)
		NODE_WEAKDOWNGRADE(&nodelock->lock);
	if (tlock == isc_rwlocktype_none)
		if (write_locked)
			RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
	if (tlock == isc_rwlocktype_read)
		if (write_locked)
			isc_rwlock_downgrade(&rbtdb->tree_lock);
	return (no_reference);
}
static isc_boolean_t decrement_reference ( dns_rbtdb_t * rbtdb , dns_rbtnode_t * node , rbtdb_serial_t least_serial , isc_rwlocktype_t nlock , isc_rwlocktype_t tlock , isc_boolean_t pruning ) { isc_result_t result ; isc_boolean_t write_locked ; rbtdb_nodelock_t * nodelock ; unsigned int refs , nrefs ; int bucket = node -> locknum ; isc_boolean_t no_reference ; nodelock = & rbtdb -> node_locks [ bucket ] ; if ( ! node -> dirty && ( node -> data != NULL || node -> down != NULL ) ) { dns_rbtnode_refdecrement ( node , & nrefs ) ; INSIST ( ( int ) nrefs >= 0 ) ; if ( nrefs == 0 ) { isc_refcount_decrement ( & nodelock -> references , & refs ) ; INSIST ( ( int ) refs >= 0 ) ; } return ( ( nrefs == 0 ) ? ISC_TRUE : ISC_FALSE ) ; } if ( nlock == isc_rwlocktype_read ) { NODE_WEAKUNLOCK ( & nodelock -> lock , isc_rwlocktype_read ) ; NODE_WEAKLOCK ( & nodelock -> lock , isc_rwlocktype_write ) ; } dns_rbtnode_refdecrement ( node , & nrefs ) ; INSIST ( ( int ) nrefs >= 0 ) ; if ( nrefs > 0 ) { if ( nlock == isc_rwlocktype_read ) NODE_WEAKDOWNGRADE ( & nodelock -> lock ) ; return ( ISC_FALSE ) ; } if ( node -> dirty && dns_rbtnode_refcurrent ( node ) == 0 ) { if ( IS_CACHE ( rbtdb ) ) clean_cache_node ( rbtdb , node ) ; else { if ( least_serial == 0 ) { RBTDB_LOCK ( & rbtdb -> lock , isc_rwlocktype_read ) ; least_serial = rbtdb -> least_serial ; RBTDB_UNLOCK ( & rbtdb -> lock , isc_rwlocktype_read ) ; } clean_zone_node ( rbtdb , node , least_serial ) ; } } isc_refcount_decrement ( & nodelock -> references , & refs ) ; INSIST ( ( int ) refs >= 0 ) ; if ( node -> data != NULL || node -> down != NULL ) { if ( nlock == isc_rwlocktype_read ) NODE_WEAKDOWNGRADE ( & nodelock -> lock ) ; return ( ISC_TRUE ) ; } if ( tlock != isc_rwlocktype_write ) { if ( tlock == isc_rwlocktype_read ) result = isc_rwlock_tryupgrade ( & rbtdb -> tree_lock ) ; else result = isc_rwlock_trylock ( & rbtdb -> tree_lock , isc_rwlocktype_write ) ; RUNTIME_CHECK ( result == ISC_R_SUCCESS || result == ISC_R_LOCKBUSY ) ; write_locked = ISC_TF ( result == ISC_R_SUCCESS ) ; } else write_locked = ISC_TRUE ; no_reference = ISC_TRUE ; if ( write_locked && dns_rbtnode_refcurrent ( node ) == 0 ) { if ( ! pruning && node -> parent != NULL && node -> parent -> down == node && node -> left == NULL && node -> right == NULL && rbtdb -> task != NULL ) { isc_event_t * ev ; dns_db_t * db ; ev = isc_event_allocate ( rbtdb -> common . mctx , NULL , DNS_EVENT_RBTPRUNE , prune_tree , node , sizeof ( isc_event_t ) ) ; if ( ev != NULL ) { new_reference ( rbtdb , node ) ; db = NULL ; attach ( ( dns_db_t * ) rbtdb , & db ) ; ev -> ev_sender = db ; isc_task_send ( rbtdb -> task , & ev ) ; no_reference = ISC_FALSE ; } else { isc_log_write ( dns_lctx , DNS_LOGCATEGORY_DATABASE , DNS_LOGMODULE_CACHE , ISC_LOG_INFO , " " " " ) ; INSIST ( ! ISC_LINK_LINKED ( node , deadlink ) ) ; ISC_LIST_APPEND ( rbtdb -> deadnodes [ bucket ] , node , deadlink ) ; } } else { if ( isc_log_wouldlog ( dns_lctx , ISC_LOG_DEBUG ( 1 ) ) ) { char printname [ DNS_NAME_FORMATSIZE ] ; isc_log_write ( dns_lctx , DNS_LOGCATEGORY_DATABASE , DNS_LOGMODULE_CACHE , ISC_LOG_DEBUG ( 1 ) , " " " " , node , dns_rbt_formatnodename ( node , printname , sizeof ( printname ) ) ) ; } delete_node ( rbtdb , node ) ; } } else if ( dns_rbtnode_refcurrent ( node ) == 0 ) { INSIST ( ! ISC_LINK_LINKED ( node , deadlink ) ) ; ISC_LIST_APPEND ( rbtdb -> deadnodes [ bucket ] , node , deadlink ) ; } else no_reference = ISC_FALSE ; if ( nlock == isc_rwlocktype_read ) NODE_WEAKDOWNGRADE ( & nodelock -> lock ) ; if ( tlock == isc_rwlocktype_none ) if ( write_locked ) RWUNLOCK ( & rbtdb -> tree_lock , isc_rwlocktype_write ) ; if ( tlock == isc_rwlocktype_read ) if ( write_locked ) isc_rwlock_downgrade ( & rbtdb -> tree_lock ) ; return ( no_reference ) ; }
Caller must be holding the node lock; either the "strong", read or write
 lock.
Caller must be holding the node lock ; either the " strong " read or write lock .
/* Handle easy and typical case first. */ /* Upgrade the lock? */ /* Restore the lock? */ /*
				 * Caller doesn't know the least serial.
				 * Get it.
				 */ /*
	 * XXXDCL should this only be done for cache zones?
	 */ /* Restore the lock? */ /*
	 * Attempt to switch to a write lock on the tree.  If this fails,
	 * we will add this node to a linked list of nodes in this locking
	 * bucket which we will free later.
	 */ /*
		 * Locking hierarchy notwithstanding, we don't need to free
		 * the node lock before acquiring the tree write lock because
		 * we only do a trylock.
		 */ /*
		 * We can now delete the node if the reference counter is
		 * zero.  This should be typically the case, but a different
		 * thread may still gain a (new) reference just before the
		 * current thread locks the tree (e.g., in findnode()).
		 */ /*
		 * If this node is the only one in the level it's in, deleting
		 * this node may recursively make its parent the only node in
		 * the parent level; if so, and if no one is currently using
		 * the parent node, this is almost the only opportunity to
		 * clean it up.  But the recursive cleanup is not that trivial
		 * since the child and parent may be in different lock buckets,
		 * which would cause a lock order reversal problem.  To avoid
		 * the trouble, we'll dispatch a separate event for batch
		 * cleaning.  We need to check whether we're deleting the node
		 * as a result of pruning to avoid infinite dispatching.
		 * Note: pruning happens only when a task has been set for the
		 * rbtdb.  If the user of the rbtdb chooses not to set a task,
		 * it's their responsibility to purge stale leaves (e.g. by
		 * periodic walk-through).
		 */ /*
				 * XXX: this is a weird situation.  We could
				 * ignore this error case, but then the stale
				 * node will unlikely be purged except via a
				 * rare condition such as manual cleanup.  So
				 * we queue it in the deadnodes list, hoping
				 * the memory shortage is temporary and the node
				 * will be deleted later.
				 */ /* Restore the lock? */ /*
	 * Relock a read lock, or unlock the write lock if no lock was held.
	 */