Add doubly linked count list implementation
authorDavid Rowley <drowley@postgresql.org>
Wed, 2 Nov 2022 01:06:05 +0000 (14:06 +1300)
committerDavid Rowley <drowley@postgresql.org>
Wed, 2 Nov 2022 01:06:05 +0000 (14:06 +1300)
We have various requirements when using a dlist_head to keep track of the
number of items in the list.  This, traditionally, has been done by
maintaining a counter variable in the calling code.  Here we tidy this up
by adding "dclist", which is very similar to dlist but also keeps track of
the number of items stored in the list.

Callers may use the new dclist_count() function when they need to know how
many items are stored. Obtaining the count is an O(1) operation.

For simplicity reasons, dclist and dlist both use dlist_node as their node
type and dlist_iter/dlist_mutable_iter as their iterator type. dclists
have all of the same functionality as dlists except there is no function
named dclist_delete().  To remove an item from a list dclist_delete_from()
must be used.  This requires knowing which dclist the given item is stored
in.

Additionally, here we also convert some dlists where additional code
exists to keep track of the number of items stored and to make these use
dclists instead.

Author: David Rowley
Reviewed-by: Bharath Rupireddy, Aleksander Alekseev
Discussion: https://postgr.es/m/CAApHDvrtVxr+FXEX0VbViCFKDGxA3tWDgw9oFewNXCJMmwLjLg@mail.gmail.com

src/backend/access/heap/rewriteheap.c
src/backend/access/transam/multixact.c
src/backend/lib/ilist.c
src/backend/replication/logical/reorderbuffer.c
src/backend/replication/logical/snapbuild.c
src/backend/utils/activity/pgstat_xact.c
src/backend/utils/adt/ri_triggers.c
src/include/lib/ilist.h
src/include/replication/reorderbuffer.h
src/include/utils/pgstat_internal.h
src/tools/pgindent/typedefs.list

index b01b39b008ce588da864905feea5e27981255c6d..a34e9b352dc9143bc3c1c276d76e1048ef89d87e 100644 (file)
@@ -196,8 +196,7 @@ typedef struct RewriteMappingFile
    TransactionId xid;          /* xid that might need to see the row */
    int         vfd;            /* fd of mappings file */
    off_t       off;            /* how far have we written yet */
-   uint32      num_mappings;   /* number of in-memory mappings */
-   dlist_head  mappings;       /* list of in-memory mappings */
+   dclist_head mappings;       /* list of in-memory mappings */
    char        path[MAXPGPATH];    /* path, for error messages */
 } RewriteMappingFile;
 
@@ -864,9 +863,10 @@ logical_heap_rewrite_flush_mappings(RewriteState state)
        Oid         dboid;
        uint32      len;
        int         written;
+       uint32      num_mappings = dclist_count(&src->mappings);
 
        /* this file hasn't got any new mappings */
-       if (src->num_mappings == 0)
+       if (num_mappings == 0)
            continue;
 
        if (state->rs_old_rel->rd_rel->relisshared)
@@ -874,7 +874,7 @@ logical_heap_rewrite_flush_mappings(RewriteState state)
        else
            dboid = MyDatabaseId;
 
-       xlrec.num_mappings = src->num_mappings;
+       xlrec.num_mappings = num_mappings;
        xlrec.mapped_rel = RelationGetRelid(state->rs_old_rel);
        xlrec.mapped_xid = src->xid;
        xlrec.mapped_db = dboid;
@@ -882,31 +882,30 @@ logical_heap_rewrite_flush_mappings(RewriteState state)
        xlrec.start_lsn = state->rs_begin_lsn;
 
        /* write all mappings consecutively */
-       len = src->num_mappings * sizeof(LogicalRewriteMappingData);
+       len = num_mappings * sizeof(LogicalRewriteMappingData);
        waldata_start = waldata = palloc(len);
 
        /*
         * collect data we need to write out, but don't modify ondisk data yet
         */
-       dlist_foreach_modify(iter, &src->mappings)
+       dclist_foreach_modify(iter, &src->mappings)
        {
            RewriteMappingDataEntry *pmap;
 
-           pmap = dlist_container(RewriteMappingDataEntry, node, iter.cur);
+           pmap = dclist_container(RewriteMappingDataEntry, node, iter.cur);
 
            memcpy(waldata, &pmap->map, sizeof(pmap->map));
            waldata += sizeof(pmap->map);
 
            /* remove from the list and free */
-           dlist_delete(&pmap->node);
+           dclist_delete_from(&src->mappings, &pmap->node);
            pfree(pmap);
 
            /* update bookkeeping */
            state->rs_num_rewrite_mappings--;
-           src->num_mappings--;
        }
 
