]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add hashmap to qpz_heap
authorAlessio Podda <alessio@isc.org>
Thu, 18 Dec 2025 00:37:53 +0000 (01:37 +0100)
committerAlessio Podda <alessio@isc.org>
Tue, 31 Mar 2026 14:22:56 +0000 (16:22 +0200)
This commit adds a level of indirection to the signing operations.
Instead of being intrusive, the qpz_heap will keep track of which
headers must be resigned through a hashmap.
The intent is to make dns_vecheader_t entirely self-contained. In
particular, the ownership structure between the heap and the headers is
flipped. Before, the headers would "own" the heap, now the heap owns
the header.

lib/dns/include/dns/rdatavec.h
lib/dns/qpzone.c
lib/dns/rdatavec.c
tests/dns/qpzone_test.c
tests/dns/vecheader_test.c

index b92f6762740db36b7f9a32427be30a10cbdc0263..37d10b5a860d7e4fcabffd923ab37febdd83bab8 100644 (file)
@@ -81,35 +81,31 @@ struct dns_vecheader {
        _Atomic(uint16_t)    attributes;
        _Atomic(dns_trust_t) trust;
 
+       dns_typepair_t typepair;
+
+       isc_refcount_t references;
+
        /*%
-        * Locked by the heap lock. Can't be packed together with other fields
-        * since it is protected by a different lock.
+        * Memory context for this header.
         */
-       unsigned int heap_index;
+       isc_mem_t *mctx;
 
        /*%
         * Locked by the owning node's lock.
         */
-       uint32_t       serial;
-       dns_ttl_t      ttl;
-       dns_typepair_t typepair;
+       uint32_t  serial;
+       dns_ttl_t ttl;
 
        /*
         * resigning (zone).
         */
-       isc_stdtime_t resign;
-       uint16_t      resign_lsb : 1;
+       int64_t resign;
 
        /*%
         * Link to the other versions of this rdataset.
         */
        ISC_SLINK(dns_vecheader_t) next_header;
 
-       /*%
-        * The database node objects containing this rdataset, if any.
-        */
-       dns_dbnode_t *node;
-
        /*%
         * Cached glue records for an rdataset of type NS (zone only).
         */
@@ -240,24 +236,10 @@ dns_vecheader_setownercase(dns_vecheader_t *header, const dns_name_t *name);
  * \li 'name' is a valid name.
  */
 
-void
-dns_vecheader_reset(dns_vecheader_t *h, dns_dbnode_t *node);
-/*%<
- * Reset an rdatavec header 'h' so it can be used to store data in
- * database node 'node'.
- */
-
 dns_vecheader_t *
-dns_vecheader_new(isc_mem_t *mctx, dns_dbnode_t *node);
+dns_vecheader_new(isc_mem_t *mctx);
 /*%<
- * Allocate memory for an rdatavec header and initialize it for use
- * in database node 'node'.
- */
-
-void
-dns_vecheader_destroy(dns_vecheader_t **headerp);
-/*%<
- * Free all memory associated with '*headerp'.
+ * Allocate memory for an rdatavec header and initialize it.
  */
 
 dns_vectop_t *
@@ -272,3 +254,8 @@ dns_vectop_destroy(isc_mem_t *mctx, dns_vectop_t **topp);
 /*%<
  * Free all memory associated with '*vectopp'.
  */
+
+/*
+ * Reference counting for dns_vecheader_t
+ */
+ISC_REFCOUNT_DECL(dns_vecheader);
index 0dce5ffb69ed296596982a888d66a40a503a0d79..2731d6b196aae04d9a715235d9529d09370daa6e 100644 (file)
@@ -22,6 +22,7 @@
 #include <isc/async.h>
 #include <isc/atomic.h>
 #include <isc/file.h>
+#include <isc/hashmap.h>
 #include <isc/heap.h>
 #include <isc/hex.h>
 #include <isc/log.h>
 #include "qpzone_p.h"
 #include "rdatavec_p.h"
 
-#define HEADERNODE(h) ((qpznode_t *)((h)->node))
-
-/* Forward declaration */
-static void deletedata(dns_dbnode_t *node, void *data);
-
 #define QPDB_ATTR_LOADED  0x01
 #define QPDB_ATTR_LOADING 0x02
 
@@ -121,6 +117,7 @@ typedef struct qpz_changed {
 typedef ISC_LIST(qpz_changed_t) qpz_changedlist_t;
 
 typedef struct qpz_resigned {
+       qpznode_t *node;
        dns_vecheader_t *header;
        ISC_LINK(struct qpz_resigned) link;
 } qpz_resigned_t;
@@ -166,14 +163,19 @@ typedef struct qpz_heap {
        /* Locks the data in this struct */
        isc_mutex_t lock;
        isc_heap_t *heap;
+       isc_hashmap_t *hashmap;
 } qpz_heap_t;
 
+typedef struct qpz_resign {
+       qpznode_t *node;
+       dns_vecheader_t *header;
+       unsigned int heap_index;
+} qpz_resign_t;
+
 ISC_REFCOUNT_STATIC_DECL(qpz_heap);
 
 struct qpznode {
        DBNODE_FIELDS;
-
-       qpz_heap_t *heap;
        /*
         * 'erefs' counts external references held by a caller: for
         * example, it could be incremented by dns_db_findnode(),
@@ -293,6 +295,79 @@ ISC_REFCOUNT_STATIC_TRACE_DECL(qpznode);
 ISC_REFCOUNT_STATIC_DECL(qpznode);
 #endif
 
+/* Forward declarations for functions used in constructors */
+static void
+qpznode_acquire(qpznode_t *node DNS__DB_FLARG);
+static bool
+qpznode_release(qpznode_t *node DNS__DB_FLARG);
+static void
+qpznode_erefs_increment(qpznode_t *node DNS__DB_FLARG);
+
+/*%
+ * Constructor and destructor for qpz_changed_t
+ */
+static qpz_changed_t *
+qpz_changed_new(isc_mem_t *mctx, qpznode_t *node DNS__DB_FLARG) {
+       qpz_changed_t *changed = isc_mem_get(mctx, sizeof(qpz_changed_t));
+       *changed = (qpz_changed_t){
+               .node = node,
+               .dirty = false,
+               .link = ISC_LINK_INITIALIZER,
+       };
+       qpznode_acquire(node DNS__DB_FLARG_PASS);
+       return changed;
+}
+
+/*%
+ * Constructor and destructor for qpz_resigned_t
+ */
+static qpz_resigned_t *
+qpz_resigned_new(isc_mem_t *mctx, qpznode_t *node, dns_vecheader_t *header) {
+       qpz_resigned_t *resigned = isc_mem_get(mctx, sizeof(qpz_resigned_t));
+       *resigned = (qpz_resigned_t){
+               .node = node,
+               .header = header,
+               .link = ISC_LINK_INITIALIZER,
+       };
+       qpznode_ref(node);
+       dns_vecheader_ref(header);
+       return resigned;
+}
+
+static void
+qpz_resigned_destroy(isc_mem_t *mctx, qpz_resigned_t **resignedp) {
+       qpz_resigned_t *resigned = *resignedp;
+       *resignedp = NULL;
+       qpznode_unref(resigned->node);
+       dns_vecheader_unref(resigned->header);
+       isc_mem_put(mctx, resigned, sizeof(qpz_resigned_t));
+}
+
+/*%
+ * Constructor and destructor for qpz_resign_t
+ */
+static qpz_resign_t *
+qpz_resign_new(isc_mem_t *mctx, qpznode_t *node, dns_vecheader_t *header) {
+       qpz_resign_t *elem = isc_mem_get(mctx, sizeof(qpz_resign_t));
+       *elem = (qpz_resign_t){
+               .node = node,
+               .header = header,
+               .heap_index = 0,
+       };
+       qpznode_ref(node);
+       dns_vecheader_ref(header);
+       return elem;
+}
+
+static void
+qpz_resign_destroy(isc_mem_t *mctx, qpz_resign_t **elemp) {
+       qpz_resign_t *elem = *elemp;
+       *elemp = NULL;
+       qpznode_unref(elem->node);
+       dns_vecheader_unref(elem->header);
+       isc_mem_put(mctx, elem, sizeof(qpz_resign_t));
+}
+
 /* QP trie methods */
 static void
 qp_attach(void *uctx, void *pval, uint32_t ival);
@@ -416,31 +491,6 @@ qpzone_get_locknum(void) {
        return isc_random_uniform(ARRAY_SIZE(qpzone_buckets_g));
 }
 
-/*%
- * Return which RRset should be resigned sooner.  If the RRsets have the
- * same signing time, prefer the other RRset over the SOA RRset.
- */
-static bool
-resign_sooner(void *v1, void *v2) {
-       dns_vecheader_t *h1 = v1;
-       dns_vecheader_t *h2 = v2;
-
-       return h1->resign < h2->resign ||
-              (h1->resign == h2->resign && h1->resign_lsb < h2->resign_lsb) ||
-              (h1->resign == h2->resign && h1->resign_lsb == h2->resign_lsb &&
-               h2->typepair == DNS_SIGTYPEPAIR(dns_rdatatype_soa));
-}
-
-/*%
- * This function sets the heap index into the header.
- */
-static void
-set_index(void *what, unsigned int idx) {
-       dns_vecheader_t *h = what;
-
-       h->heap_index = idx;
-}
-
 static void
 free_glue(isc_mem_t *mctx, dns_glue_t *glue) {
        while (glue != NULL) {
@@ -584,6 +634,128 @@ qpdb_destroy(dns_db_t *arg) {
        qpzone_destroy(qpdb);
 }
 
+/*%
+ * Compare resign times with SOA priority logic.
+ * Returns true if lhs should be resigned sooner than rhs.
+ */
+static bool
+resign_sooner_values(int64_t lhs_resign, int64_t rhs_resign,
+                    dns_typepair_t rhs_typepair) {
+       return lhs_resign < rhs_resign ||
+              (lhs_resign == rhs_resign &&
+               rhs_typepair == DNS_SIGTYPEPAIR(dns_rdatatype_soa));
+}
+
+/*%
+ * Return which RRset should be resigned sooner.  If the RRsets have the
+ * same signing time, prefer the other RRset over the SOA RRset.
+ */
+static bool
+resign_sooner(void *v1, void *v2) {
+       qpz_resign_t *elem1 = v1;
+       qpz_resign_t *elem2 = v2;
+
+       return resign_sooner_values(elem1->header->resign,
+                                   elem2->header->resign,
+                                   elem2->header->typepair);
+}
+
+/*%
+ * This function sets the heap index into the qpz_resign_t.
+ */
+static void
+set_index(void *what, unsigned int idx) {
+       qpz_resign_t *elem = what;
+
+       elem->heap_index = idx;
+}
+
+/*%
+ * Hashmap matching function for qpz_resign_t entries.
+ * Matches based on header and node pointers.
+ */
+static bool
+resign_match(void *elem_ptr, const void *key) {
+       qpz_resign_t *elem = elem_ptr;
+       const qpz_resign_t *search_elem = key;
+
+       return elem->header == search_elem->header &&
+              elem->node == search_elem->node;
+}
+
+/*%
+ * Generate hash value for a heap element based on header pointer.
+ */
+static uint32_t
+resign_hash(dns_vecheader_t *header) {
+       uintptr_t headerptr = (uintptr_t)header;
+       return isc_hash32(&headerptr, sizeof(headerptr), true);
+}
+
+/*%
+ * Find an element in the heap/hashmap by header and node.
+ * Returns ISC_R_SUCCESS if found, ISC_R_NOTFOUND if not found.
+ * If found, *found_elem will point to the element.
+ * Assumes heap lock is already held.
+ */
+static isc_result_t
+resign_lookup(qpz_heap_t *heap, dns_vecheader_t *header, qpznode_t *node,
+             qpz_resign_t **found_elem) {
+       qpz_resign_t search_elem = {
+               .header = header,
+               .node = node,
+       };
+       uint32_t hashval = resign_hash(header);
+
+       return isc_hashmap_find(heap->hashmap, hashval, resign_match,
+                               &search_elem, (void **)found_elem);
+}
+
+/*%
+ * Add an element to the heap/hashmap.
+ * Assumes heap lock is already held.
+ */
+static void
+resign_register(qpz_heap_t *heap, qpznode_t *node, dns_vecheader_t *header) {
+       qpz_resign_t *elem = qpz_resign_new(heap->mctx, node, header);
+       uint32_t hashval = resign_hash(header);
+
+       /* Verify invariant: element should not already be in hashmap */
+       isc_result_t result = isc_hashmap_add(heap->hashmap, hashval,
+                                             resign_match, elem, elem, NULL);
+       INSIST(result == ISC_R_SUCCESS);
+
+       isc_heap_insert(heap->heap, elem);
+}
+
+/*%
+ * Remove an element from the heap/hashmap.
+ * Assumes heap lock is already held.
+ */
+static void
+resign_unregister(qpz_heap_t *heap, qpznode_t *node, dns_vecheader_t *header) {
+       if (header == NULL) {
+               return;
+       }
+
+       qpz_resign_t *found_elem = NULL;
+       uint32_t hashval = resign_hash(header);
+       qpz_resign_t search_elem = {
+               .header = header,
+               .node = node,
+       };
+       isc_result_t result = isc_hashmap_find(heap->hashmap, hashval,
+                                              resign_match, &search_elem,
+                                              (void **)&found_elem);
+
+       if (result == ISC_R_SUCCESS) {
+               isc_heap_delete(heap->heap, found_elem->heap_index);
+               isc_hashmap_delete(heap->hashmap, hashval, resign_match,
+                                  found_elem);
+               qpz_resign_destroy(heap->mctx, &found_elem);
+       }
+}
+
 static qpz_heap_t *
 new_qpz_heap(isc_mem_t *mctx) {
        qpz_heap_t *new_heap = isc_mem_get(mctx, sizeof(*new_heap));
@@ -595,36 +767,42 @@ new_qpz_heap(isc_mem_t *mctx) {
        isc_heap_create(mctx, resign_sooner, set_index, 0, &new_heap->heap);
        isc_mem_attach(mctx, &new_heap->mctx);
 
+       isc_hashmap_create(mctx, 1u, &new_heap->hashmap);
+
        return new_heap;
 }
 
-/*
- * This function accesses the heap lock through the header and node rather than
- * directly through &qpdb->heap->lock to handle a critical race condition.
- *
- * Consider this scenario:
- * 1. A reference is taken to a qpznode
- * 2. The database containing that node is freed
- * 3. The qpznode reference is finally released
- *
- * When the qpznode reference is released, it needs to unregister all its
- * vecheaders from the resigning heap. The heap is a separate refcounted
- * object with references from both the database and every qpznode. This
- * design ensures that even after the database is destroyed, if nodes are
- * still alive, the heap remains accessible for safe cleanup.
- *
- * Accessing the heap lock through the database (&qpdb->heap->lock) would
- * cause a segfault in this scenario, even though the heap itself is still
- * alive. By going through the node's heap reference, we maintain safe access
- * to the heap lock regardless of the database's lifecycle.
- */
-static isc_mutex_t *
-get_heap_lock(dns_vecheader_t *header) {
-       return &HEADERNODE(header)->heap->lock;
+static void
+resign_rollback(qpzonedb_t *qpdb, qpznode_t *node, qpz_version_t *version,
+               dns_vecheader_t *header DNS__DB_FLARG) {
+       if (header == NULL) {
+               return;
+       }
+
+       qpz_resigned_t *resigned = qpz_resigned_new(((dns_db_t *)qpdb)->mctx,
+                                                   node, header);
+
+       RWLOCK(&qpdb->lock, isc_rwlocktype_write);
+       ISC_LIST_APPEND(version->resigned_list, resigned, link);
+       RWUNLOCK(&qpdb->lock, isc_rwlocktype_write);
 }
 
 static void
 qpz_heap_destroy(qpz_heap_t *qpheap) {
+       /* Clean up hashmap entries */
+       isc_hashmap_iter_t *iter = NULL;
+       isc_hashmap_iter_create(qpheap->hashmap, &iter);
+       for (isc_result_t result = isc_hashmap_iter_first(iter);
+            result == ISC_R_SUCCESS; result = isc_hashmap_iter_next(iter))
+       {
+               qpz_resign_t *elem = NULL;
+               isc_hashmap_iter_current(iter, (void **)&elem);
+               /* Release the node reference and deallocate */
+               qpz_resign_destroy(qpheap->mctx, &elem);
+       }
+       isc_hashmap_iter_destroy(&iter);
+       isc_hashmap_destroy(&qpheap->hashmap);
+
        isc_mutex_destroy(&qpheap->lock);
        isc_heap_destroy(&qpheap->heap);
        isc_mem_putanddetach(&qpheap->mctx, qpheap, sizeof(*qpheap));
@@ -638,14 +816,12 @@ new_qpznode(qpzonedb_t *qpdb, const dns_name_t *name, dns_namespace_t nspace) {
                .methods = &qpznode_methods,
                .name = DNS_NAME_INITEMPTY,
                .nspace = nspace,
-               .heap = qpdb->heap,
                .references = ISC_REFCOUNT_INITIALIZER(1),
                .locknum = qpzone_get_locknum(),
        };
 
        isc_mem_attach(qpdb->common.mctx, &newdata->mctx);
        dns_name_dup(name, qpdb->common.mctx, &newdata->name);
-       qpz_heap_ref(newdata->heap);
 
 #if DNS_DB_NODETRACE
        fprintf(stderr, "new_qpznode:%s:%s:%d:%p->references = 1\n", __func__,
@@ -824,7 +1000,7 @@ first_existing_header(dns_vectop_t *top, uint32_t serial) {
 }
 
 static void
-clean_multiple_headers(dns_vectop_t *top) {
+clean_multiple_headers(qpz_heap_t *heap, qpznode_t *node, dns_vectop_t *top) {
        uint32_t parent_serial = UINT32_MAX;
 
        REQUIRE(top != NULL);
@@ -835,8 +1011,10 @@ clean_multiple_headers(dns_vectop_t *top) {
 
                if (header->serial == parent_serial || IGNORE(header)) {
                        ISC_SLIST_PTR_REMOVE(p, header, next_header);
-                       deletedata(header->node, header);
-                       dns_vecheader_destroy(&header);
+                       LOCK(&heap->lock);
+                       resign_unregister(heap, node, header);
+                       UNLOCK(&heap->lock);
+                       dns_vecheader_unref(header);
                } else {
                        parent_serial = header->serial;
                        ISC_SLIST_PTR_ADVANCE(p, next_header);
@@ -845,7 +1023,8 @@ clean_multiple_headers(dns_vectop_t *top) {
 }
 
 static bool
-clean_multiple_versions(dns_vectop_t *top, uint32_t least_serial) {
+clean_multiple_versions(qpz_heap_t *heap, qpznode_t *node, dns_vectop_t *top,
+                       uint32_t least_serial) {
        REQUIRE(top != NULL);
 
        if (ISC_SLIST_EMPTY(top->headers)) {
@@ -859,8 +1038,10 @@ clean_multiple_versions(dns_vectop_t *top, uint32_t least_serial) {
                dns_vecheader_t *header = *p;
                if (header->serial < least_serial) {
                        ISC_SLIST_PTR_REMOVE(p, header, next_header);
-                       deletedata(header->node, header);
-                       dns_vecheader_destroy(&header);
+                       LOCK(&heap->lock);
+                       resign_unregister(heap, node, header);
+                       UNLOCK(&heap->lock);
+                       dns_vecheader_unref(header);
                } else {
                        multiple = true;
                        ISC_SLIST_PTR_ADVANCE(p, next_header);
@@ -870,7 +1051,7 @@ clean_multiple_versions(dns_vectop_t *top, uint32_t least_serial) {
 }
 
 static void
-clean_zone_node(qpznode_t *node, uint32_t least_serial) {
+clean_zone_node(qpz_heap_t *heap, qpznode_t *node, uint32_t least_serial) {
        bool still_dirty = false;
 
        /*
@@ -888,7 +1069,7 @@ clean_zone_node(qpznode_t *node, uint32_t least_serial) {
                 * with the same serial number, or that have the IGNORE
                 * attribute.
                 */
-               clean_multiple_headers(top);
+               clean_multiple_headers(heap, node, top);
 
                if (first_header(top) != NULL) {
                        /*
@@ -900,7 +1081,7 @@ clean_zone_node(qpznode_t *node, uint32_t least_serial) {
                         * less than least_serial too, but we cannot delete it
                         * because it is the most recent version.
                         */
-                       still_dirty = clean_multiple_versions(top,
+                       still_dirty = clean_multiple_versions(heap, node, top,
                                                              least_serial);
                }
        }
@@ -955,37 +1136,24 @@ qpznode_erefs_decrement(qpznode_t *node DNS__DB_FLARG) {
  * (and possibly the node use counter), cleans up and deletes the node
  * if necessary, then decrements the internal reference counter as well.
  */
-static void
-qpznode_release(qpznode_t *node, uint32_t least_serial,
-               isc_rwlocktype_t *nlocktypep DNS__DB_FLARG) {
-       REQUIRE(*nlocktypep != isc_rwlocktype_none);
+static bool
+qpznode_release(qpznode_t *node DNS__DB_FLARG) {
+       bool has_erefs = false;
 
        if (!qpznode_erefs_decrement(node DNS__DB_FLARG_PASS)) {
+               has_erefs = true;
                goto unref;
        }
 
        /* Handle easy and typical case first. */
        if (!node->dirty && !ISC_SLIST_EMPTY(node->next_type)) {
+               has_erefs = true;
                goto unref;
        }
 
-       if (node->dirty && least_serial > 0) {
-               /*
-                * Only do node cleanup when called from closeversion.
-                * Closeversion, unlike other call sites, will provide the
-                * least_serial, and will hold a write lock instead of a read
-                * lock.
-                *
-                * This way we avoid having to protect the db by increasing
-                * the db reference count, avoiding contention in single
-                * zone workloads.
-                */
-               REQUIRE(*nlocktypep == isc_rwlocktype_write);
-               clean_zone_node(node, least_serial);
-       }
-
 unref:
        qpznode_unref(node);
+       return has_erefs;
 }
 
 static void
@@ -995,8 +1163,6 @@ bindrdataset(qpzonedb_t *qpdb, qpznode_t *node, dns_vecheader_t *header,
                return;
        }
 
-       qpznode_acquire(node DNS__DB_FLARG_PASS);
-
        INSIST(rdataset->methods == NULL); /* We must be disassociated. */
 
        rdataset->methods = &dns_rdatavec_rdatasetmethods;
@@ -1013,6 +1179,7 @@ bindrdataset(qpzonedb_t *qpdb, qpznode_t *node, dns_vecheader_t *header,
        rdataset->vec.db = (dns_db_t *)qpdb;
        rdataset->vec.node = (dns_dbnode_t *)node;
        rdataset->vec.header = header;
+       dns_vecheader_ref(header);
        rdataset->vec.iter.iter_pos = NULL;
        rdataset->vec.iter.iter_count = 0;
 
@@ -1025,7 +1192,7 @@ bindrdataset(qpzonedb_t *qpdb, qpznode_t *node, dns_vecheader_t *header,
         */
        if (RESIGN(header)) {
                rdataset->attributes.resign = true;
-               rdataset->resign = (header->resign << 1) | header->resign_lsb;
+               rdataset->resign = header->resign;
        } else {
                rdataset->resign = 0;
        }
@@ -1230,39 +1397,6 @@ newversion(dns_db_t *db, dns_dbversion_t **versionp) {
        return ISC_R_SUCCESS;
 }
 
-static void
-resigninsert(dns_vecheader_t *newheader) {
-       REQUIRE(newheader->heap_index == 0);
-
-       LOCK(get_heap_lock(newheader));
-       isc_heap_insert(HEADERNODE(newheader)->heap->heap, newheader);
-       UNLOCK(get_heap_lock(newheader));
-}
-
-static void
-resigndelete(qpzonedb_t *qpdb ISC_ATTR_UNUSED, qpz_version_t *version,
-            dns_vecheader_t *header DNS__DB_FLARG) {
-       if (header == NULL || header->heap_index == 0) {
-               return;
-       }
-
-       LOCK(get_heap_lock(header));
-       isc_heap_delete(HEADERNODE(header)->heap->heap, header->heap_index);
-       UNLOCK(get_heap_lock(header));
-
-       header->heap_index = 0;
-       qpznode_acquire(HEADERNODE(header) DNS__DB_FLARG_PASS);
-
-       qpz_resigned_t *resigned = isc_mem_get(((dns_db_t *)qpdb)->mctx,
-                                              sizeof(*resigned));
-       *resigned = (qpz_resigned_t){
-               .header = header,
-               .link = ISC_LINK_INITIALIZER,
-       };
-
-       ISC_LIST_APPEND(version->resigned_list, resigned, link);
-}
-
 static void
 make_least_version(qpzonedb_t *qpdb, qpz_version_t *version,
                   qpz_changedlist_t *cleanup_list) {
@@ -1481,18 +1615,18 @@ closeversion(dns_db_t *db, dns_dbversion_t **versionp,
                isc_rwlock_t *nlock = NULL;
                isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
                dns_vecheader_t *header = resigned->header;
+               qpznode_t *resigned_node = resigned->node;
 
                ISC_LIST_UNLINK(resigned_list, resigned, link);
 
-               isc_mem_put(db->mctx, resigned, sizeof(*resigned));
-
-               nlock = qpzone_get_lock(HEADERNODE(header));
+               nlock = qpzone_get_lock(resigned_node);
                NODE_WRLOCK(nlock, &nlocktype);
                if (rollback && !IGNORE(header)) {
-                       resigninsert(header);
+                       LOCK(&qpdb->heap->lock);
+                       resign_register(qpdb->heap, resigned_node, header);
+                       UNLOCK(&qpdb->heap->lock);
                }
-               qpznode_release(HEADERNODE(header), least_serial,
-                               &nlocktype DNS__DB_FLARG_PASS);
+               qpz_resigned_destroy(db->mctx, &resigned);
                NODE_UNLOCK(nlock, &nlocktype);
        }
 
@@ -1512,11 +1646,17 @@ closeversion(dns_db_t *db, dns_dbversion_t **versionp,
                if (rollback) {
                        rollback_node(node, serial);
                }
-               qpznode_release(node, least_serial,
-                               &nlocktype DNS__DB_FILELINE);
+               bool has_erefs = qpznode_release(node DNS__DB_FILELINE);
+               if (!has_erefs) {
+                       clean_zone_node(qpdb->heap, node, least_serial);
+               }
 
                NODE_UNLOCK(nlock, &nlocktype);
 
+               /*
+                * The node reference is released separately above, so
+                * we just free the changed structure here.
+                */
                isc_mem_put(qpdb->common.mctx, changed, sizeof(*changed));
        }
 
@@ -1713,19 +1853,15 @@ cname_and_other(qpznode_t *node, uint32_t serial) {
 }
 
 static qpz_changed_t *
-add_changed(qpzonedb_t *qpdb, dns_vecheader_t *header,
+add_changed(qpzonedb_t *qpdb, qpznode_t *node,
            qpz_version_t *version DNS__DB_FLARG) {
-       qpz_changed_t *changed = NULL;
-       qpznode_t *node = HEADERNODE(header);
-
-       changed = isc_mem_get(qpdb->common.mctx, sizeof(*changed));
+       qpz_changed_t *changed = qpz_changed_new(qpdb->common.mctx,
+                                                node DNS__DB_FLARG_PASS);
 
        RWLOCK(&qpdb->lock, isc_rwlocktype_write);
        REQUIRE(version->writer);
 
-       *changed = (qpz_changed_t){ .node = node };
        ISC_LIST_INITANDAPPEND(version->changed_list, changed, link);
-       qpznode_acquire(node DNS__DB_FLARG_PASS);
        RWUNLOCK(&qpdb->lock, isc_rwlocktype_write);
 
        return changed;
@@ -1779,8 +1915,7 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
                 * being made to this node, because it's harmless and
                 * simplifies the code.
                 */
-               changed = add_changed(qpdb, newheader,
-                                     version DNS__DB_FLARG_PASS);
+               changed = add_changed(qpdb, node, version DNS__DB_FLARG_PASS);
        }
 
        ntypes = 0;
@@ -1851,22 +1986,19 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
                                 * alone.  It will get cleaned up when
                                 * clean_zone_node() runs.
                                 */
-                               deletedata(newheader->node, newheader);
-                               dns_vecheader_destroy(&newheader);
+                               dns_vecheader_unref(newheader);
                                newheader = merged;
-                               dns_vecheader_reset(newheader,
-                                                   (dns_dbnode_t *)node);
                                /*
                                 * dns_rdatavec_subtract takes the header from
                                 * the first argument, so it preserves the case
                                 */
                                if (loading && RESIGN(newheader) &&
                                    RESIGN(header) &&
-                                   resign_sooner(header, newheader))
+                                   resign_sooner_values(header->resign,
+                                                        newheader->resign,
+                                                        newheader->typepair))
                                {
                                        newheader->resign = header->resign;
-                                       newheader->resign_lsb =
-                                               header->resign_lsb;
                                }
                        } else {
                                if (result == DNS_R_TOOMANYRECORDS) {
@@ -1876,8 +2008,7 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
                                                        header->typepair),
                                                "updating", qpdb->maxrrperset);
                                }
-                               deletedata(newheader->node, newheader);
-                               dns_vecheader_destroy(&newheader);
+                               dns_vecheader_unref(newheader);
                                return result;
                        }
                }
@@ -1887,7 +2018,9 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
 
                if (loading) {
                        if (RESIGN(newheader)) {
-                               resigninsert(newheader);
+                               LOCK(&qpdb->heap->lock);
+                               resign_register(qpdb->heap, node, newheader);
+                               UNLOCK(&qpdb->heap->lock);
                                /* resigndelete not needed here */
                        }
 
@@ -1903,13 +2036,18 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
                        maybe_update_recordsandsize(false, version, header,
                                                    nodename->length);
 
-                       deletedata(header->node, header);
-                       dns_vecheader_destroy(&header);
+                       LOCK(&qpdb->heap->lock);
+                       resign_unregister(qpdb->heap, node, header);
+                       UNLOCK(&qpdb->heap->lock);
+                       dns_vecheader_unref(header);
                } else {
                        if (RESIGN(newheader)) {
-                               resigninsert(newheader);
-                               resigndelete(qpdb, version,
-                                            header DNS__DB_FLARG_PASS);
+                               LOCK(&qpdb->heap->lock);
+                               resign_register(qpdb->heap, node, newheader);
+                               resign_unregister(qpdb->heap, node, header);
+                               UNLOCK(&qpdb->heap->lock);
+                               resign_rollback(qpdb, node, version,
+                                               header DNS__DB_FLARG_PASS);
                        }
 
                        ISC_SLIST_PREPEND(foundtop->headers, newheader,
@@ -1930,14 +2068,17 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
                 * If we're trying to delete the type, don't bother.
                 */
                if (!EXISTS(newheader)) {
-                       deletedata(newheader->node, newheader);
-                       dns_vecheader_destroy(&newheader);
+                       dns_vecheader_unref(newheader);
                        return DNS_R_UNCHANGED;
                }
 
                if (RESIGN(newheader)) {
-                       resigninsert(newheader);
-                       resigndelete(qpdb, version, header DNS__DB_FLARG_PASS);
+                       LOCK(&qpdb->heap->lock);
+                       resign_register(qpdb->heap, node, newheader);
+                       resign_unregister(qpdb->heap, node, header);
+                       UNLOCK(&qpdb->heap->lock);
+                       resign_rollback(qpdb, node, version,
+                                       header DNS__DB_FLARG_PASS);
                }
 
                if (foundtop != NULL) {
@@ -1966,8 +2107,10 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
                        if (qpdb->maxtypepername > 0 &&
                            ntypes >= qpdb->maxtypepername)
                        {
-                               deletedata(newheader->node, newheader);
-                               dns_vecheader_destroy(&newheader);
+                               LOCK(&qpdb->heap->lock);
+                               resign_unregister(qpdb->heap, node, newheader);
+                               UNLOCK(&qpdb->heap->lock);
+                               dns_vecheader_unref(newheader);
                                return DNS_R_TOOMANYRECORDS;
                        }
 
@@ -2114,7 +2257,6 @@ loading_addrdataset(void *arg, const dns_name_t *name, dns_rdataset_t *rdataset,
        }
 
        dns_vecheader_t *newheader = (dns_vecheader_t *)region.base;
-       dns_vecheader_reset(newheader, (dns_dbnode_t *)node);
        newheader->ttl = rdataset->ttl;
        newheader->serial = 1;
        atomic_store(&newheader->trust, rdataset->trust);
@@ -2123,10 +2265,7 @@ loading_addrdataset(void *arg, const dns_name_t *name, dns_rdataset_t *rdataset,
 
        if (rdataset->attributes.resign) {
                DNS_VECHEADER_SETATTR(newheader, DNS_VECHEADERATTR_RESIGN);
-               newheader->resign =
-                       (isc_stdtime_t)(dns_time64_from32(rdataset->resign) >>
-                                       1);
-               newheader->resign_lsb = rdataset->resign & 0x1;
+               newheader->resign = dns_time64_from32(rdataset->resign);
        }
 
        nlock = qpzone_get_lock(node);
@@ -2308,10 +2447,11 @@ getsize(dns_db_t *db, dns_dbversion_t *dbversion, uint64_t *records,
 }
 
 static isc_result_t
-setsigningtime(dns_db_t *db, dns_dbnode_t *node, dns_rdataset_t *rdataset,
+setsigningtime(dns_db_t *db, dns_dbnode_t *dbnode, dns_rdataset_t *rdataset,
               isc_stdtime_t resign) {
        qpzonedb_t *qpdb = (qpzonedb_t *)db;
-       dns_vecheader_t *header = NULL, oldheader;
+       qpznode_t *node = (qpznode_t *)dbnode;
+       dns_vecheader_t *header = NULL;
        isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
        isc_rwlock_t *nlock = NULL;
 
@@ -2321,40 +2461,51 @@ setsigningtime(dns_db_t *db, dns_dbnode_t *node, dns_rdataset_t *rdataset,
 
        header = dns_vecheader_getheader(rdataset);
 
-       nlock = qpzone_get_lock((qpznode_t *)node);
+       nlock = qpzone_get_lock(node);
        NODE_WRLOCK(nlock, &nlocktype);
 
-       oldheader = *header;
-
        /*
-        * Only break the heap invariant (by adjusting resign and resign_lsb)
-        * if we are going to be restoring it by calling isc_heap_increased
-        * or isc_heap_decreased.
+        * Check if element is in the heap using hashmap lookup.
         */
-       if (resign != 0) {
-               header->resign = (isc_stdtime_t)(dns_time64_from32(resign) >>
-                                                1);
-               header->resign_lsb = resign & 0x1;
-       }
-       if (header->heap_index != 0) {
+       qpz_resign_t *found_elem = NULL;
+       isc_result_t find_result;
+
+       LOCK(&qpdb->heap->lock);
+       find_result = resign_lookup(qpdb->heap, header, node, &found_elem);
+
+       if (find_result == ISC_R_SUCCESS) {
+               /* Element is in heap */
                INSIST(RESIGN(header));
-               LOCK(get_heap_lock(header));
                if (resign == 0) {
-                       isc_heap_delete(((qpznode_t *)node)->heap->heap,
-                                       header->heap_index);
-                       header->heap_index = 0;
-               } else if (resign_sooner(header, &oldheader)) {
-                       isc_heap_increased(((qpznode_t *)node)->heap->heap,
-                                          header->heap_index);
-               } else if (resign_sooner(&oldheader, header)) {
-                       isc_heap_decreased(((qpznode_t *)node)->heap->heap,
-                                          header->heap_index);
+                       resign_unregister(qpdb->heap, node, header);
+               } else {
+                       int64_t old_resign = header->resign;
+                       int64_t new_resign = dns_time64_from32(resign);
+
+                       header->resign = new_resign;
+
+                       if (resign_sooner_values(new_resign, old_resign,
+                                                header->typepair))
+                       {
+                               isc_heap_increased(qpdb->heap->heap,
+                                                  found_elem->heap_index);
+                       } else if (resign_sooner_values(old_resign, new_resign,
+                                                       header->typepair))
+                       {
+                               isc_heap_decreased(qpdb->heap->heap,
+                                                  found_elem->heap_index);
+                       }
+                       /* No heap adjustment needed if neither direction
+                        * indicates sooner */
                }
-               UNLOCK(get_heap_lock(header));
        } else if (resign != 0) {
+               /* Element not in heap, add it */
+               header->resign = dns_time64_from32(resign);
                DNS_VECHEADER_SETATTR(header, DNS_VECHEADERATTR_RESIGN);
-               resigninsert(header);
+
+               resign_register(qpdb->heap, node, header);
        }
+       UNLOCK(&qpdb->heap->lock);
        NODE_UNLOCK(nlock, &nlocktype);
        return ISC_R_SUCCESS;
 }
@@ -2363,6 +2514,7 @@ static isc_result_t
 getsigningtime(dns_db_t *db, isc_stdtime_t *resign, dns_name_t *foundname,
               dns_typepair_t *typepair) {
        qpzonedb_t *qpdb = (qpzonedb_t *)db;
+       qpz_resign_t *elem = NULL;
        dns_vecheader_t *header = NULL;
        isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
        isc_rwlock_t *nlock = NULL;
@@ -2374,33 +2526,33 @@ getsigningtime(dns_db_t *db, isc_stdtime_t *resign, dns_name_t *foundname,
        REQUIRE(typepair != NULL);
 
        LOCK(&qpdb->heap->lock);
-       header = isc_heap_element(qpdb->heap->heap, 1);
-       if (header == NULL) {
+       elem = isc_heap_element(qpdb->heap->heap, 1);
+       if (elem == NULL) {
                UNLOCK(&qpdb->heap->lock);
                return ISC_R_NOTFOUND;
        }
-       nlock = qpzone_get_lock(HEADERNODE(header));
+       header = elem->header;
+       nlock = qpzone_get_lock(elem->node);
        UNLOCK(&qpdb->heap->lock);
 
 again:
        NODE_RDLOCK(nlock, &nlocktype);
 
        LOCK(&qpdb->heap->lock);
-       header = isc_heap_element(qpdb->heap->heap, 1);
+       elem = isc_heap_element(qpdb->heap->heap, 1);
 
-       if (header != NULL && qpzone_get_lock(HEADERNODE(header)) != nlock) {
+       if (elem != NULL && qpzone_get_lock(elem->node) != nlock) {
                UNLOCK(&qpdb->heap->lock);
                NODE_UNLOCK(nlock, &nlocktype);
 
-               nlock = qpzone_get_lock(HEADERNODE(header));
+               nlock = qpzone_get_lock(elem->node);
                goto again;
        }
 
-       if (header != NULL) {
-               *resign = RESIGN(header)
-                                 ? (header->resign << 1) | header->resign_lsb
-                                 : 0;
-               dns_name_copy(&HEADERNODE(header)->name, foundname);
+       if (elem != NULL) {
+               header = elem->header;
+               *resign = RESIGN(header) ? (uint32_t)header->resign : 0;
+               dns_name_copy(&elem->node->name, foundname);
                *typepair = header->typepair;
                result = ISC_R_SUCCESS;
        }
@@ -3812,7 +3964,7 @@ tree_exit:
                nlock = qpzone_get_lock(node);
 
                NODE_RDLOCK(nlock, &nlocktype);
-               qpznode_release(node, 0, &nlocktype DNS__DB_FLARG_PASS);
+               (void)qpznode_release(node DNS__DB_FLARG_PASS);
                NODE_UNLOCK(nlock, &nlocktype);
        }
 
@@ -3891,7 +4043,7 @@ qpzone_detachnode(dns_dbnode_t **nodep DNS__DB_FLARG) {
 
        rcu_read_lock();
        NODE_RDLOCK(nlock, &nlocktype);
-       qpznode_release(node, 0, &nlocktype DNS__DB_FLARG_PASS);
+       (void)qpznode_release(node DNS__DB_FLARG_PASS);
        NODE_UNLOCK(nlock, &nlocktype);
        rcu_read_unlock();
 }
@@ -3923,19 +4075,6 @@ getoriginnode(dns_db_t *db, dns_dbnode_t **nodep DNS__DB_FLARG) {
        return ISC_R_SUCCESS;
 }
 
-static void
-deletedata(dns_dbnode_t *node ISC_ATTR_UNUSED, void *data) {
-       dns_vecheader_t *header = data;
-
-       if (header->heap_index != 0) {
-               LOCK(get_heap_lock(header));
-               isc_heap_delete(HEADERNODE(header)->heap->heap,
-                               header->heap_index);
-               UNLOCK(get_heap_lock(header));
-       }
-       header->heap_index = 0;
-}
-
 /*
  * Rdataset Iterator Methods
  */
@@ -4076,7 +4215,7 @@ dereference_iter_node(qpdb_dbiterator_t *iter DNS__DB_FLARG) {
        nlock = qpzone_get_lock(node);
 
        NODE_RDLOCK(nlock, &nlocktype);
-       qpznode_release(node, 0, &nlocktype DNS__DB_FLARG_PASS);
+       (void)qpznode_release(node DNS__DB_FLARG_PASS);
        NODE_UNLOCK(nlock, &nlocktype);
 }
 
@@ -4488,7 +4627,7 @@ dbiterator_prev(dns_dbiterator_t *iterator DNS__DB_FLARG) {
         */
        nlock = qpzone_get_lock(node);
        NODE_RDLOCK(nlock, &nlocktype);
-       qpznode_release(node, 0, &nlocktype DNS__DB_FLARG_PASS);
+       (void)qpznode_release(node DNS__DB_FLARG_PASS);
        NODE_UNLOCK(nlock, &nlocktype);
 
        if (result == ISC_R_SUCCESS) {
@@ -4582,7 +4721,7 @@ dbiterator_next(dns_dbiterator_t *iterator DNS__DB_FLARG) {
         */
        nlock = qpzone_get_lock(node);
        NODE_RDLOCK(nlock, &nlocktype);
-       qpznode_release(node, 0, &nlocktype DNS__DB_FLARG_PASS);
+       (void)qpznode_release(node DNS__DB_FLARG_PASS);
        NODE_UNLOCK(nlock, &nlocktype);
 
        if (result == ISC_R_SUCCESS) {
@@ -4741,7 +4880,6 @@ qpzone_addrdataset_inner(qpzonedb_t *qpdb, qpznode_t *node,
        dns_rdataset_getownercase(rdataset, name);
 
        dns_vecheader_t *newheader = (dns_vecheader_t *)region.base;
-       dns_vecheader_reset(newheader, (dns_dbnode_t *)node);
 
        dns_vecheader_setownercase(newheader, name);
 
@@ -4753,10 +4891,7 @@ qpzone_addrdataset_inner(qpzonedb_t *qpdb, qpznode_t *node,
        newheader->serial = version->serial;
        if (rdataset->attributes.resign) {
                DNS_VECHEADER_SETATTR(newheader, DNS_VECHEADERATTR_RESIGN);
-               newheader->resign =
-                       (isc_stdtime_t)(dns_time64_from32(rdataset->resign) >>
-                                       1);
-               newheader->resign_lsb = rdataset->resign & 0x1;
+               newheader->resign = dns_time64_from32(rdataset->resign);
        }
 
        /*
@@ -4877,22 +5012,18 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
        }
 
        newheader = (dns_vecheader_t *)region.base;
-       dns_vecheader_reset(newheader, (dns_dbnode_t *)node);
        newheader->ttl = rdataset->ttl;
        atomic_init(&newheader->attributes, 0);
        newheader->serial = version->serial;
        if (rdataset->attributes.resign) {
                DNS_VECHEADER_SETATTR(newheader, DNS_VECHEADERATTR_RESIGN);
-               newheader->resign =
-                       (isc_stdtime_t)(dns_time64_from32(rdataset->resign) >>
-                                       1);
-               newheader->resign_lsb = rdataset->resign & 0x1;
+               newheader->resign = dns_time64_from32(rdataset->resign);
        }
 
        nlock = qpzone_get_lock(node);
        NODE_WRLOCK(nlock, &nlocktype);
 
-       changed = add_changed(qpdb, newheader, version DNS__DB_FLARG_PASS);
+       changed = add_changed(qpdb, node, version DNS__DB_FLARG_PASS);
        ISC_SLIST_FOREACH(top, node->next_type, next_type) {
                if (top->typepair == newheader->typepair) {
                        foundtop = top;
@@ -4931,10 +5062,11 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
                                &subresult);
                }
                if (result == ISC_R_SUCCESS) {
-                       deletedata(newheader->node, newheader);
-                       dns_vecheader_destroy(&newheader);
+                       LOCK(&qpdb->heap->lock);
+                       resign_unregister(qpdb->heap, node, newheader);
+                       UNLOCK(&qpdb->heap->lock);
+                       dns_vecheader_unref(newheader);
                        newheader = subresult;
-                       dns_vecheader_reset(newheader, (dns_dbnode_t *)node);
                        /*
                         * dns_rdatavec_subtract takes the header from the first
                         * argument, so it preserves the case
@@ -4943,8 +5075,9 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
                                DNS_VECHEADER_SETATTR(newheader,
                                                      DNS_VECHEADERATTR_RESIGN);
                                newheader->resign = header->resign;
-                               newheader->resign_lsb = header->resign_lsb;
-                               resigninsert(newheader);
+                               LOCK(&qpdb->heap->lock);
+                               resign_register(qpdb->heap, node, newheader);
+                               UNLOCK(&qpdb->heap->lock);
                        }
                        /*
                         * We have to set the serial since the rdatavec
@@ -4964,18 +5097,21 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
                         * This subtraction would remove all of the rdata;
                         * add a nonexistent header instead.
                         */
-                       deletedata(newheader->node, newheader);
-                       dns_vecheader_destroy(&newheader);
-                       newheader = dns_vecheader_new(db->mctx,
-                                                     (dns_dbnode_t *)node);
+                       LOCK(&qpdb->heap->lock);
+                       resign_unregister(qpdb->heap, node, newheader);
+                       UNLOCK(&qpdb->heap->lock);
+                       dns_vecheader_unref(newheader);
+                       newheader = dns_vecheader_new(db->mctx);
                        newheader->ttl = 0;
                        newheader->typepair = foundtop->typepair;
                        atomic_init(&newheader->attributes,
                                    DNS_VECHEADERATTR_NONEXISTENT);
                        newheader->serial = version->serial;
                } else {
-                       deletedata(newheader->node, newheader);
-                       dns_vecheader_destroy(&newheader);
+                       LOCK(&qpdb->heap->lock);
+                       resign_unregister(qpdb->heap, node, newheader);
+                       UNLOCK(&qpdb->heap->lock);
+                       dns_vecheader_unref(newheader);
                        goto unlock;
                }
 
@@ -4989,14 +5125,19 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
 
                node->dirty = true;
                changed->dirty = true;
-               resigndelete(qpdb, version, header DNS__DB_FLARG_PASS);
+               LOCK(&qpdb->heap->lock);
+               resign_unregister(qpdb->heap, node, header);
+               UNLOCK(&qpdb->heap->lock);
+               resign_rollback(qpdb, node, version, header DNS__DB_FLARG_PASS);
        } else {
                /*
                 * The rdataset doesn't exist, so we don't need to do anything
                 * to satisfy the deletion request.
                 */
-               deletedata(newheader->node, newheader);
-               dns_vecheader_destroy(&newheader);
+               LOCK(&qpdb->heap->lock);
+               resign_unregister(qpdb->heap, node, newheader);
+               UNLOCK(&qpdb->heap->lock);
+               dns_vecheader_unref(newheader);
                if ((options & DNS_DBSUB_EXACT) != 0) {
                        result = DNS_R_NOTEXACT;
                } else {
@@ -5045,7 +5186,7 @@ qpzone_deleterdataset(dns_db_t *db, dns_dbnode_t *dbnode,
                return ISC_R_NOTIMPLEMENTED;
        }
 
-       newheader = dns_vecheader_new(db->mctx, (dns_dbnode_t *)node);
+       newheader = dns_vecheader_new(db->mctx);
        newheader->typepair = DNS_TYPEPAIR_VALUE(type, covers);
        newheader->ttl = 0;
        atomic_init(&newheader->attributes, DNS_VECHEADERATTR_NONEXISTENT);
@@ -5584,21 +5725,18 @@ static dns_dbmethods_t qpdb_zonemethods = {
 static dns_dbnode_methods_t qpznode_methods = (dns_dbnode_methods_t){
        .attachnode = qpzone_attachnode,
        .detachnode = qpzone_detachnode,
-       .deletedata = deletedata,
 };
 
 static void
 destroy_qpznode(qpznode_t *node) {
        ISC_SLIST_FOREACH(top, node->next_type, next_type) {
                ISC_SLIST_FOREACH(header, top->headers, next_header) {
-                       deletedata(header->node, header);
-                       dns_vecheader_destroy(&header);
+                       dns_vecheader_unref(header);
                }
 
                dns_vectop_destroy(node->mctx, &top);
        }
 
-       qpz_heap_unref(node->heap);
        dns_name_free(&node->name, node->mctx);
        isc_mem_putanddetach(&node->mctx, node, sizeof(qpznode_t));
 }
index 7ae9331c036810a9e30dbd5ce90f35b25fa56aad..85a78fd2f457cffa379afd2ab62c69c5cca6b316 100644 (file)
@@ -21,6 +21,7 @@
 #include <isc/ascii.h>
 #include <isc/atomic.h>
 #include <isc/mem.h>
+#include <isc/refcount.h>
 #include <isc/region.h>
 #include <isc/result.h>
 #include <isc/string.h>
@@ -133,6 +134,8 @@ newvec(dns_rdataset_t *rdataset, isc_mem_t *mctx, isc_region_t *region,
                .next_header = ISC_SLINK_INITIALIZER,
                .trust = rdataset->trust,
                .ttl = rdataset->ttl,
+               .references = ISC_REFCOUNT_INITIALIZER(1),
+               .mctx = isc_mem_ref(mctx),
        };
 
        region->base = (unsigned char *)header;
@@ -356,11 +359,16 @@ dns_rdatavec_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
                                                      rdataset->covers);
                }
 
+               /*
+                * Reset the vecheader content, but keep the refcount and mctx.
+                */
                *new = (dns_vecheader_t){
                        .next_header = ISC_SLINK_INITIALIZER,
                        .typepair = typepair,
                        .trust = rdataset->trust,
                        .ttl = rdataset->ttl,
+                       .references = atomic_load_acquire(&new->references),
+                       .mctx = new->mctx,
                };
        }
 
@@ -583,10 +591,17 @@ dns_rdatavec_merge(dns_vecheader_t *oheader, dns_vecheader_t *nheader,
        uint16_t case_attrs = DNS_VECHEADER_GETATTR(
                oheader,
                DNS_VECHEADERATTR_CASESET | DNS_VECHEADERATTR_CASEFULLYLOWER);
-       DNS_VECHEADER_CLRATTR(as_header,
-                             DNS_VECHEADERATTR_CASESET |
-                                     DNS_VECHEADERATTR_CASEFULLYLOWER);
+       atomic_init(&as_header->attributes, 0);
        DNS_VECHEADER_SETATTR(as_header, case_attrs);
+       if (RESIGN(nheader)) {
+               DNS_VECHEADER_SETATTR(as_header, DNS_VECHEADERATTR_RESIGN);
+       }
+
+       /* Initialize refcount for the new header */
+       isc_refcount_init(&as_header->references, 1);
+
+       /* We need to re-attach to the memory context for refcount reasons. */
+       as_header->mctx = isc_mem_ref(mctx);
 
        tcurrent = tstart + header_size(nheader);
 
@@ -740,6 +755,18 @@ dns_rdatavec_subtract(dns_vecheader_t *oheader, dns_vecheader_t *sheader,
         */
        tstart = isc_mem_get(mctx, tlength);
        memmove(tstart, oheader, header_size(oheader));
+
+       /* Initialize refcount for the new header */
+       dns_vecheader_t *as_header = (dns_vecheader_t *)tstart;
+       isc_refcount_init(&as_header->references, 1);
+       atomic_init(&as_header->attributes, 0);
+       if (RESIGN(oheader)) {
+               DNS_VECHEADER_SETATTR(as_header, DNS_VECHEADERATTR_RESIGN);
+       }
+
+       /* We need to re-attach to the memory context for refcount reasons. */
+       as_header->mctx = isc_mem_ref(mctx);
+
        tcurrent = tstart + header_size(oheader);
 
        /*
@@ -790,47 +817,18 @@ dns_vecheader_setownercase(dns_vecheader_t *header, const dns_name_t *name) {
        DNS_VECHEADER_SETATTR(header, DNS_VECHEADERATTR_CASESET);
 }
 
-void
-dns_vecheader_reset(dns_vecheader_t *h, dns_dbnode_t *node) {
-       h->heap_index = 0;
-       h->node = node;
-
-       atomic_init(&h->attributes, 0);
-
-       STATIC_ASSERT(sizeof(h->attributes) == 2,
-                     "The .attributes field of dns_vecheader_t needs to be "
-                     "16-bit int type exactly.");
-}
-
 dns_vecheader_t *
-dns_vecheader_new(isc_mem_t *mctx, dns_dbnode_t *node) {
+dns_vecheader_new(isc_mem_t *mctx) {
        dns_vecheader_t *h = NULL;
 
        h = isc_mem_get(mctx, sizeof(*h));
        *h = (dns_vecheader_t){
-               .node = node,
+               .references = ISC_REFCOUNT_INITIALIZER(1),
+               .mctx = isc_mem_ref(mctx),
        };
        return h;
 }
 
-void
-dns_vecheader_destroy(dns_vecheader_t **headerp) {
-       unsigned int size;
-       dns_vecheader_t *header = *headerp;
-
-       *headerp = NULL;
-
-       isc_mem_t *mctx = header->node->mctx;
-
-       if (EXISTS(header)) {
-               size = dns_rdatavec_size(header);
-       } else {
-               size = sizeof(*header);
-       }
-
-       isc_mem_put(mctx, header, size);
-}
-
 /* Iterators for already bound rdatavec */
 
 isc_result_t
@@ -912,9 +910,7 @@ vecheader_current(rdatavec_iter_t *iter, dns_rdata_t *rdata) {
 
 static void
 rdataset_disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG) {
-       dns_dbnode_t *node = rdataset->vec.node;
-
-       dns__db_detachnode(&node DNS__DB_FLARG_PASS);
+       dns_vecheader_unref(rdataset->vec.header);
 }
 
 static isc_result_t
@@ -936,16 +932,14 @@ rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
 static void
 rdataset_clone(const dns_rdataset_t *source,
               dns_rdataset_t *target DNS__DB_FLARG) {
-       dns_dbnode_t *node = source->vec.node;
-       dns_dbnode_t *cloned_node = NULL;
-
-       dns__db_attachnode(node, &cloned_node DNS__DB_FLARG_PASS);
        INSIST(!ISC_LINK_LINKED(target, link));
        *target = *source;
        ISC_LINK_INIT(target, link);
 
        target->vec.iter.iter_pos = NULL;
        target->vec.iter.iter_count = 0;
+
+       dns_vecheader_ref(target->vec.header);
 }
 
 static unsigned int
@@ -1013,3 +1007,16 @@ dns_vectop_destroy(isc_mem_t *mctx, dns_vectop_t **topp) {
        *topp = NULL;
        isc_mem_put(mctx, top, sizeof(*top));
 }
+
+static void
+vecheader_destroy(dns_vecheader_t *header) {
+       unsigned int size = EXISTS(header) ? dns_rdatavec_size(header)
+                                          : sizeof(*header);
+
+       isc_mem_putanddetach(&header->mctx, header, size);
+}
+
+/*
+ * Reference counting implementation for dns_vecheader_t
+ */
+ISC_REFCOUNT_IMPL(dns_vecheader, vecheader_destroy);
index 46c3cd7448f717f80082cc1d8f6e46275e95df4e..817e72105f6200ee7b12ca65e8af23f6daf0f0b2 100644 (file)
@@ -187,9 +187,7 @@ ownercase_test_one(const char *str1, const char *str2) {
                .common.mctx = isc_g_mctx,
        };
        qpznode_t node = { .methods = &qpznode_methods, .locknum = 0 };
-       dns_vecheader_t header = {
-               .node = (dns_dbnode_t *)&node,
-       };
+       dns_vecheader_t header = { 0 };
        dns_rdataset_t rdataset = {
                .magic = DNS_RDATASET_MAGIC,
                .vec = { .db = (dns_db_t *)qpdb,
@@ -360,9 +358,7 @@ ISC_RUN_TEST_IMPL(setownercase) {
                .common.mctx = isc_g_mctx,
        };
        qpznode_t node = { .methods = &qpznode_methods, .locknum = 0 };
-       dns_vecheader_t header = {
-               .node = (dns_dbnode_t *)&node,
-       };
+       dns_vecheader_t header = { 0 };
        dns_rdataset_t rdataset = {
                .magic = DNS_RDATASET_MAGIC,
                .vec = { .db = (dns_db_t *)qpdb,
index fd486bd88f345c004298cd465810a1f4e6b29162..f64e7a8d94e6e653d5da79ed1c1b805c1b374b49 100644 (file)
@@ -282,10 +282,274 @@ cleanup:
        }
 }
 
+/* Test case where dns_rdatavec_subtract causes assertion failure */
+ISC_RUN_TEST_IMPL(rdatavec_subtract_assertion_failure) {
+       isc_mem_t *mctx = isc_g_mctx;
+       UNUSED(state);
+       dns_vecheader_t *original_header = NULL, *subtract_header = NULL,
+                       *result_header = NULL;
+       dns_rdataset_t original_rdataset, subtract_rdataset;
+       dns_rdatalist_t *original_rdatalist = NULL, *subtract_rdatalist = NULL;
+       dns_rdata_t *original_rdata1 = NULL, *original_rdata2 = NULL,
+                   *subtract_rdata = NULL;
+       unsigned char *original_data1 = NULL, *original_data2 = NULL,
+                     *subtract_data = NULL;
+       isc_region_t original_region, subtract_region;
+       isc_result_t result;
+
+       /* Allocate temporary structures for original rdatalist with 2 records
+        */
+       original_data1 = isc_mem_get(mctx, 256);
+       original_data2 = isc_mem_get(mctx, 256);
+       original_rdatalist = isc_mem_get(mctx, sizeof(*original_rdatalist));
+       original_rdata1 = isc_mem_get(mctx, sizeof(*original_rdata1));
+       original_rdata2 = isc_mem_get(mctx, sizeof(*original_rdata2));
+
+       /* Allocate temporary structures for subtract rdatalist with 1 record */
+       subtract_data = isc_mem_get(mctx, 256);
+       subtract_rdatalist = isc_mem_get(mctx, sizeof(*subtract_rdatalist));
+       subtract_rdata = isc_mem_get(mctx, sizeof(*subtract_rdata));
+
+       /* Initialize original rdataset and rdatalist with 2 A records */
+       dns_rdataset_init(&original_rdataset);
+       dns_rdatalist_init(original_rdatalist);
+       original_rdatalist->type = dns_rdatatype_a;
+       original_rdatalist->rdclass = dns_rdataclass_in;
+       original_rdatalist->ttl = 300;
+
+       /* Create first rdata: 192.168.1.1 */
+       dns_rdata_init(original_rdata1);
+       CHECK(dns_test_rdatafromstring(original_rdata1, dns_rdataclass_in,
+                                      dns_rdatatype_a, original_data1, 256,
+                                      "192.168.1.1", false));
+       ISC_LIST_APPEND(original_rdatalist->rdata, original_rdata1, link);
+
+       /* Create second rdata: 192.168.1.2 */
+       dns_rdata_init(original_rdata2);
+       CHECK(dns_test_rdatafromstring(original_rdata2, dns_rdataclass_in,
+                                      dns_rdatatype_a, original_data2, 256,
+                                      "192.168.1.2", false));
+       ISC_LIST_APPEND(original_rdatalist->rdata, original_rdata2, link);
+
+       dns_rdatalist_tordataset(original_rdatalist, &original_rdataset);
+
+       /* Initialize subtract rdataset and rdatalist with 1 A record */
+       dns_rdataset_init(&subtract_rdataset);
+       dns_rdatalist_init(subtract_rdatalist);
+       subtract_rdatalist->type = dns_rdatatype_a;
+       subtract_rdatalist->rdclass = dns_rdataclass_in;
+       subtract_rdatalist->ttl = 300;
+
+       /* Create subtract rdata: 192.168.1.1 (same as first record) */
+       dns_rdata_init(subtract_rdata);
+       CHECK(dns_test_rdatafromstring(subtract_rdata, dns_rdataclass_in,
+                                      dns_rdatatype_a, subtract_data, 256,
+                                      "192.168.1.1", false));
+       ISC_LIST_APPEND(subtract_rdatalist->rdata, subtract_rdata, link);
+
+       dns_rdatalist_tordataset(subtract_rdatalist, &subtract_rdataset);
+
+       /* Convert to vecheaders (each starts with refcount = 1) */
+       CHECK(dns_rdatavec_fromrdataset(&original_rdataset, mctx,
+                                       &original_region, 0));
+       original_header = (dns_vecheader_t *)original_region.base;
+
+       CHECK(dns_rdatavec_fromrdataset(&subtract_rdataset, mctx,
+                                       &subtract_region, 0));
+       subtract_header = (dns_vecheader_t *)subtract_region.base;
+
+       /*
+        * This should cause assertion failure because dns_rdatavec_subtract()
+        * copies the original header (including its mctx) with memmove(), then
+        * tries to call isc_mem_attach() on the already-attached mctx field.
+        * Since we're subtracting 1 record from 2, it should create a new
+        * header and hit the problematic code path at rdatavec.c:759
+        */
+       result = dns_rdatavec_subtract(original_header, subtract_header, mctx,
+                                      dns_rdataclass_in, dns_rdatatype_a, 0,
+                                      &result_header);
+
+       /* If we get here without assertion failure, the bug has been fixed */
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_non_null(result_header);
+
+       /* Result should contain only the second record (192.168.1.2) */
+       unsigned int result_count = dns_rdatavec_count(result_header);
+       assert_int_equal(result_count, 1);
+
+cleanup:
+       /* Cleanup rdatasets */
+       if (DNS_RDATASET_VALID(&original_rdataset)) {
+               dns_rdataset_disassociate(&original_rdataset);
+       }
+       if (DNS_RDATASET_VALID(&subtract_rdataset)) {
+               dns_rdataset_disassociate(&subtract_rdataset);
+       }
+
+       /* Cleanup vecheaders */
+       if (original_header != NULL) {
+               dns_vecheader_unref(original_header);
+       }
+       if (subtract_header != NULL) {
+               dns_vecheader_unref(subtract_header);
+       }
+       if (result_header != NULL) {
+               dns_vecheader_unref(result_header);
+       }
+
+       /* Cleanup temporary structures for original */
+       if (original_rdata1 != NULL) {
+               isc_mem_put(mctx, original_rdata1, sizeof(*original_rdata1));
+       }
+       if (original_rdata2 != NULL) {
+               isc_mem_put(mctx, original_rdata2, sizeof(*original_rdata2));
+       }
+       if (original_rdatalist != NULL) {
+               isc_mem_put(mctx, original_rdatalist,
+                           sizeof(*original_rdatalist));
+       }
+       if (original_data1 != NULL) {
+               isc_mem_put(mctx, original_data1, 256);
+       }
+       if (original_data2 != NULL) {
+               isc_mem_put(mctx, original_data2, 256);
+       }
+
+       /* Cleanup temporary structures for subtract */
+       if (subtract_rdata != NULL) {
+               isc_mem_put(mctx, subtract_rdata, sizeof(*subtract_rdata));
+       }
+       if (subtract_rdatalist != NULL) {
+               isc_mem_put(mctx, subtract_rdatalist,
+                           sizeof(*subtract_rdatalist));
+       }
+       if (subtract_data != NULL) {
+               isc_mem_put(mctx, subtract_data, 256);
+       }
+}
+
+/* Test refcount functionality with merge and cleanup */
+ISC_RUN_TEST_IMPL(rdatavec_refcount_merge) {
+       isc_mem_t *mctx = isc_g_mctx;
+       UNUSED(state);
+       dns_vecheader_t *header1 = NULL, *header2 = NULL, *merged_header = NULL;
+       dns_rdataset_t rdataset1, rdataset2;
+       dns_rdatalist_t *rdatalist1 = NULL, *rdatalist2 = NULL;
+       dns_rdata_t *rdata1 = NULL, *rdata2 = NULL;
+       unsigned char *data1 = NULL, *data2 = NULL;
+       isc_region_t region1, region2;
+       isc_result_t result;
+
+       /* Allocate temporary structures for first rdatalist */
+       data1 = isc_mem_get(mctx, 256);
+       rdatalist1 = isc_mem_get(mctx, sizeof(*rdatalist1));
+       rdata1 = isc_mem_get(mctx, sizeof(*rdata1));
+
+       /* Allocate temporary structures for second rdatalist */
+       data2 = isc_mem_get(mctx, 256);
+       rdatalist2 = isc_mem_get(mctx, sizeof(*rdatalist2));
+       rdata2 = isc_mem_get(mctx, sizeof(*rdata2));
+
+       /* Initialize first rdataset and rdatalist */
+       dns_rdataset_init(&rdataset1);
+       dns_rdatalist_init(rdatalist1);
+       rdatalist1->type = dns_rdatatype_a;
+       rdatalist1->rdclass = dns_rdataclass_in;
+       rdatalist1->ttl = 300;
+
+       /* Create first rdata */
+       dns_rdata_init(rdata1);
+       CHECK(dns_test_rdatafromstring(rdata1, dns_rdataclass_in,
+                                      dns_rdatatype_a, data1, 256,
+                                      "192.168.1.1", false));
+
+       ISC_LIST_APPEND(rdatalist1->rdata, rdata1, link);
+       dns_rdatalist_tordataset(rdatalist1, &rdataset1);
+
+       /* Initialize second rdataset and rdatalist */
+       dns_rdataset_init(&rdataset2);
+       dns_rdatalist_init(rdatalist2);
+       rdatalist2->type = dns_rdatatype_a;
+       rdatalist2->rdclass = dns_rdataclass_in;
+       rdatalist2->ttl = 300;
+
+       /* Create second rdata */
+       dns_rdata_init(rdata2);
+       CHECK(dns_test_rdatafromstring(rdata2, dns_rdataclass_in,
+                                      dns_rdatatype_a, data2, 256,
+                                      "192.168.1.2", false));
+
+       ISC_LIST_APPEND(rdatalist2->rdata, rdata2, link);
+       dns_rdatalist_tordataset(rdatalist2, &rdataset2);
+
+       /* Convert to vecheaders (each starts with refcount = 1) */
+       CHECK(dns_rdatavec_fromrdataset(&rdataset1, mctx, &region1, 0));
+       header1 = (dns_vecheader_t *)region1.base;
+
+       CHECK(dns_rdatavec_fromrdataset(&rdataset2, mctx, &region2, 0));
+       header2 = (dns_vecheader_t *)region2.base;
+
+       /* Merge headers (this will create a new header with refcount = 1) */
+       CHECK(dns_rdatavec_merge(header1, header2, mctx, dns_rdataclass_in,
+                                dns_rdatatype_a, 0, 0, &merged_header));
+       assert_non_null(merged_header);
+
+       /* Test: merged header should have expected count */
+       unsigned int merged_count = dns_rdatavec_count(merged_header);
+       assert_int_equal(merged_count, 2);
+
+       /* Test: merged header should have expected size */
+       unsigned int merged_size = dns_rdatavec_size(merged_header);
+       assert_true(merged_size > sizeof(dns_vecheader_t));
+
+cleanup:
+       /* Cleanup rdatasets */
+       if (DNS_RDATASET_VALID(&rdataset1)) {
+               dns_rdataset_disassociate(&rdataset1);
+       }
+       if (DNS_RDATASET_VALID(&rdataset2)) {
+               dns_rdataset_disassociate(&rdataset2);
+       }
+
+       /* Cleanup using refcount - each header should be unreferenced once */
+       if (header1 != NULL) {
+               dns_vecheader_unref(header1);
+       }
+       if (header2 != NULL) {
+               dns_vecheader_unref(header2);
+       }
+       if (merged_header != NULL) {
+               dns_vecheader_unref(merged_header);
+       }
+
+       /* Cleanup temporary structures */
+       if (rdata1 != NULL) {
+               isc_mem_put(mctx, rdata1, sizeof(*rdata1));
+       }
+       if (rdatalist1 != NULL) {
+               isc_mem_put(mctx, rdatalist1, sizeof(*rdatalist1));
+       }
+       if (data1 != NULL) {
+               isc_mem_put(mctx, data1, 256);
+       }
+       if (rdata2 != NULL) {
+               isc_mem_put(mctx, rdata2, sizeof(*rdata2));
+       }
+       if (rdatalist2 != NULL) {
+               isc_mem_put(mctx, rdatalist2, sizeof(*rdatalist2));
+       }
+       if (data2 != NULL) {
+               isc_mem_put(mctx, data2, 256);
+       }
+}
+
 ISC_TEST_LIST_START
 ISC_TEST_ENTRY_CUSTOM(merge_headers, setup_mctx, teardown_mctx)
 ISC_TEST_ENTRY_CUSTOM(merge_case_preservation, setup_mctx, teardown_mctx)
 ISC_TEST_ENTRY_CUSTOM(setcase_size_consistency, setup_mctx, teardown_mctx)
+ISC_TEST_ENTRY_CUSTOM(rdatavec_subtract_assertion_failure, setup_mctx,
+                     teardown_mctx)
+ISC_TEST_ENTRY_CUSTOM(rdatavec_refcount_merge, setup_mctx, teardown_mctx)
 ISC_TEST_LIST_END
 
 ISC_TEST_MAIN