-       Assert(src->num_mappings == 0);
+       Assert(dclist_count(&src->mappings) == 0);
        Assert(waldata == waldata_start + len);
 
        /*
@@ -1002,8 +1001,7 @@ logical_rewrite_log_mapping(RewriteState state, TransactionId xid,
                 LSN_FORMAT_ARGS(state->rs_begin_lsn),
                 xid, GetCurrentTransactionId());
 
-       dlist_init(&src->mappings);
-       src->num_mappings = 0;
+       dclist_init(&src->mappings);
        src->off = 0;
        memcpy(src->path, path, sizeof(path));
        src->vfd = PathNameOpenFile(path,
@@ -1017,8 +1015,7 @@ logical_rewrite_log_mapping(RewriteState state, TransactionId xid,
    pmap = MemoryContextAlloc(state->rs_cxt,
                              sizeof(RewriteMappingDataEntry));
    memcpy(&pmap->map, map, sizeof(LogicalRewriteMappingData));
-   dlist_push_tail(&src->mappings, &pmap->node);
-   src->num_mappings++;
+   dclist_push_tail(&src->mappings, &pmap->node);
    state->rs_num_rewrite_mappings++;
 
    /*
index 5eff87665be7785366862e672aa9c1e4e2cc40da..204aa9504562692a236fae0c9ea6731f36c970ec 100644 (file)
@@ -319,8 +319,7 @@ typedef struct mXactCacheEnt
 } mXactCacheEnt;
 
 #define MAX_CACHE_ENTRIES  256
-static dlist_head MXactCache = DLIST_STATIC_INIT(MXactCache);
-static int MXactCacheMembers = 0;
+static dclist_head MXactCache = DCLIST_STATIC_INIT(MXactCache);
 static MemoryContext MXactContext = NULL;
 
 #ifdef MULTIXACT_DEBUG
@@ -1504,9 +1503,10 @@ mXactCacheGetBySet(int nmembers, MultiXactMember *members)
    /* sort the array so comparison is easy */
    qsort(members, nmembers, sizeof(MultiXactMember), mxactMemberComparator);
 
-   dlist_foreach(iter, &MXactCache)
+   dclist_foreach(iter, &MXactCache)
    {
-       mXactCacheEnt *entry = dlist_container(mXactCacheEnt, node, iter.cur);
+       mXactCacheEnt *entry = dclist_container(mXactCacheEnt, node,
+                                               iter.cur);
 
        if (entry->nmembers != nmembers)
            continue;
@@ -1518,7 +1518,7 @@ mXactCacheGetBySet(int nmembers, MultiXactMember *members)
        if (memcmp(members, entry->members, nmembers * sizeof(MultiXactMember)) == 0)
        {
            debug_elog3(DEBUG2, "CacheGet: found %u", entry->multi);
-           dlist_move_head(&MXactCache, iter.cur);
+           dclist_move_head(&MXactCache, iter.cur);
            return entry->multi;
        }
    }
@@ -1542,9 +1542,10 @@ mXactCacheGetById(MultiXactId multi, MultiXactMember **members)
 
    debug_elog3(DEBUG2, "CacheGet: looking for %u", multi);
 
-   dlist_foreach(iter, &MXactCache)
+   dclist_foreach(iter, &MXactCache)
    {
-       mXactCacheEnt *entry = dlist_container(mXactCacheEnt, node, iter.cur);
+       mXactCacheEnt *entry = dclist_container(mXactCacheEnt, node,
+                                               iter.cur);
 
        if (entry->multi == multi)
        {
@@ -1566,7 +1567,7 @@ mXactCacheGetById(MultiXactId multi, MultiXactMember **members)
             * This is acceptable only because we exit the iteration
             * immediately afterwards.
             */
-           dlist_move_head(&MXactCache, iter.cur);
+           dclist_move_head(&MXactCache, iter.cur);
 
            *members = ptr;
            return entry->nmembers;
@@ -1610,16 +1611,15 @@ mXactCachePut(MultiXactId multi, int nmembers, MultiXactMember *members)
    /* mXactCacheGetBySet assumes the entries are sorted, so sort them */
    qsort(entry->members, nmembers, sizeof(MultiXactMember), mxactMemberComparator);
 
-   dlist_push_head(&MXactCache, &entry->node);
-   if (MXactCacheMembers++ >= MAX_CACHE_ENTRIES)
+   dclist_push_head(&MXactCache, &entry->node);
+   if (dclist_count(&MXactCache) > MAX_CACHE_ENTRIES)
    {
        dlist_node *node;
 
-       node = dlist_tail_node(&MXactCache);
-       dlist_delete(node);
-       MXactCacheMembers--;
+       node = dclist_tail_node(&MXactCache);
+       dclist_delete_from(&MXactCache, node);
 
-       entry = dlist_container(mXactCacheEnt, node, node);
+       entry = dclist_container(mXactCacheEnt, node, node);
        debug_elog3(DEBUG2, "CachePut: pruning cached multi %u",
                    entry->multi);
 
@@ -1699,8 +1699,7 @@ AtEOXact_MultiXact(void)
     * a child of TopTransactionContext, we needn't delete it explicitly.
     */
    MXactContext = NULL;
-   dlist_init(&MXactCache);
-   MXactCacheMembers = 0;
+   dclist_init(&MXactCache);
 }
 
 /*
@@ -1766,8 +1765,7 @@ PostPrepare_MultiXact(TransactionId xid)
     * Discard the local MultiXactId cache like in AtEOXact_MultiXact.
     */
    MXactContext = NULL;
-   dlist_init(&MXactCache);
-   MXactCacheMembers = 0;
+   dclist_init(&MXactCache);
 }
 
 /*
index 29ef21621242a44b0ccc1e7637003737e24e2dc1..e8ea98117641cc30b9b6b26a75f481af97a623d0 100644 (file)
@@ -52,6 +52,23 @@ slist_delete(slist_head *head, slist_node *node)
 }
 
 #ifdef ILIST_DEBUG
+/*
+ * dlist_member_check
+ *     Validate that 'node' is a member of 'head'
+ */
+void
+dlist_member_check(dlist_head *head, dlist_node *node)
+{
+   dlist_iter  iter;
+
+   dlist_foreach(iter, head)
+   {
+       if (iter.cur == node)
+           return;
+   }
+   elog(ERROR, "double linked list member check failure");
+}
+
 /*
  * Verify integrity of a doubly linked list
  */
index 20c9cd139ab5fa5c13496eaa315c31b7a3a5f646..22a15a482ab60ac501ffdaae656e6e2a51ed2954 100644 (file)
@@ -349,8 +349,6 @@ ReorderBufferAllocate(void)
    buffer->by_txn_last_xid = InvalidTransactionId;
    buffer->by_txn_last_txn = NULL;
 
-   buffer->catchange_ntxns = 0;
-
    buffer->outbuf = NULL;
    buffer->outbufsize = 0;
    buffer->size = 0;
@@ -368,7 +366,7 @@ ReorderBufferAllocate(void)
 
    dlist_init(&buffer->toplevel_by_lsn);
    dlist_init(&buffer->txns_by_base_snapshot_lsn);
-   dlist_init(&buffer->catchange_txns);
+   dclist_init(&buffer->catchange_txns);
 
    /*
     * Ensure there's no stale data from prior uses of this slot, in case some
@@ -1553,12 +1551,7 @@ ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
     */
    dlist_delete(&txn->node);
    if (rbtxn_has_catalog_changes(txn))
-   {
-       dlist_delete(&txn->catchange_node);
-       rb->catchange_ntxns--;
-
-       Assert(rb->catchange_ntxns >= 0);
-   }
+       dclist_delete_from(&rb->catchange_txns, &txn->catchange_node);
 
    /* now remove reference from buffer */
    hash_search(rb->by_txn,
@@ -3309,8 +3302,7 @@ ReorderBufferXidSetCatalogChanges(ReorderBuffer *rb, TransactionId xid,
    if (!rbtxn_has_catalog_changes(txn))
    {
        txn->txn_flags |= RBTXN_HAS_CATALOG_CHANGES;
-       dlist_push_tail(&rb->catchange_txns, &txn->catchange_node);
-       rb->catchange_ntxns++;
+       dclist_push_tail(&rb->catchange_txns, &txn->catchange_node);
    }
 
    /*
@@ -3323,8 +3315,7 @@ ReorderBufferXidSetCatalogChanges(ReorderBuffer *rb, TransactionId xid,
    if (toptxn != NULL && !rbtxn_has_catalog_changes(toptxn))
    {
        toptxn->txn_flags |= RBTXN_HAS_CATALOG_CHANGES;
-       dlist_push_tail(&rb->catchange_txns, &toptxn->catchange_node);
-       rb->catchange_ntxns++;
+       dclist_push_tail(&rb->catchange_txns, &toptxn->catchange_node);
    }
 }
 
@@ -3342,19 +3333,17 @@ ReorderBufferGetCatalogChangesXacts(ReorderBuffer *rb)
    size_t      xcnt = 0;
 
    /* Quick return if the list is empty */
-   if (rb->catchange_ntxns == 0)
-   {
-       Assert(dlist_is_empty(&rb->catchange_txns));
+   if (dclist_count(&rb->catchange_txns) == 0)
        return NULL;
-   }
 
    /* Initialize XID array */
-   xids = (TransactionId *) palloc(sizeof(TransactionId) * rb->catchange_ntxns);
-   dlist_foreach(iter, &rb->catchange_txns)
+   xids = (TransactionId *) palloc(sizeof(TransactionId) *
+                                   dclist_count(&rb->catchange_txns));
+   dclist_foreach(iter, &rb->catchange_txns)
    {
-       ReorderBufferTXN *txn = dlist_container(ReorderBufferTXN,
-                                               catchange_node,
-                                               iter.cur);
+       ReorderBufferTXN *txn = dclist_container(ReorderBufferTXN,
+                                                catchange_node,
+                                                iter.cur);
 
        Assert(rbtxn_has_catalog_changes(txn));
 
@@ -3363,7 +3352,7 @@ ReorderBufferGetCatalogChangesXacts(ReorderBuffer *rb)
 
    qsort(xids, xcnt, sizeof(TransactionId), xidComparator);
 
-   Assert(xcnt == rb->catchange_ntxns);
+   Assert(xcnt == dclist_count(&rb->catchange_txns));
    return xids;
 }
 
index f892d19bfb08fb0c56b35087778d5d1e4dd70657..5006a5c464fcac3ec06e580cdc6e6058773f337a 100644 (file)
@@ -1688,7 +1688,7 @@ SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn)
 
    /* Get the catalog modifying transactions that are yet not committed */
    catchange_xip = ReorderBufferGetCatalogChangesXacts(builder->reorder);
-   catchange_xcnt = builder->reorder->catchange_ntxns;
+   catchange_xcnt = dclist_count(&builder->reorder->catchange_txns);
 
    needed_length = sizeof(SnapBuildOnDisk) +
        sizeof(TransactionId) * (builder->committed.xcnt + catchange_xcnt);
index d6f660edf7b1729fe22f0672ed3d5607acc45b5f..5a3aca4aefd9f13bd431670ae0291cfca7fb9860 100644 (file)
@@ -70,16 +70,13 @@ AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit)
    dlist_mutable_iter iter;
    int         not_freed_count = 0;
 
-   if (xact_state->pending_drops_count == 0)
-   {
-       Assert(dlist_is_empty(&xact_state->pending_drops));
+   if (dclist_count(&xact_state->pending_drops) == 0)
        return;
-   }
 
-   dlist_foreach_modify(iter, &xact_state->pending_drops)
+   dclist_foreach_modify(iter, &xact_state->pending_drops)
    {
        PgStat_PendingDroppedStatsItem *pending =
-       dlist_container(PgStat_PendingDroppedStatsItem, node, iter.cur);
+       dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur);
        xl_xact_stats_item *it = &pending->item;
 
        if (isCommit && !pending->is_create)
@@ -101,8 +98,7 @@ AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit)
                not_freed_count++;
        }
 
-       dlist_delete(&pending->node);
-       xact_state->pending_drops_count--;
+       dclist_delete_from(&xact_state->pending_drops, &pending->node);
        pfree(pending);
    }
 
@@ -144,19 +140,18 @@ AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state,
    dlist_mutable_iter iter;
    int         not_freed_count = 0;
 
-   if (xact_state->pending_drops_count == 0)
+   if (dclist_count(&xact_state->pending_drops) == 0)
        return;
 
    parent_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
 
-   dlist_foreach_modify(iter, &xact_state->pending_drops)
+   dclist_foreach_modify(iter, &xact_state->pending_drops)
    {
        PgStat_PendingDroppedStatsItem *pending =
-       dlist_container(PgStat_PendingDroppedStatsItem, node, iter.cur);
+       dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur);
        xl_xact_stats_item *it = &pending->item;
 
-       dlist_delete(&pending->node);
-       xact_state->pending_drops_count--;
+       dclist_delete_from(&xact_state->pending_drops, &pending->node);
 
        if (!isCommit && pending->is_create)
        {
@@ -175,8 +170,7 @@ AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state,
             * remove the stats object, the surrounding transaction might
             * still abort. Pass it on to the parent.
             */
-           dlist_push_tail(&parent_xact_state->pending_drops, &pending->node);
-           parent_xact_state->pending_drops_count++;
+           dclist_push_tail(&parent_xact_state->pending_drops, &pending->node);
        }
        else
        {
@@ -184,7 +178,7 @@ AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state,
        }
    }
 
-   Assert(xact_state->pending_drops_count == 0);
+   Assert(dclist_count(&xact_state->pending_drops) == 0);
    if (not_freed_count > 0)
        pgstat_request_entry_refs_gc();
 }
@@ -250,8 +244,7 @@ pgstat_get_xact_stack_level(int nest_level)
        xact_state = (PgStat_SubXactStatus *)
            MemoryContextAlloc(TopTransactionContext,
                               sizeof(PgStat_SubXactStatus));
-       dlist_init(&xact_state->pending_drops);
-       xact_state->pending_drops_count = 0;
+       dclist_init(&xact_state->pending_drops);
        xact_state->nest_level = nest_level;
        xact_state->prev = pgStatXactStack;
        xact_state->first = NULL;
@@ -291,20 +284,20 @@ pgstat_get_transactional_drops(bool isCommit, xl_xact_stats_item **items)
    Assert(!isCommit || xact_state->nest_level == 1);
    Assert(!isCommit || xact_state->prev == NULL);
 
-   *items = palloc(xact_state->pending_drops_count
+   *items = palloc(dclist_count(&xact_state->pending_drops)
                    * sizeof(xl_xact_stats_item));
 
-   dlist_foreach(iter, &xact_state->pending_drops)
+   dclist_foreach(iter, &xact_state->pending_drops)
    {
        PgStat_PendingDroppedStatsItem *pending =
-       dlist_container(PgStat_PendingDroppedStatsItem, node, iter.cur);
+       dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur);
 
        if (isCommit && pending->is_create)
            continue;
        if (!isCommit && !pending->is_create)
            continue;
 
-       Assert(nitems < xact_state->pending_drops_count);
+       Assert(nitems < dclist_count(&xact_state->pending_drops));
        (*items)[nitems++] = pending->item;
    }
 
@@ -351,8 +344,7 @@ create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, bool
    drop->item.dboid = dboid;
    drop->item.objoid = objoid;
 
-   dlist_push_tail(&xact_state->pending_drops, &drop->node);
-   xact_state->pending_drops_count++;
+   dclist_push_tail(&xact_state->pending_drops, &drop->node);
 }
 
 /*
index 1d503e7e0114f391ec2d3c10e7f5fe5c98ad5359..61c2eecacaa435bfef16b64d00314b2d08e067a4 100644 (file)
@@ -176,8 +176,7 @@ typedef struct RI_CompareHashEntry
 static HTAB *ri_constraint_cache = NULL;
 static HTAB *ri_query_cache = NULL;
 static HTAB *ri_compare_cache = NULL;
-static dlist_head ri_constraint_cache_valid_list;
-static int ri_constraint_cache_valid_count = 0;
+static dclist_head ri_constraint_cache_valid_list;
 
 
 /*
@@ -2172,10 +2171,9 @@ ri_LoadConstraintInfo(Oid constraintOid)
 
    /*
     * For efficient processing of invalidation messages below, we keep a
-    * doubly-linked list, and a count, of all currently valid entries.
+    * doubly-linked count list of all currently valid entries.
     */
-   dlist_push_tail(&ri_constraint_cache_valid_list, &riinfo->valid_link);
-   ri_constraint_cache_valid_count++;
+   dclist_push_tail(&ri_constraint_cache_valid_list, &riinfo->valid_link);
 
    riinfo->valid = true;
 
@@ -2233,13 +2231,13 @@ InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
     * O(N^2) behavior in situations where a session touches many foreign keys
     * and also does many ALTER TABLEs, such as a restore from pg_dump.
     */
-   if (ri_constraint_cache_valid_count > 1000)
+   if (dclist_count(&ri_constraint_cache_valid_list) > 1000)
        hashvalue = 0;          /* pretend it's a cache reset */
 
-   dlist_foreach_modify(iter, &ri_constraint_cache_valid_list)
+   dclist_foreach_modify(iter, &ri_constraint_cache_valid_list)
    {
-       RI_ConstraintInfo *riinfo = dlist_container(RI_ConstraintInfo,
-                                                   valid_link, iter.cur);
+       RI_ConstraintInfo *riinfo = dclist_container(RI_ConstraintInfo,
+                                                    valid_link, iter.cur);
 
        /*
         * We must invalidate not only entries directly matching the given
@@ -2252,8 +2250,7 @@ InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
        {
            riinfo->valid = false;
            /* Remove invalidated entries from the list, too */
-           dlist_delete(iter.cur);
-           ri_constraint_cache_valid_count--;
+           dclist_delete_from(&ri_constraint_cache_valid_list, iter.cur);
        }
    }
 }
index 7ab0888f4ff353132cad16f79e90bc30f6708290..3c543e7c365dde9747a10c0e5d45e5598dba9979 100644 (file)
  * the link fields in the remainder would be wasted space.  But usually,
  * it saves space to not have separately-allocated list nodes.)
  *
+ * The doubly-linked list comes in 2 forms.  dlist_head defines a head of a
+ * doubly-linked list of dlist_nodes, whereas dclist_head defines the head of
+ * a doubly-linked list of dlist_nodes with an additional 'count' field to
+ * keep track of how many items are contained within the given list.  For
+ * simplicity, dlist_head and dclist_head share the same node and iterator
+ * types.  The functions to manipulate a dlist_head always have a name
+ * starting with "dlist", whereas functions to manipulate a dclist_head have a
+ * name starting with "dclist".  dclist_head comes with an additional function
+ * (dclist_count) to return the number of entries in the list.  dclists are
+ * able to store a maximum of PG_UINT32_MAX elements.  It is up to the caller
+ * to ensure no more than this many items are added to a dclist.
+ *
  * None of the functions here allocate any memory; they just manipulate
- * externally managed memory.  The APIs for singly and doubly linked lists
- * are identical as far as capabilities of both allow.
+ * externally managed memory.  With the exception doubly-linked count lists
+ * providing the ability to obtain the number of items in the list, the APIs
+ * for singly and both doubly linked lists are identical as far as
+ * capabilities of both allow.
  *
  * Each list has a list header, which exists even when the list is empty.
  * An empty singly-linked list has a NULL pointer in its header.
- * There are two kinds of empty doubly linked lists: those that have been
- * initialized to NULL, and those that have been initialized to circularity.
+ *
+ * For both doubly-linked list types, there are two valid ways to represent an
+ * empty list.  The head's 'next' pointer can either be NULL or the head's
+ * 'next' and 'prev' links can both point back to the list head (circular).
  * (If a dlist is modified and then all its elements are deleted, it will be
- * in the circular state.) We prefer circular dlists because there are some
+ * in the circular state.) We prefer circular dlists because there are some
  * operations that can be done without branches (and thus faster) on lists
  * that use circular representation.  However, it is often convenient to
  * initialize list headers to zeroes rather than setting them up with an
- * explicit initialization function, so we also allow the other case.
+ * explicit initialization function, so we also allow the NULL initalization.
  *
  * EXAMPLES
  *
@@ -146,15 +162,17 @@ typedef struct dlist_head
 
 
 /*
- * Doubly linked list iterator.
+ * Doubly linked list iterator type for dlist_head and and dclist_head types.
+ *
+ * Used as state in dlist_foreach() and dlist_reverse_foreach() (and the
+ * dclist variant thereof).
  *
- * Used as state in dlist_foreach() and dlist_reverse_foreach(). To get the
- * current element of the iteration use the 'cur' member.
+ * To get the current element of the iteration use the 'cur' member.
  *
  * Iterations using this are *not* allowed to change the list while iterating!
  *
  * NB: We use an extra "end" field here to avoid multiple evaluations of
- * arguments in the dlist_foreach() macro.
+ * arguments in the dlist_foreach() and dclist_foreach() macros.
  */
 typedef struct dlist_iter
 {
@@ -163,10 +181,12 @@ typedef struct dlist_iter
 } dlist_iter;
 
 /*
- * Doubly linked list iterator allowing some modifications while iterating.
+ * Doubly linked list iterator for both dlist_head and dclist_head types.
+ * This iterator type allows some modifications while iterating.
  *
- * Used as state in dlist_foreach_modify(). To get the current element of the
- * iteration use the 'cur' member.
+ * Used as state in dlist_foreach_modify() and dclist_foreach_modify().
+ *
+ * To get the current element of the iteration use the 'cur' member.
  *
  * Iterations using this are only allowed to change the list at the current
  * point of iteration. It is fine to delete the current node, but it is *not*
@@ -182,6 +202,19 @@ typedef struct dlist_mutable_iter
    dlist_node *end;            /* last node we'll iterate to */
 } dlist_mutable_iter;
 
+/*
+ * Head of a doubly linked list with a count of the number of items
+ *
+ * This internally makes use of a dlist to implement the actual list.  When
+ * items are added or removed from the list the count is updated to reflect
+ * the current number of items in the list.
+ */
+typedef struct dclist_head
+{
+   dlist_head  dlist;          /* the actual list header */
+   uint32      count;          /* the number of items in the list */
+} dclist_head;
+
 /*
  * Node of a singly linked list.
  *
@@ -246,6 +279,7 @@ typedef struct slist_mutable_iter
 
 /* Static initializers */
 #define DLIST_STATIC_INIT(name) {{&(name).head, &(name).head}}
+#define DCLIST_STATIC_INIT(name) {{{&(name).dlist.head, &(name).dlist.head}}, 0}
 #define SLIST_STATIC_INIT(name) {{NULL}}
 
 
@@ -255,6 +289,7 @@ typedef struct slist_mutable_iter
 extern void slist_delete(slist_head *head, slist_node *node);
 
 #ifdef ILIST_DEBUG
+extern void dlist_member_check(dlist_head *head, dlist_node *node);
 extern void dlist_check(dlist_head *head);
 extern void slist_check(slist_head *head);
 #else
@@ -264,6 +299,7 @@ extern void slist_check(slist_head *head);
  * in which functions the only point of passing the list head pointer is to be
  * able to run these checks.
  */
+#define dlist_member_check(head, node) ((void) (head))
 #define dlist_check(head)  ((void) (head))
 #define slist_check(head)  ((void) (head))
 #endif                         /* ILIST_DEBUG */
@@ -361,6 +397,17 @@ dlist_delete(dlist_node *node)
    node->next->prev = node->prev;
 }
 
+/*
+ * Same as dlist_delete, but performs checks in ILIST_DEBUG builds to ensure
+ * that 'node' belongs to 'head'.
+ */
+static inline void
+dlist_delete_from(dlist_head *head, dlist_node *node)
+{
+   dlist_member_check(head, node);
+   dlist_delete(node);
+}
+
 /*
  * Remove and return the first node from a list (there must be one).
  */
@@ -562,6 +609,309 @@ dlist_tail_node(dlist_head *head)
         (iter).cur != (iter).end;                                          \
         (iter).cur = (iter).cur->prev)
 
+/* doubly-linked count list implementation */
+
+/*
+ * dclist_init
+ *     Initialize a doubly linked count list.
+ *
+ * Previous state will be thrown away without any cleanup.
+ */
+static inline void
+dclist_init(dclist_head *head)
+{
+   dlist_init(&head->dlist);
+   head->count = 0;
+}
+
+/*
+ * dclist_is_empty
+ *     Returns true if the list is empty, otherwise false.
+ */
+static inline bool
+dclist_is_empty(dclist_head *head)
+{
+   Assert(dlist_is_empty(&head->dlist) == (head->count == 0));
+   return (head->count == 0);
+}
+
+/*
+ * dclist_push_head
+ *     Insert a node at the beginning of the list.
+ */
+static inline void
+dclist_push_head(dclist_head *head, dlist_node *node)
+{
+   if (head->dlist.head.next == NULL)  /* convert NULL header to circular */
+       dclist_init(head);
+
+   dlist_push_head(&head->dlist, node);
+   head->count++;
+
+   Assert(head->count > 0);    /* count overflow check */
+}
+
+/*
+ * dclist_push_tail
+ *     Insert a node at the end of the list.
+ */
+static inline void
+dclist_push_tail(dclist_head *head, dlist_node *node)
+{
+   if (head->dlist.head.next == NULL)  /* convert NULL header to circular */
+       dclist_init(head);
+
+   dlist_push_tail(&head->dlist, node);
+   head->count++;
+
+   Assert(head->count > 0);    /* count overflow check */
+}
+
+/*
+ * dclist_insert_after
+ *     Insert a node after another *in the same list*
+ *
+ * Caution: 'after' must be a member of 'head'.
+ */
+static inline void
+dclist_insert_after(dclist_head *head, dlist_node *after, dlist_node *node)
+{
+   dlist_member_check(&head->dlist, after);
+   Assert(head->count > 0);    /* must be at least 1 already */
+
+   dlist_insert_after(after, node);
+   head->count++;
+
+   Assert(head->count > 0);    /* count overflow check */
+}
+
+/*
+ * dclist_insert_before
+ *     Insert a node before another *in the same list*
+ *
+ * Caution: 'before' must be a member of 'head'.
+ */
+static inline void
+dclist_insert_before(dclist_head *head, dlist_node *before, dlist_node *node)
+{
+   dlist_member_check(&head->dlist, before);
+   Assert(head->count > 0);    /* must be at least 1 already */
+
+   dlist_insert_before(before, node);
+   head->count++;
+
+   Assert(head->count > 0);    /* count overflow check */
+}
+
+/*
+ * dclist_delete_from
+ *     Deletes 'node' from 'head'.
+ *
+ * Caution: 'node' must be a member of 'head'.
+ */
+static inline void
+dclist_delete_from(dclist_head *head, dlist_node *node)
+{
+   Assert(head->count > 0);
+
+   dlist_delete_from(&head->dlist, node);
+   head->count--;
+}
+
+/*
+ * dclist_pop_head_node
+ *     Remove and return the first node from a list (there must be one).
+ */
+static inline dlist_node *
+dclist_pop_head_node(dclist_head *head)
+{
+   dlist_node *node;
+
+   Assert(head->count > 0);
+
+   node = dlist_pop_head_node(&head->dlist);
+   head->count--;
+   return node;
+}
+
+/*
+ * dclist_move_head
+ *     Move 'node' from its current position in the list to the head position
+ *     in 'head'.
+ *
+ * Caution: 'node' must be a member of 'head'.
+ */
+static inline void
+dclist_move_head(dclist_head *head, dlist_node *node)
+{
+   dlist_member_check(&head->dlist, node);
+   Assert(head->count > 0);
+
+   dlist_move_head(&head->dlist, node);
+}
+
+/*
+ * dclist_move_tail
+ *     Move 'node' from its current position in the list to the tail position
+ *     in 'head'.
+ *
+ * Caution: 'node' must be a member of 'head'.
+ */
+static inline void
+dclist_move_tail(dclist_head *head, dlist_node *node)
+{
+   dlist_member_check(&head->dlist, node);
+   Assert(head->count > 0);
+
+   dlist_move_tail(&head->dlist, node);
+}
+
+/*
+ * dclist_has_next
+ *     Check whether 'node' has a following node.
+ *
+ * Caution: 'node' must be a member of 'head'.
+ */
+static inline bool
+dclist_has_next(dclist_head *head, dlist_node *node)
+{
+   dlist_member_check(&head->dlist, node);
+   Assert(head->count > 0);
+
+   return dlist_has_next(&head->dlist, node);
+}
+
+/*
+ * dclist_has_prev
+ *     Check whether 'node' has a preceding node.
+ *
+ * Caution: 'node' must be a member of 'head'.
+ */
+static inline bool
+dclist_has_prev(dclist_head *head, dlist_node *node)
+{
+   dlist_member_check(&head->dlist, node);
+   Assert(head->count > 0);
+
+   return dlist_has_prev(&head->dlist, node);
+}
+
+/*
+ * dclist_next_node
+ *     Return the next node in the list (there must be one).
+ */
+static inline dlist_node *
+dclist_next_node(dclist_head *head, dlist_node *node)
+{
+   Assert(head->count > 0);
+
+   return dlist_next_node(&head->dlist, node);
+}
+
+/*
+ * dclist_prev_node
+ *     Return the prev node in the list (there must be one).
+ */
+static inline dlist_node *
+dclist_prev_node(dclist_head *head, dlist_node *node)
+{
+   Assert(head->count > 0);
+
+   return dlist_prev_node(&head->dlist, node);
+}
+
+/* internal support function to get address of head element's struct */
+static inline void *
+dclist_head_element_off(dclist_head *head, size_t off)
+{
+   Assert(!dclist_is_empty(head));
+
+   return (char *) head->dlist.head.next - off;
+}
+
+/*
+ * dclist_head_node
+ *     Return the first node in the list (there must be one).
+ */
+static inline dlist_node *
+dclist_head_node(dclist_head *head)
+{
+   Assert(head->count > 0);
+
+   return (dlist_node *) dlist_head_element_off(&head->dlist, 0);
+}
+
+/* internal support function to get address of tail element's struct */
+static inline void *
+dclist_tail_element_off(dclist_head *head, size_t off)
+{
+   Assert(!dclist_is_empty(head));
+
+   return (char *) head->dlist.head.prev - off;
+}
+
+/*
+ * Return the last node in the list (there must be one).
+ */
+static inline dlist_node *
+dclist_tail_node(dclist_head *head)
+{
+   Assert(head->count > 0);
+
+   return (dlist_node *) dlist_tail_element_off(&head->dlist, 0);
+}
+
+/*
+ * dclist_count
+ *     Returns the stored number of entries in 'head'
+ */
+static inline uint32
+dclist_count(dclist_head *head)
+{
+   Assert(dlist_is_empty(&head->dlist) == (head->count == 0));
+
+   return head->count;
+}
+
+/*
+ * Return the containing struct of 'type' where 'membername' is the dlist_node
+ * pointed at by 'ptr'.
+ *
+ * This is used to convert a dlist_node * back to its containing struct.
+ *
+ * Note: This is effectively just the same as dlist_container, so reuse that.
+ */
+#define dclist_container(type, membername, ptr) \
+       dlist_container(type, membername, ptr)
+
+ /*
+  * Return the address of the first element in the list.
+  *
+  * The list must not be empty.
+  */
+#define dclist_head_element(type, membername, lhead)                           \
+   (AssertVariableIsOfTypeMacro(((type *) NULL)->membername, dlist_node),  \
+    (type *) dclist_head_element_off(lhead, offsetof(type, membername)))
+
+ /*
+  * Return the address of the last element in the list.
+  *
+  * The list must not be empty.
+  */
+#define dclist_tail_element(type, membername, lhead)                           \
+   (AssertVariableIsOfTypeMacro(((type *) NULL)->membername, dlist_node),  \
+    ((type *) dclist_tail_element_off(lhead, offsetof(type, membername))))
+
+
+/* Iterators for dclists */
+#define dclist_foreach(iter, lhead) \
+   dlist_foreach(iter, &((lhead)->dlist))
+
+#define dclist_foreach_modify(iter, lhead) \
+   dlist_foreach_modify(iter, &((lhead)->dlist))
+
+#define dclist_reverse_foreach(iter, lhead) \
+   dlist_reverse_foreach(iter, &((lhead)->dlist))
 
 /* singly linked list implementation */
 
index 02b59a193158a54d7390ef06a7814e86d529a190..b23d8cc4f9fdb426e497476c35d7c55fc714a229 100644 (file)
@@ -534,8 +534,7 @@ struct ReorderBuffer
    /*
     * Transactions and subtransactions that have modified system catalogs.
     */
-   dlist_head  catchange_txns;
-   int         catchange_ntxns;
+   dclist_head catchange_txns;
 
    /*
     * one-entry sized cache for by_txn. Very frequently the same txn gets
index c869533b282451e7fec2953e21b987b57c7bbfd9..e2c7b5932409c4468ec4a2571b7a07fa75c9ba79 100644 (file)
@@ -162,8 +162,7 @@ typedef struct PgStat_SubXactStatus
     * if the transaction commits/aborts. To handle replicas and crashes,
     * stats drops are included in commit / abort records.
     */
-   dlist_head  pending_drops;
-   int         pending_drops_count;
+   dclist_head pending_drops;
 
    /*
     * Tuple insertion/deletion counts for an open transaction can't be
index 2f02cc8f4222864d67cc844c2f8f7100bd3bdf23..9683b0a88e527667b950d8c11373da22a7d6ed89 100644 (file)
@@ -1514,8 +1514,8 @@ MemoryContextCallback
 MemoryContextCallbackFunction
 MemoryContextCounters
 MemoryContextData
-MemoryContextMethods
 MemoryContextMethodID
+MemoryContextMethods
 MemoryStatsPrintFunc
 MergeAction
 MergeActionState
@@ -3194,6 +3194,7 @@ datapagemap_t
 dateKEY
 datetkn
 dce_uuid_t
+dclist_head
 decimal
 deparse_columns
 deparse_context