diff --git a/src/backend/lib/Makefile b/src/backend/lib/Makefile index 2e1061e24aa..98ce3d7e4ad 100644 --- a/src/backend/lib/Makefile +++ b/src/backend/lib/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/lib top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -OBJS = dllist.o stringinfo.o +OBJS = ilist.o stringinfo.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/lib/dllist.c b/src/backend/lib/dllist.c deleted file mode 100644 index 52af56a0795..00000000000 --- a/src/backend/lib/dllist.c +++ /dev/null @@ -1,214 +0,0 @@ -/*------------------------------------------------------------------------- - * - * dllist.c - * this is a simple doubly linked list implementation - * the elements of the lists are void* - * - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * src/backend/lib/dllist.c - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include "lib/dllist.h" - - -Dllist * -DLNewList(void) -{ - Dllist *l; - - l = (Dllist *) palloc(sizeof(Dllist)); - - l->dll_head = NULL; - l->dll_tail = NULL; - - return l; -} - -void -DLInitList(Dllist *list) -{ - list->dll_head = NULL; - list->dll_tail = NULL; -} - -/* - * free up a list and all the nodes in it --- but *not* whatever the nodes - * might point to! - */ -void -DLFreeList(Dllist *list) -{ - Dlelem *curr; - - while ((curr = DLRemHead(list)) != NULL) - pfree(curr); - - pfree(list); -} - -Dlelem * -DLNewElem(void *val) -{ - Dlelem *e; - - e = (Dlelem *) palloc(sizeof(Dlelem)); - - e->dle_next = NULL; - e->dle_prev = NULL; - e->dle_val = val; - e->dle_list = NULL; - return e; -} - -void -DLInitElem(Dlelem *e, void *val) -{ - e->dle_next = NULL; - e->dle_prev = NULL; - e->dle_val = val; - e->dle_list = NULL; -} - -void -DLFreeElem(Dlelem *e) -{ - pfree(e); -} - -void -DLRemove(Dlelem *e) -{ - Dllist *l = e->dle_list; - - if (e->dle_prev) - e->dle_prev->dle_next = e->dle_next; - else - { - /* must be the head element */ - Assert(e == l->dll_head); - l->dll_head = e->dle_next; - } - if (e->dle_next) - e->dle_next->dle_prev = e->dle_prev; - else - { - /* must be the tail element */ - Assert(e == l->dll_tail); - l->dll_tail = e->dle_prev; - } - - e->dle_next = NULL; - e->dle_prev = NULL; - e->dle_list = NULL; -} - -void -DLAddHead(Dllist *l, Dlelem *e) -{ - e->dle_list = l; - - if (l->dll_head) - l->dll_head->dle_prev = e; - e->dle_next = l->dll_head; - e->dle_prev = NULL; - l->dll_head = e; - - if (l->dll_tail == NULL) /* if this is first element added */ - l->dll_tail = e; -} - -void -DLAddTail(Dllist *l, Dlelem *e) -{ - e->dle_list = l; - - if (l->dll_tail) - l->dll_tail->dle_next = e; - e->dle_prev = l->dll_tail; - e->dle_next = NULL; - l->dll_tail = e; - - if (l->dll_head == NULL) /* if this is first element added */ - l->dll_head = e; -} - -Dlelem * -DLRemHead(Dllist *l) -{ - /* remove and return the head */ - Dlelem *result = l->dll_head; - - if (result == NULL) - return result; - - if (result->dle_next) - result->dle_next->dle_prev = NULL; - - l->dll_head = result->dle_next; - - if (result == l->dll_tail) /* if the head is also the tail */ - l->dll_tail = NULL; - - result->dle_next = NULL; - result->dle_list = NULL; - - return result; -} - -Dlelem * -DLRemTail(Dllist *l) -{ - /* remove and return the tail */ - Dlelem *result = l->dll_tail; - - if (result == NULL) - return result; - - if (result->dle_prev) - result->dle_prev->dle_next = NULL; - - l->dll_tail = result->dle_prev; - - if (result == l->dll_head) /* if the tail is also the head */ - l->dll_head = NULL; - - result->dle_prev = NULL; - result->dle_list = NULL; - - return result; -} - -/* Same as DLRemove followed by DLAddHead, but faster */ -void -DLMoveToFront(Dlelem *e) -{ - Dllist *l = e->dle_list; - - if (l->dll_head == e) - return; /* Fast path if already at front */ - - Assert(e->dle_prev != NULL); /* since it's not the head */ - e->dle_prev->dle_next = e->dle_next; - - if (e->dle_next) - e->dle_next->dle_prev = e->dle_prev; - else - { - /* must be the tail element */ - Assert(e == l->dll_tail); - l->dll_tail = e->dle_prev; - } - - l->dll_head->dle_prev = e; - e->dle_next = l->dll_head; - e->dle_prev = NULL; - l->dll_head = e; - /* We need not check dll_tail, since there must have been > 1 entry */ -} diff --git a/src/backend/lib/ilist.c b/src/backend/lib/ilist.c new file mode 100644 index 00000000000..c5831acd67d --- /dev/null +++ b/src/backend/lib/ilist.c @@ -0,0 +1,109 @@ +/*------------------------------------------------------------------------- + * + * ilist.c + * support for integrated/inline doubly- and singly- linked lists + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/lib/ilist.c + * + * NOTES + * This file only contains functions that are too big to be considered + * for inlining. See ilist.h for most of the goodies. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +/* See ilist.h */ +#define ILIST_INCLUDE_DEFINITIONS + +#include "lib/ilist.h" + +/* + * removes a node from a list + * + * Attention: O(n) + */ +void +slist_delete(slist_head *head, slist_node *node) +{ + slist_node *last = &head->head; + slist_node *cur; + bool found PG_USED_FOR_ASSERTS_ONLY = false; + + while ((cur = last->next) != NULL) + { + if (cur == node) + { + last->next = cur->next; +#ifdef USE_ASSERT_CHECKING + found = true; +#endif + break; + } + last = cur; + } + + slist_check(head); + Assert(found); +} + +#ifdef ILIST_DEBUG +/* + * Verify integrity of a doubly linked list + */ +void +dlist_check(dlist_head *head) +{ + dlist_node *cur; + + if (head == NULL || !(&head->head)) + elog(ERROR, "doubly linked list head is not properly initialized"); + + /* iterate in forward direction */ + for (cur = head->head.next; cur != &head->head; cur = cur->next) + { + if (cur == NULL || + cur->next == NULL || + cur->prev == NULL || + cur->prev->next != cur || + cur->next->prev != cur) + elog(ERROR, "doubly linked list is corrupted"); + } + + /* iterate in backward direction */ + for (cur = head->head.prev; cur != &head->head; cur = cur->prev) + { + if (cur == NULL || + cur->next == NULL || + cur->prev == NULL || + cur->prev->next != cur || + cur->next->prev != cur) + elog(ERROR, "doubly linked list is corrupted"); + } +} + +/* + * Verify integrity of a singly linked list + */ +void +slist_check(slist_head *head) +{ + slist_node *cur; + + if (head == NULL) + elog(ERROR, "singly linked is NULL"); + + /* + * there isn't much we can test in a singly linked list other that it + * actually ends sometime, i.e. hasn't introduced a cycle or similar + */ + for (cur = head->head.next; cur != NULL; cur = cur->next) + ; +} + +#endif /* ILIST_DEBUG */ diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 74db821387c..afd15aac973 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -77,7 +77,7 @@ #include "catalog/pg_database.h" #include "commands/dbcommands.h" #include "commands/vacuum.h" -#include "lib/dllist.h" +#include "lib/ilist.h" #include "libpq/pqsignal.h" #include "miscadmin.h" #include "pgstat.h" @@ -152,6 +152,7 @@ typedef struct avl_dbase Oid adl_datid; /* hash key -- must be first */ TimestampTz adl_next_worker; int adl_score; + dlist_node adl_node; } avl_dbase; /* struct to keep track of databases in worker */ @@ -208,7 +209,7 @@ typedef struct autovac_table */ typedef struct WorkerInfoData { - SHM_QUEUE wi_links; + dlist_node wi_links; Oid wi_dboid; Oid wi_tableoid; PGPROC *wi_proc; @@ -251,15 +252,18 @@ typedef struct { sig_atomic_t av_signal[AutoVacNumSignals]; pid_t av_launcherpid; - WorkerInfo av_freeWorkers; - SHM_QUEUE av_runningWorkers; + dlist_head av_freeWorkers; + dlist_head av_runningWorkers; WorkerInfo av_startingWorker; } AutoVacuumShmemStruct; static AutoVacuumShmemStruct *AutoVacuumShmem; -/* the database list in the launcher, and the context that contains it */ -static Dllist *DatabaseList = NULL; +/* + * the database list (of avl_dbase elements) in the launcher, and the context + * that contains it + */ +static dlist_head DatabaseList = DLIST_STATIC_INIT(DatabaseList); static MemoryContext DatabaseListCxt = NULL; /* Pointer to my own WorkerInfo, valid on each worker */ @@ -508,7 +512,7 @@ AutoVacLauncherMain(int argc, char *argv[]) /* don't leave dangling pointers to freed memory */ DatabaseListCxt = NULL; - DatabaseList = NULL; + dlist_init(&DatabaseList); /* * Make sure pgstat also considers our stat data as gone. Note: we @@ -576,7 +580,7 @@ AutoVacLauncherMain(int argc, char *argv[]) struct timeval nap; TimestampTz current_time = 0; bool can_launch; - Dlelem *elem; + avl_dbase *avdb; int rc; /* @@ -586,7 +590,7 @@ AutoVacLauncherMain(int argc, char *argv[]) * wakening conditions. */ - launcher_determine_sleep((AutoVacuumShmem->av_freeWorkers != NULL), + launcher_determine_sleep(!dlist_is_empty(&AutoVacuumShmem->av_freeWorkers), false, &nap); /* Allow sinval catchup interrupts while sleeping */ @@ -679,7 +683,7 @@ AutoVacLauncherMain(int argc, char *argv[]) current_time = GetCurrentTimestamp(); LWLockAcquire(AutovacuumLock, LW_SHARED); - can_launch = (AutoVacuumShmem->av_freeWorkers != NULL); + can_launch = !dlist_is_empty(&AutoVacuumShmem->av_freeWorkers); if (AutoVacuumShmem->av_startingWorker != NULL) { @@ -721,8 +725,7 @@ AutoVacLauncherMain(int argc, char *argv[]) worker->wi_tableoid = InvalidOid; worker->wi_proc = NULL; worker->wi_launchtime = 0; - worker->wi_links.next = (SHM_QUEUE *) AutoVacuumShmem->av_freeWorkers; - AutoVacuumShmem->av_freeWorkers = worker; + dlist_push_head(&AutoVacuumShmem->av_freeWorkers, &worker->wi_links); AutoVacuumShmem->av_startingWorker = NULL; elog(WARNING, "worker took too long to start; canceled"); } @@ -738,20 +741,7 @@ AutoVacLauncherMain(int argc, char *argv[]) /* We're OK to start a new worker */ - elem = DLGetTail(DatabaseList); - if (elem != NULL) - { - avl_dbase *avdb = DLE_VAL(elem); - - /* - * launch a worker if next_worker is right now or it is in the - * past - */ - if (TimestampDifferenceExceeds(avdb->adl_next_worker, - current_time, 0)) - launch_worker(current_time); - } - else + if (dlist_is_empty(&DatabaseList)) { /* * Special case when the list is empty: start a worker right away. @@ -763,6 +753,23 @@ AutoVacLauncherMain(int argc, char *argv[]) */ launch_worker(current_time); } + else + { + /* + * because rebuild_database_list constructs a list with most + * distant adl_next_worker first, we obtain our database from the + * tail of the list. + */ + avdb = dlist_tail_element(avl_dbase, adl_node, &DatabaseList); + + /* + * launch a worker if next_worker is right now or it is in the + * past + */ + if (TimestampDifferenceExceeds(avdb->adl_next_worker, + current_time, 0)) + launch_worker(current_time); + } } /* Normal exit from the autovac launcher is here */ @@ -783,7 +790,7 @@ AutoVacLauncherMain(int argc, char *argv[]) static void launcher_determine_sleep(bool canlaunch, bool recursing, struct timeval * nap) { - Dlelem *elem; + avl_dbase *avdb; /* * We sleep until the next scheduled vacuum. We trust that when the @@ -796,14 +803,15 @@ launcher_determine_sleep(bool canlaunch, bool recursing, struct timeval * nap) nap->tv_sec = autovacuum_naptime; nap->tv_usec = 0; } - else if ((elem = DLGetTail(DatabaseList)) != NULL) + else if (!dlist_is_empty(&DatabaseList)) { - avl_dbase *avdb = DLE_VAL(elem); TimestampTz current_time = GetCurrentTimestamp(); TimestampTz next_wakeup; long secs; int usecs; + avdb = dlist_tail_element(avl_dbase, adl_node, &DatabaseList); + next_wakeup = avdb->adl_next_worker; TimestampDifference(current_time, next_wakeup, &secs, &usecs); @@ -867,6 +875,7 @@ rebuild_database_list(Oid newdb) int score; int nelems; HTAB *dbhash; + dlist_iter iter; /* use fresh stats */ autovac_refresh_stats(); @@ -927,36 +936,28 @@ rebuild_database_list(Oid newdb) } /* Now insert the databases from the existing list */ - if (DatabaseList != NULL) + dlist_foreach(iter, &DatabaseList) { - Dlelem *elem; + avl_dbase *avdb = dlist_container(avl_dbase, adl_node, iter.cur); + avl_dbase *db; + bool found; + PgStat_StatDBEntry *entry; - elem = DLGetHead(DatabaseList); - while (elem != NULL) + /* + * skip databases with no stat entries -- in particular, this gets + * rid of dropped databases + */ + entry = pgstat_fetch_stat_dbentry(avdb->adl_datid); + if (entry == NULL) + continue; + + db = hash_search(dbhash, &(avdb->adl_datid), HASH_ENTER, &found); + + if (!found) { - avl_dbase *avdb = DLE_VAL(elem); - avl_dbase *db; - bool found; - PgStat_StatDBEntry *entry; - - elem = DLGetSucc(elem); - - /* - * skip databases with no stat entries -- in particular, this gets - * rid of dropped databases - */ - entry = pgstat_fetch_stat_dbentry(avdb->adl_datid); - if (entry == NULL) - continue; - - db = hash_search(dbhash, &(avdb->adl_datid), HASH_ENTER, &found); - - if (!found) - { - /* hash_search already filled in the key */ - db->adl_score = score++; - /* next_worker is filled in later */ - } + /* hash_search already filled in the key */ + db->adl_score = score++; + /* next_worker is filled in later */ } } @@ -987,7 +988,7 @@ rebuild_database_list(Oid newdb) /* from here on, the allocated memory belongs to the new list */ MemoryContextSwitchTo(newcxt); - DatabaseList = DLNewList(); + dlist_init(&DatabaseList); if (nelems > 0) { @@ -1029,15 +1030,13 @@ rebuild_database_list(Oid newdb) for (i = 0; i < nelems; i++) { avl_dbase *db = &(dbary[i]); - Dlelem *elem; current_time = TimestampTzPlusMilliseconds(current_time, millis_increment); db->adl_next_worker = current_time; - elem = DLNewElem(db); /* later elements should go closer to the head of the list */ - DLAddHead(DatabaseList, elem); + dlist_push_head(&DatabaseList, &db->adl_node); } } @@ -1086,7 +1085,7 @@ do_start_worker(void) /* return quickly when there are no free workers */ LWLockAcquire(AutovacuumLock, LW_SHARED); - if (AutoVacuumShmem->av_freeWorkers == NULL) + if (dlist_is_empty(&AutoVacuumShmem->av_freeWorkers)) { LWLockRelease(AutovacuumLock); return InvalidOid; @@ -1147,7 +1146,7 @@ do_start_worker(void) foreach(cell, dblist) { avw_dbase *tmp = lfirst(cell); - Dlelem *elem; + dlist_iter iter; /* Check to see if this one is at risk of wraparound */ if (TransactionIdPrecedes(tmp->adw_frozenxid, xidForceLimit)) @@ -1179,11 +1178,10 @@ do_start_worker(void) * autovacuum time yet. */ skipit = false; - elem = DatabaseList ? DLGetTail(DatabaseList) : NULL; - while (elem != NULL) + dlist_reverse_foreach(iter, &DatabaseList) { - avl_dbase *dbp = DLE_VAL(elem); + avl_dbase *dbp = dlist_container(avl_dbase, adl_node, iter.cur); if (dbp->adl_datid == tmp->adw_datid) { @@ -1200,7 +1198,6 @@ do_start_worker(void) break; } - elem = DLGetPred(elem); } if (skipit) continue; @@ -1218,20 +1215,17 @@ do_start_worker(void) if (avdb != NULL) { WorkerInfo worker; + dlist_node *wptr; LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); /* * Get a worker entry from the freelist. We checked above, so there - * really should be a free slot -- complain very loudly if there - * isn't. + * really should be a free slot. */ - worker = AutoVacuumShmem->av_freeWorkers; - if (worker == NULL) - elog(FATAL, "no free worker found"); - - AutoVacuumShmem->av_freeWorkers = (WorkerInfo) worker->wi_links.next; + wptr = dlist_pop_head_node(&AutoVacuumShmem->av_freeWorkers); + worker = dlist_container(WorkerInfoData, wi_links, wptr); worker->wi_dboid = avdb->adw_datid; worker->wi_proc = NULL; worker->wi_launchtime = GetCurrentTimestamp(); @@ -1274,22 +1268,25 @@ static void launch_worker(TimestampTz now) { Oid dbid; - Dlelem *elem; + dlist_iter iter; dbid = do_start_worker(); if (OidIsValid(dbid)) { + bool found = false; + /* * Walk the database list and update the corresponding entry. If the * database is not on the list, we'll recreate the list. */ - elem = (DatabaseList == NULL) ? NULL : DLGetHead(DatabaseList); - while (elem != NULL) + dlist_foreach(iter, &DatabaseList) { - avl_dbase *avdb = DLE_VAL(elem); + avl_dbase *avdb = dlist_container(avl_dbase, adl_node, iter.cur); if (avdb->adl_datid == dbid) { + found = true; + /* * add autovacuum_naptime seconds to the current time, and use * that as the new "next_worker" field for this database. @@ -1297,10 +1294,9 @@ launch_worker(TimestampTz now) avdb->adl_next_worker = TimestampTzPlusMilliseconds(now, autovacuum_naptime * 1000); - DLMoveToFront(elem); + dlist_move_head(&DatabaseList, iter.cur); break; } - elem = DLGetSucc(elem); } /* @@ -1310,7 +1306,7 @@ launch_worker(TimestampTz now) * pgstat entry, but this is not a problem because we don't want to * schedule workers regularly into those in any case. */ - if (elem == NULL) + if (!found) rebuild_database_list(dbid); } } @@ -1590,8 +1586,8 @@ AutoVacWorkerMain(int argc, char *argv[]) MyWorkerInfo->wi_proc = MyProc; /* insert into the running list */ - SHMQueueInsertBefore(&AutoVacuumShmem->av_runningWorkers, - &MyWorkerInfo->wi_links); + dlist_push_head(&AutoVacuumShmem->av_runningWorkers, + &MyWorkerInfo->wi_links); /* * remove from the "starting" pointer, so that the launcher can start @@ -1681,8 +1677,7 @@ FreeWorkerInfo(int code, Datum arg) */ AutovacuumLauncherPid = AutoVacuumShmem->av_launcherpid; - SHMQueueDelete(&MyWorkerInfo->wi_links); - MyWorkerInfo->wi_links.next = (SHM_QUEUE *) AutoVacuumShmem->av_freeWorkers; + dlist_delete(&AutoVacuumShmem->av_runningWorkers, &MyWorkerInfo->wi_links); MyWorkerInfo->wi_dboid = InvalidOid; MyWorkerInfo->wi_tableoid = InvalidOid; MyWorkerInfo->wi_proc = NULL; @@ -1690,7 +1685,7 @@ FreeWorkerInfo(int code, Datum arg) MyWorkerInfo->wi_cost_delay = 0; MyWorkerInfo->wi_cost_limit = 0; MyWorkerInfo->wi_cost_limit_base = 0; - AutoVacuumShmem->av_freeWorkers = MyWorkerInfo; + dlist_push_head(&AutoVacuumShmem->av_freeWorkers, &MyWorkerInfo->wi_links); /* not mine anymore */ MyWorkerInfo = NULL; @@ -1740,7 +1735,7 @@ autovac_balance_cost(void) autovacuum_vac_cost_delay : VacuumCostDelay); double cost_total; double cost_avail; - WorkerInfo worker; + dlist_iter iter; /* not set? nothing to do */ if (vac_cost_limit <= 0 || vac_cost_delay <= 0) @@ -1748,19 +1743,14 @@ autovac_balance_cost(void) /* caculate the total base cost limit of active workers */ cost_total = 0.0; - worker = (WorkerInfo) SHMQueueNext(&AutoVacuumShmem->av_runningWorkers, - &AutoVacuumShmem->av_runningWorkers, - offsetof(WorkerInfoData, wi_links)); - while (worker) + dlist_foreach(iter, &AutoVacuumShmem->av_runningWorkers) { + WorkerInfo worker = dlist_container(WorkerInfoData, wi_links, iter.cur); + if (worker->wi_proc != NULL && worker->wi_cost_limit_base > 0 && worker->wi_cost_delay > 0) cost_total += (double) worker->wi_cost_limit_base / worker->wi_cost_delay; - - worker = (WorkerInfo) SHMQueueNext(&AutoVacuumShmem->av_runningWorkers, - &worker->wi_links, - offsetof(WorkerInfoData, wi_links)); } /* there are no cost limits -- nothing to do */ if (cost_total <= 0) @@ -1771,11 +1761,10 @@ autovac_balance_cost(void) * limit to autovacuum_vacuum_cost_limit. */ cost_avail = (double) vac_cost_limit / vac_cost_delay; - worker = (WorkerInfo) SHMQueueNext(&AutoVacuumShmem->av_runningWorkers, - &AutoVacuumShmem->av_runningWorkers, - offsetof(WorkerInfoData, wi_links)); - while (worker) + dlist_foreach(iter, &AutoVacuumShmem->av_runningWorkers) { + WorkerInfo worker = dlist_container(WorkerInfoData, wi_links, iter.cur); + if (worker->wi_proc != NULL && worker->wi_cost_limit_base > 0 && worker->wi_cost_delay > 0) { @@ -1797,10 +1786,6 @@ autovac_balance_cost(void) worker->wi_cost_limit, worker->wi_cost_limit_base, worker->wi_cost_delay); } - - worker = (WorkerInfo) SHMQueueNext(&AutoVacuumShmem->av_runningWorkers, - &worker->wi_links, - offsetof(WorkerInfoData, wi_links)); } } @@ -2177,10 +2162,10 @@ do_autovacuum(void) { Oid relid = lfirst_oid(cell); autovac_table *tab; - WorkerInfo worker; bool skipit; int stdVacuumCostDelay; int stdVacuumCostLimit; + dlist_iter iter; CHECK_FOR_INTERRUPTS(); @@ -2197,29 +2182,23 @@ do_autovacuum(void) * worker. */ skipit = false; - worker = (WorkerInfo) SHMQueueNext(&AutoVacuumShmem->av_runningWorkers, - &AutoVacuumShmem->av_runningWorkers, - offsetof(WorkerInfoData, wi_links)); - while (worker) + dlist_foreach(iter, &AutoVacuumShmem->av_runningWorkers) { + WorkerInfo worker = dlist_container(WorkerInfoData, wi_links, iter.cur); + /* ignore myself */ if (worker == MyWorkerInfo) - goto next_worker; + continue; /* ignore workers in other databases */ if (worker->wi_dboid != MyDatabaseId) - goto next_worker; + continue; if (worker->wi_tableoid == relid) { skipit = true; break; } - - next_worker: - worker = (WorkerInfo) SHMQueueNext(&AutoVacuumShmem->av_runningWorkers, - &worker->wi_links, - offsetof(WorkerInfoData, wi_links)); } LWLockRelease(AutovacuumLock); if (skipit) @@ -2875,8 +2854,8 @@ AutoVacuumShmemInit(void) Assert(!found); AutoVacuumShmem->av_launcherpid = 0; - AutoVacuumShmem->av_freeWorkers = NULL; - SHMQueueInit(&AutoVacuumShmem->av_runningWorkers); + dlist_init(&AutoVacuumShmem->av_freeWorkers); + dlist_init(&AutoVacuumShmem->av_runningWorkers); AutoVacuumShmem->av_startingWorker = NULL; worker = (WorkerInfo) ((char *) AutoVacuumShmem + @@ -2884,10 +2863,7 @@ AutoVacuumShmemInit(void) /* initialize the WorkerInfo free list */ for (i = 0; i < autovacuum_max_workers; i++) - { - worker[i].wi_links.next = (SHM_QUEUE *) AutoVacuumShmem->av_freeWorkers; - AutoVacuumShmem->av_freeWorkers = &worker[i]; - } + dlist_push_head(&AutoVacuumShmem->av_freeWorkers, &worker[i].wi_links); } else Assert(found); diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index dfe40492d27..c8a80f038c4 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -95,7 +95,7 @@ #include "access/xlog.h" #include "bootstrap/bootstrap.h" #include "catalog/pg_control.h" -#include "lib/dllist.h" +#include "lib/ilist.h" #include "libpq/auth.h" #include "libpq/ip.h" #include "libpq/libpq.h" @@ -146,10 +146,10 @@ typedef struct bkend int child_slot; /* PMChildSlot for this backend, if any */ bool is_autovacuum; /* is it an autovacuum process? */ bool dead_end; /* is it going to send an error and quit? */ - Dlelem elem; /* list link in BackendList */ + dlist_node elem; /* list link in BackendList */ } Backend; -static Dllist *BackendList; +static dlist_head BackendList = DLIST_STATIC_INIT(BackendList); #ifdef EXEC_BACKEND static Backend *ShmemBackendArray; @@ -1027,11 +1027,6 @@ PostmasterMain(int argc, char *argv[]) */ set_stack_base(); - /* - * Initialize the list of active backends. - */ - BackendList = DLNewList(); - /* * Initialize pipe (or process handle on Windows) that allows children to * wake up from sleep on postmaster death. @@ -1872,7 +1867,7 @@ processCancelRequest(Port *port, void *pkt) Backend *bp; #ifndef EXEC_BACKEND - Dlelem *curr; + dlist_iter iter; #else int i; #endif @@ -1886,9 +1881,9 @@ processCancelRequest(Port *port, void *pkt) * duplicate array in shared memory. */ #ifndef EXEC_BACKEND - for (curr = DLGetHead(BackendList); curr; curr = DLGetSucc(curr)) + dlist_foreach(iter, &BackendList) { - bp = (Backend *) DLE_VAL(curr); + bp = dlist_container(Backend, elem, iter.cur); #else for (i = MaxLivePostmasterChildren() - 1; i >= 0; i--) { @@ -2648,7 +2643,7 @@ static void CleanupBackend(int pid, int exitstatus) /* child's exit status. */ { - Dlelem *curr; + dlist_mutable_iter iter; LogChildExit(DEBUG2, _("server process"), pid, exitstatus); @@ -2680,9 +2675,9 @@ CleanupBackend(int pid, return; } - for (curr = DLGetHead(BackendList); curr; curr = DLGetSucc(curr)) + dlist_foreach_modify(iter, &BackendList) { - Backend *bp = (Backend *) DLE_VAL(curr); + Backend *bp = dlist_container(Backend, elem, iter.cur); if (bp->pid == pid) { @@ -2701,7 +2696,7 @@ CleanupBackend(int pid, ShmemBackendArrayRemove(bp); #endif } - DLRemove(curr); + dlist_delete(&BackendList, iter.cur); free(bp); break; } @@ -2718,8 +2713,7 @@ CleanupBackend(int pid, static void HandleChildCrash(int pid, int exitstatus, const char *procname) { - Dlelem *curr, - *next; + dlist_mutable_iter iter; Backend *bp; /* @@ -2734,10 +2728,10 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) } /* Process regular backends */ - for (curr = DLGetHead(BackendList); curr; curr = next) + dlist_foreach_modify(iter, &BackendList) { - next = DLGetSucc(curr); - bp = (Backend *) DLE_VAL(curr); + bp = dlist_container(Backend, elem, iter.cur); + if (bp->pid == pid) { /* @@ -2750,7 +2744,7 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) ShmemBackendArrayRemove(bp); #endif } - DLRemove(curr); + dlist_delete(&BackendList, iter.cur); free(bp); /* Keep looping so we can signal remaining backends */ } @@ -3113,7 +3107,7 @@ PostmasterStateMachine(void) * normal state transition leading up to PM_WAIT_DEAD_END, or during * FatalError processing. */ - if (DLGetHead(BackendList) == NULL && + if (dlist_is_empty(&BackendList) && PgArchPID == 0 && PgStatPID == 0) { /* These other guys should be dead already */ @@ -3239,12 +3233,12 @@ signal_child(pid_t pid, int signal) static bool SignalSomeChildren(int signal, int target) { - Dlelem *curr; + dlist_iter iter; bool signaled = false; - for (curr = DLGetHead(BackendList); curr; curr = DLGetSucc(curr)) + dlist_foreach(iter, &BackendList) { - Backend *bp = (Backend *) DLE_VAL(curr); + Backend *bp = dlist_container(Backend, elem, iter.cur); if (bp->dead_end) continue; @@ -3382,8 +3376,8 @@ BackendStartup(Port *port) */ bn->pid = pid; bn->is_autovacuum = false; - DLInitElem(&bn->elem, bn); - DLAddHead(BackendList, &bn->elem); + dlist_push_head(&BackendList, &bn->elem); + #ifdef EXEC_BACKEND if (!bn->dead_end) ShmemBackendArrayAdd(bn); @@ -4491,12 +4485,12 @@ PostmasterRandom(void) static int CountChildren(int target) { - Dlelem *curr; + dlist_iter iter; int cnt = 0; - for (curr = DLGetHead(BackendList); curr; curr = DLGetSucc(curr)) + dlist_foreach(iter, &BackendList) { - Backend *bp = (Backend *) DLE_VAL(curr); + Backend *bp = dlist_container(Backend, elem, iter.cur); if (bp->dead_end) continue; @@ -4675,8 +4669,7 @@ StartAutovacuumWorker(void) if (bn->pid > 0) { bn->is_autovacuum = true; - DLInitElem(&bn->elem, bn); - DLAddHead(BackendList, &bn->elem); + dlist_push_head(&BackendList, &bn->elem); #ifdef EXEC_BACKEND ShmemBackendArrayAdd(bn); #endif diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index d6f6b1c0de4..05ceff51cdf 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -291,7 +291,7 @@ CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple) static void CatCachePrintStats(int code, Datum arg) { - CatCache *cache; + slist_iter iter; long cc_searches = 0; long cc_hits = 0; long cc_neg_hits = 0; @@ -300,8 +300,10 @@ CatCachePrintStats(int code, Datum arg) long cc_lsearches = 0; long cc_lhits = 0; - for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next) + slist_foreach(iter, &CacheHdr->ch_caches) { + CatCache *cache = slist_container(CatCache, cc_next, iter.cur); + if (cache->cc_ntup == 0 && cache->cc_searches == 0) continue; /* don't print unused caches */ elog(DEBUG2, "catcache %s/%u: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lsrch, %ld lhits", @@ -368,8 +370,7 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) return; /* nothing left to do */ } - /* delink from linked list */ - DLRemove(&ct->cache_elem); + dlist_delete(ct->cache_bucket, &ct->cache_elem); /* free associated tuple data */ if (ct->tuple.t_data != NULL) @@ -412,7 +413,7 @@ CatCacheRemoveCList(CatCache *cache, CatCList *cl) } /* delink from linked list */ - DLRemove(&cl->cache_elem); + dlist_delete(&cache->cc_lists, &cl->cache_elem); /* free associated tuple data */ if (cl->tuple.t_data != NULL) @@ -442,18 +443,18 @@ CatCacheRemoveCList(CatCache *cache, CatCList *cl) void CatalogCacheIdInvalidate(int cacheId, uint32 hashValue) { - CatCache *ccp; + slist_iter cache_iter; CACHE1_elog(DEBUG2, "CatalogCacheIdInvalidate: called"); /* * inspect caches to find the proper cache */ - for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next) + slist_foreach(cache_iter, &CacheHdr->ch_caches) { Index hashIndex; - Dlelem *elt, - *nextelt; + dlist_mutable_iter iter; + CatCache *ccp = slist_container(CatCache, cc_next, cache_iter.cur); if (cacheId != ccp->id) continue; @@ -468,11 +469,9 @@ CatalogCacheIdInvalidate(int cacheId, uint32 hashValue) * Invalidate *all* CatCLists in this cache; it's too hard to tell * which searches might still be correct, so just zap 'em all. */ - for (elt = DLGetHead(&ccp->cc_lists); elt; elt = nextelt) + dlist_foreach_modify(iter, &ccp->cc_lists) { - CatCList *cl = (CatCList *) DLE_VAL(elt); - - nextelt = DLGetSucc(elt); + CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur); if (cl->refcount > 0) cl->dead = true; @@ -484,12 +483,9 @@ CatalogCacheIdInvalidate(int cacheId, uint32 hashValue) * inspect the proper hash bucket for tuple matches */ hashIndex = HASH_INDEX(hashValue, ccp->cc_nbuckets); - - for (elt = DLGetHead(&ccp->cc_bucket[hashIndex]); elt; elt = nextelt) + dlist_foreach_modify(iter, &ccp->cc_bucket[hashIndex]) { - CatCTup *ct = (CatCTup *) DLE_VAL(elt); - - nextelt = DLGetSucc(elt); + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); if (hashValue == ct->hash_value) { @@ -557,17 +553,18 @@ AtEOXact_CatCache(bool isCommit) #ifdef USE_ASSERT_CHECKING if (assert_enabled) { - CatCache *ccp; + slist_iter cache_iter; - for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next) + slist_foreach(cache_iter, &(CacheHdr->ch_caches)) { - Dlelem *elt; + CatCache *ccp = slist_container(CatCache, cc_next, cache_iter.cur); + dlist_iter iter; int i; /* Check CatCLists */ - for (elt = DLGetHead(&ccp->cc_lists); elt; elt = DLGetSucc(elt)) + dlist_foreach(iter, &ccp->cc_lists) { - CatCList *cl = (CatCList *) DLE_VAL(elt); + CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur); Assert(cl->cl_magic == CL_MAGIC); Assert(cl->refcount == 0); @@ -577,11 +574,11 @@ AtEOXact_CatCache(bool isCommit) /* Check individual tuples */ for (i = 0; i < ccp->cc_nbuckets; i++) { - for (elt = DLGetHead(&ccp->cc_bucket[i]); - elt; - elt = DLGetSucc(elt)) + dlist_head *bucket = &ccp->cc_bucket[i]; + + dlist_foreach(iter, bucket) { - CatCTup *ct = (CatCTup *) DLE_VAL(elt); + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); Assert(ct->ct_magic == CT_MAGIC); Assert(ct->refcount == 0); @@ -604,16 +601,13 @@ AtEOXact_CatCache(bool isCommit) static void ResetCatalogCache(CatCache *cache) { - Dlelem *elt, - *nextelt; + dlist_mutable_iter iter; int i; /* Remove each list in this cache, or at least mark it dead */ - for (elt = DLGetHead(&cache->cc_lists); elt; elt = nextelt) + dlist_foreach_modify(iter, &cache->cc_lists) { - CatCList *cl = (CatCList *) DLE_VAL(elt); - - nextelt = DLGetSucc(elt); + CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur); if (cl->refcount > 0) cl->dead = true; @@ -624,11 +618,11 @@ ResetCatalogCache(CatCache *cache) /* Remove each tuple in this cache, or at least mark it dead */ for (i = 0; i < cache->cc_nbuckets; i++) { - for (elt = DLGetHead(&cache->cc_bucket[i]); elt; elt = nextelt) - { - CatCTup *ct = (CatCTup *) DLE_VAL(elt); + dlist_head *bucket = &cache->cc_bucket[i]; - nextelt = DLGetSucc(elt); + dlist_foreach_modify(iter, bucket) + { + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); if (ct->refcount > 0 || (ct->c_list && ct->c_list->refcount > 0)) @@ -654,12 +648,16 @@ ResetCatalogCache(CatCache *cache) void ResetCatalogCaches(void) { - CatCache *cache; + slist_iter iter; CACHE1_elog(DEBUG2, "ResetCatalogCaches called"); - for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next) + slist_foreach(iter, &CacheHdr->ch_caches) + { + CatCache *cache = slist_container(CatCache, cc_next, iter.cur); + ResetCatalogCache(cache); + } CACHE1_elog(DEBUG2, "end of ResetCatalogCaches call"); } @@ -680,12 +678,14 @@ ResetCatalogCaches(void) void CatalogCacheFlushCatalog(Oid catId) { - CatCache *cache; + slist_iter iter; CACHE2_elog(DEBUG2, "CatalogCacheFlushCatalog called for %u", catId); - for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next) + slist_foreach(iter, &(CacheHdr->ch_caches)) { + CatCache *cache = slist_container(CatCache, cc_next, iter.cur); + /* Does this cache store tuples of the target catalog? */ if (cache->cc_reloid == catId) { @@ -760,7 +760,7 @@ InitCatCache(int id, if (CacheHdr == NULL) { CacheHdr = (CatCacheHeader *) palloc(sizeof(CatCacheHeader)); - CacheHdr->ch_caches = NULL; + slist_init(&CacheHdr->ch_caches); CacheHdr->ch_ntup = 0; #ifdef CATCACHE_STATS /* set up to dump stats at backend exit */ @@ -770,10 +770,8 @@ InitCatCache(int id, /* * allocate a new cache structure - * - * Note: we assume zeroing initializes the Dllist headers correctly */ - cp = (CatCache *) palloc0(sizeof(CatCache) + nbuckets * sizeof(Dllist)); + cp = (CatCache *) palloc0(sizeof(CatCache) + nbuckets * sizeof(dlist_node)); /* * initialize the cache's relation information for the relation @@ -792,6 +790,9 @@ InitCatCache(int id, for (i = 0; i < nkeys; ++i) cp->cc_key[i] = key[i]; + dlist_init(&cp->cc_lists); + MemSet(&cp->cc_bucket, 0, nbuckets * sizeof(dlist_head)); + /* * new cache is initialized as far as we can go for now. print some * debugging information, if appropriate. @@ -801,8 +802,7 @@ InitCatCache(int id, /* * add completed cache to top of group header's list */ - cp->cc_next = CacheHdr->ch_caches; - CacheHdr->ch_caches = cp; + slist_push_head(&CacheHdr->ch_caches, &cp->cc_next); /* * back to the old context before we return... @@ -1060,7 +1060,8 @@ SearchCatCache(CatCache *cache, ScanKeyData cur_skey[CATCACHE_MAXKEYS]; uint32 hashValue; Index hashIndex; - Dlelem *elt; + dlist_mutable_iter iter; + dlist_head *bucket; CatCTup *ct; Relation relation; SysScanDesc scandesc; @@ -1094,13 +1095,13 @@ SearchCatCache(CatCache *cache, /* * scan the hash bucket until we find a match or exhaust our tuples */ - for (elt = DLGetHead(&cache->cc_bucket[hashIndex]); - elt; - elt = DLGetSucc(elt)) + bucket = &cache->cc_bucket[hashIndex]; + + dlist_foreach_modify(iter, bucket) { bool res; - ct = (CatCTup *) DLE_VAL(elt); + ct = dlist_container(CatCTup, cache_elem, iter.cur); if (ct->dead) continue; /* ignore dead entries */ @@ -1125,7 +1126,7 @@ SearchCatCache(CatCache *cache, * most frequently accessed elements in any hashbucket will tend to be * near the front of the hashbucket's list.) */ - DLMoveToFront(&ct->cache_elem); + dlist_move_head(bucket, &ct->cache_elem); /* * If it's a positive entry, bump its refcount and return it. If it's @@ -1340,7 +1341,7 @@ SearchCatCacheList(CatCache *cache, { ScanKeyData cur_skey[CATCACHE_MAXKEYS]; uint32 lHashValue; - Dlelem *elt; + dlist_iter iter; CatCList *cl; CatCTup *ct; List *volatile ctlist; @@ -1382,13 +1383,11 @@ SearchCatCacheList(CatCache *cache, /* * scan the items until we find a match or exhaust our list */ - for (elt = DLGetHead(&cache->cc_lists); - elt; - elt = DLGetSucc(elt)) + dlist_foreach(iter, &cache->cc_lists) { bool res; - cl = (CatCList *) DLE_VAL(elt); + cl = dlist_container(CatCList, cache_elem, iter.cur); if (cl->dead) continue; /* ignore dead entries */ @@ -1416,7 +1415,7 @@ SearchCatCacheList(CatCache *cache, * since there's no point in that unless they are searched for * individually.) */ - DLMoveToFront(&cl->cache_elem); + dlist_move_head(&cache->cc_lists, &cl->cache_elem); /* Bump the list's refcount and return it */ ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); @@ -1468,6 +1467,8 @@ SearchCatCacheList(CatCache *cache, { uint32 hashValue; Index hashIndex; + bool found = false; + dlist_head *bucket; /* * See if there's an entry for this tuple already. @@ -1476,11 +1477,10 @@ SearchCatCacheList(CatCache *cache, hashValue = CatalogCacheComputeTupleHashValue(cache, ntp); hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); - for (elt = DLGetHead(&cache->cc_bucket[hashIndex]); - elt; - elt = DLGetSucc(elt)) + bucket = &cache->cc_bucket[hashIndex]; + dlist_foreach(iter, bucket) { - ct = (CatCTup *) DLE_VAL(elt); + ct = dlist_container(CatCTup, cache_elem, iter.cur); if (ct->dead || ct->negative) continue; /* ignore dead and negative entries */ @@ -1498,10 +1498,11 @@ SearchCatCacheList(CatCache *cache, if (ct->c_list) continue; + found = true; break; /* A-OK */ } - if (elt == NULL) + if (!found) { /* We didn't find a usable entry, so make a new one */ ct = CatalogCacheCreateEntry(cache, ntp, @@ -1564,7 +1565,6 @@ SearchCatCacheList(CatCache *cache, cl->cl_magic = CL_MAGIC; cl->my_cache = cache; - DLInitElem(&cl->cache_elem, cl); cl->refcount = 0; /* for the moment */ cl->dead = false; cl->ordered = ordered; @@ -1587,7 +1587,7 @@ SearchCatCacheList(CatCache *cache, } Assert(i == nmembers); - DLAddHead(&cache->cc_lists, &cl->cache_elem); + dlist_push_head(&cache->cc_lists, &cl->cache_elem); /* Finally, bump the list's refcount and return it */ cl->refcount++; @@ -1664,14 +1664,15 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, */ ct->ct_magic = CT_MAGIC; ct->my_cache = cache; - DLInitElem(&ct->cache_elem, (void *) ct); + ct->cache_bucket = &cache->cc_bucket[hashIndex]; + ct->c_list = NULL; ct->refcount = 0; /* for the moment */ ct->dead = false; ct->negative = negative; ct->hash_value = hashValue; - DLAddHead(&cache->cc_bucket[hashIndex], &ct->cache_elem); + dlist_push_head(ct->cache_bucket, &ct->cache_elem); cache->cc_ntup++; CacheHdr->ch_ntup++; @@ -1785,7 +1786,7 @@ PrepareToInvalidateCacheTuple(Relation relation, HeapTuple newtuple, void (*function) (int, uint32, Oid)) { - CatCache *ccp; + slist_iter iter; Oid reloid; CACHE1_elog(DEBUG2, "PrepareToInvalidateCacheTuple: called"); @@ -1808,10 +1809,11 @@ PrepareToInvalidateCacheTuple(Relation relation, * ---------------- */ - for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next) + slist_foreach(iter, &(CacheHdr->ch_caches)) { uint32 hashvalue; Oid dbid; + CatCache *ccp = slist_container(CatCache, cc_next, iter.cur); if (ccp->cc_reloid != reloid) continue; diff --git a/src/include/lib/dllist.h b/src/include/lib/dllist.h deleted file mode 100644 index 25ed64c7c4e..00000000000 --- a/src/include/lib/dllist.h +++ /dev/null @@ -1,85 +0,0 @@ -/*------------------------------------------------------------------------- - * - * dllist.h - * simple doubly linked list primitives - * the elements of the list are void* so the lists can contain anything - * Dlelem can only be in one list at a time - * - * - * Here's a small example of how to use Dllists: - * - * Dllist *lst; - * Dlelem *elt; - * void *in_stuff; -- stuff to stick in the list - * void *out_stuff - * - * lst = DLNewList(); -- make a new dllist - * DLAddHead(lst, DLNewElem(in_stuff)); -- add a new element to the list - * with in_stuff as the value - * ... - * elt = DLGetHead(lst); -- retrieve the head element - * out_stuff = (void*)DLE_VAL(elt); -- get the stuff out - * DLRemove(elt); -- removes the element from its list - * DLFreeElem(elt); -- free the element since we don't - * use it anymore - * - * - * It is also possible to use Dllist objects that are embedded in larger - * structures instead of being separately malloc'd. To do this, use - * DLInitElem() to initialize a Dllist field within a larger object. - * Don't forget to DLRemove() each field from its list (if any) before - * freeing the larger object! - * - * - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * src/include/lib/dllist.h - * - *------------------------------------------------------------------------- - */ - -#ifndef DLLIST_H -#define DLLIST_H - -struct Dllist; -struct Dlelem; - -typedef struct Dlelem -{ - struct Dlelem *dle_next; /* next element */ - struct Dlelem *dle_prev; /* previous element */ - void *dle_val; /* value of the element */ - struct Dllist *dle_list; /* what list this element is in */ -} Dlelem; - -typedef struct Dllist -{ - Dlelem *dll_head; - Dlelem *dll_tail; -} Dllist; - -extern Dllist *DLNewList(void); /* allocate and initialize a list header */ -extern void DLInitList(Dllist *list); /* init a header alloced by caller */ -extern void DLFreeList(Dllist *list); /* free up a list and all the nodes in - * it */ -extern Dlelem *DLNewElem(void *val); -extern void DLInitElem(Dlelem *e, void *val); -extern void DLFreeElem(Dlelem *e); -extern void DLRemove(Dlelem *e); /* removes node from list */ -extern void DLAddHead(Dllist *list, Dlelem *node); -extern void DLAddTail(Dllist *list, Dlelem *node); -extern Dlelem *DLRemHead(Dllist *list); /* remove and return the head */ -extern Dlelem *DLRemTail(Dllist *list); -extern void DLMoveToFront(Dlelem *e); /* move node to front of its list */ - -/* These are macros for speed */ -#define DLGetHead(list) ((list)->dll_head) -#define DLGetTail(list) ((list)->dll_tail) -#define DLGetSucc(elem) ((elem)->dle_next) -#define DLGetPred(elem) ((elem)->dle_prev) -#define DLGetListHdr(elem) ((elem)->dle_list) - -#define DLE_VAL(elem) ((elem)->dle_val) - -#endif /* DLLIST_H */ diff --git a/src/include/lib/ilist.h b/src/include/lib/ilist.h new file mode 100644 index 00000000000..54514423358 --- /dev/null +++ b/src/include/lib/ilist.h @@ -0,0 +1,767 @@ +/*------------------------------------------------------------------------- + * + * ilist.h + * integrated/inline doubly- and singly-linked lists + * + * This implementation is as efficient as possible: the lists don't have + * any memory management overhead, because the list pointers are embedded + * within some larger structure. + * + * There are two kinds of empty doubly linked lists: those that have been + * initialized to NULL, and those that have been initialized to circularity. + * The second kind is useful for tight optimization, because there are some + * operations that can be done without branches (and thus faster) on lists that + * have been initialized to circularity. Most users don't care all that much, + * and so can skip the initialization step until really required. + * + * NOTES + * This is intended to be used in situations where memory for a struct and + * its contents already needs to be allocated and the overhead of + * allocating extra list cells for every list element is noticeable. Thus, + * none of the functions here allocate any memory; they just manipulate + * externally managed memory. The API for singly/doubly linked lists is + * identical as far as capabilities of both allow. + * + * EXAMPLES + * + * Here's a simple example demonstrating how this can be used. Let's assume we + * want to store information about the tables contained in a database. + * + * #include "lib/ilist.h" + * + * // Define struct for the databases including a list header that will be used + * // to access the nodes in the list later on. + * typedef struct my_database + * { + * char *datname; + * dlist_head tables; + * // ... + * } my_database; + * + * // Define struct for the tables. Note the list_node element which stores + * // information about prev/next list nodes. + * typedef struct my_table + * { + * char *tablename; + * dlist_node list_node; + * perm_t permissions; + * // ... + * } my_table; + * + * // create a database + * my_database *db = create_database(); + * + * // and add a few tables to its table list + * dlist_push_head(&db->tables, &create_table(db, "a")->list_node); + * ... + * dlist_push_head(&db->tables, &create_table(db, "b")->list_node); + * + * + * To iterate over the table list, we allocate an iterator element and use + * a specialized looping construct. Inside a dlist_foreach, the iterator's + * 'cur' field can be used to access the current element. iter.cur points to a + * 'dlist_node', but most of the time what we want is the actual table + * information; dlist_container() gives us that, like so: + * + * dlist_iter iter; + * dlist_foreach(iter, &db->tables) + * { + * my_table *tbl = dlist_container(my_table, list_node, iter.cur); + * printf("we have a table: %s in database %s\n", + * tbl->tablename, db->datname); + * } + * + * + * While a simple iteration is useful, we sometimes also want to manipulate + * the list while iterating. There is a different iterator element and looping + * construct for that. Suppose we want to delete tables that meet a certain + * criterion: + * + * dlist_mutable_iter miter; + * dlist_foreach_modify(miter, &db->tables) + * { + * my_table *tbl = dlist_container(my_table, list_node, miter.cur); + * + * if (!tbl->to_be_deleted) + * continue; // don't touch this one + * + * // unlink the current table from the linked list + * dlist_delete(&db->tables, miter.cur); + * // as these lists never manage memory, we can freely access the table + * // after it's been deleted + * drop_table(db, tbl); + * } + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/include/lib/ilist.h + *------------------------------------------------------------------------- + */ +#ifndef ILIST_H +#define ILIST_H + +/* + * Enable for extra debugging. This is rather expensive, so it's not enabled by + * default even when USE_ASSERT_CHECKING. + */ +/* #define ILIST_DEBUG */ + +/* + * Node of a doubly linked list. + * + * Embed this in structs that need to be part of a doubly linked list. + */ +typedef struct dlist_node dlist_node; +struct dlist_node +{ + dlist_node *prev; + dlist_node *next; +}; + +/* + * Head of a doubly linked list. + * + * Non-empty lists are internally circularly linked. Circular lists have the + * advantage of not needing any branches in the most common list manipulations. + * An empty list can also be represented as a pair of NULL pointers, making + * initialization easier. + */ +typedef struct dlist_head +{ + /* + * head->next either points to the first element of the list; to &head if + * it's a circular empty list; or to NULL if empty and not circular. + * + * head->prev either points to the last element of the list; to &head if + * it's a circular empty list; or to NULL if empty and not circular. + */ + dlist_node head; +} dlist_head; + + +/* + * Doubly linked list iterator. + * + * Used as state in dlist_foreach() and dlist_reverse_foreach(). 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 type for this to make it possible to avoid multiple + * evaluations of arguments in the dlist_foreach() macro. + */ +typedef struct dlist_iter +{ + dlist_node *end; /* last node we iterate to */ + dlist_node *cur; /* current element */ +} dlist_iter; + +/* + * Doubly linked list iterator allowing some modifications while iterating + * + * Used as state in dlist_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* + * fine to modify other nodes. + * + * NB: We need a separate type for mutable iterations to avoid having to pass + * in two iterators or some other state variable as we need to store the + * '->next' node of the current node so it can be deleted or modified by the + * user. + */ +typedef struct dlist_mutable_iter +{ + dlist_node *end; /* last node we iterate to */ + dlist_node *cur; /* current element */ + dlist_node *next; /* next node we iterate to, so we can delete + * cur */ +} dlist_mutable_iter; + +/* + * Node of a singly linked list. + * + * Embed this in structs that need to be part of a singly linked list. + */ +typedef struct slist_node slist_node; +struct slist_node +{ + slist_node *next; +}; + +/* + * Head of a singly linked list. + * + * Singly linked lists are not circularly linked, in contrast to doubly linked + * lists. As no pointer to the last list element and to the previous node needs + * to be maintained this doesn't incur any additional branches in the usual + * manipulations. + */ +typedef struct slist_head +{ + slist_node head; +} slist_head; + +/* + * Singly linked list iterator + * + * Used in slist_foreach(). To get the current element of the iteration use the + * 'cur' member. + * + * Do *not* manipulate the list while iterating! + * + * NB: this wouldn't really need to be an extra struct, we could use a + * slist_node * directly. We still use a separate type for consistency. + */ +typedef struct slist_iter +{ + slist_node *cur; +} slist_iter; + +/* + * Singly linked list iterator allowing some modifications while iterating + * + * Used in slist_foreach_modify. + * + * Iterations using this are allowed to remove the current node and to add more + * nodes to the beginning of the list. + */ +typedef struct slist_mutable_iter +{ + slist_node *cur; + slist_node *next; +} slist_mutable_iter; + + +/* Prototypes for functions too big to be inline */ + +/* Attention: O(n) */ +extern void slist_delete(slist_head *head, slist_node *node); + +#ifdef ILIST_DEBUG +extern void dlist_check(dlist_head *head); +extern void slist_check(slist_head *head); +#else +/* + * These seemingly useless casts to void are here to keep the compiler quiet + * about the argument being unused in many functions in a non-debug compile, + * in which functions the only point of passing the list head pointer is to be + * able to run these checks. + */ +#define dlist_check(head) (void) (head) +#define slist_check(head) (void) (head) +#endif /* ILIST_DEBUG */ + +/* Static initializers */ +#define DLIST_STATIC_INIT(name) {{&name.head, &name.head}} +#define SLIST_STATIC_INIT(name) {{NULL}} + + +/* + * We want the functions below to be inline; but if the compiler doesn't + * support that, fall back on providing them as regular functions. See + * STATIC_IF_INLINE in c.h. + */ +#ifndef PG_USE_INLINE +extern void dlist_init(dlist_head *head); +extern bool dlist_is_empty(dlist_head *head); +extern void dlist_push_head(dlist_head *head, dlist_node *node); +extern void dlist_push_tail(dlist_head *head, dlist_node *node); +extern void dlist_insert_after(dlist_head *head, + dlist_node *after, dlist_node *node); +extern void dlist_insert_before(dlist_head *head, + dlist_node *before, dlist_node *node); +extern void dlist_delete(dlist_head *head, dlist_node *node); +extern dlist_node *dlist_pop_head_node(dlist_head *head); +extern void dlist_move_head(dlist_head *head, dlist_node *node); +extern bool dlist_has_next(dlist_head *head, dlist_node *node); +extern bool dlist_has_prev(dlist_head *head, dlist_node *node); +extern dlist_node *dlist_next_node(dlist_head *head, dlist_node *node); +extern dlist_node *dlist_prev_node(dlist_head *head, dlist_node *node); +extern dlist_node *dlist_head_node(dlist_head *head); +extern dlist_node *dlist_tail_node(dlist_head *head); + +/* dlist macro support functions */ +extern void *dlist_tail_element_off(dlist_head *head, size_t off); +extern void *dlist_head_element_off(dlist_head *head, size_t off); +#endif /* !PG_USE_INLINE */ + +#if defined(PG_USE_INLINE) || defined(ILIST_INCLUDE_DEFINITIONS) +/* + * Initialize the head of a list. Previous state will be thrown away without + * any cleanup. + */ +STATIC_IF_INLINE void +dlist_init(dlist_head *head) +{ + head->head.next = head->head.prev = &head->head; + + dlist_check(head); +} + +/* + * Insert a node at the beginning of the list. + */ +STATIC_IF_INLINE void +dlist_push_head(dlist_head *head, dlist_node *node) +{ + if (head->head.next == NULL) + dlist_init(head); + + node->next = head->head.next; + node->prev = &head->head; + node->next->prev = node; + head->head.next = node; + + dlist_check(head); +} + +/* + * Inserts a node at the end of the list. + */ +STATIC_IF_INLINE void +dlist_push_tail(dlist_head *head, dlist_node *node) +{ + if (head->head.next == NULL) + dlist_init(head); + + node->next = &head->head; + node->prev = head->head.prev; + node->prev->next = node; + head->head.prev = node; + + dlist_check(head); +} + +/* + * Insert a node after another *in the same list* + */ +STATIC_IF_INLINE void +dlist_insert_after(dlist_head *head, dlist_node *after, dlist_node *node) +{ + dlist_check(head); + /* XXX: assert 'after' is in 'head'? */ + + node->prev = after; + node->next = after->next; + after->next = node; + node->next->prev = node; + + dlist_check(head); +} + +/* + * Insert a node before another *in the same list* + */ +STATIC_IF_INLINE void +dlist_insert_before(dlist_head *head, dlist_node *before, dlist_node *node) +{ + dlist_check(head); + /* XXX: assert 'before' is in 'head'? */ + + node->prev = before->prev; + node->next = before; + before->prev = node; + node->prev->next = node; + + dlist_check(head); +} + +/* + * Delete 'node' from list. + * + * It is not allowed to delete a 'node' which is is not in the list 'head' + */ +STATIC_IF_INLINE void +dlist_delete(dlist_head *head, dlist_node *node) +{ + dlist_check(head); + + node->prev->next = node->next; + node->next->prev = node->prev; + + dlist_check(head); +} + +/* + * Delete and return the first node from a list. + * + * Undefined behaviour when the list is empty. Check with dlist_is_empty if + * necessary. + */ +STATIC_IF_INLINE dlist_node * +dlist_pop_head_node(dlist_head *head) +{ + dlist_node *ret; + + Assert(&head->head != head->head.next); + + ret = head->head.next; + dlist_delete(head, head->head.next); + return ret; +} + +/* + * Move element from its current position in the list to the head position in + * the same list. + * + * Undefined behaviour if 'node' is not already part of the list. + */ +STATIC_IF_INLINE void +dlist_move_head(dlist_head *head, dlist_node *node) +{ + /* fast path if it's already at the head */ + if (head->head.next == node) + return; + + dlist_delete(head, node); + dlist_push_head(head, node); + + dlist_check(head); +} + +/* + * Check whether the passed node is the last element in the list. + */ +STATIC_IF_INLINE bool +dlist_has_next(dlist_head *head, dlist_node *node) +{ + return node->next != &head->head; +} + +/* + * Check whether the passed node is the first element in the list. + */ +STATIC_IF_INLINE bool +dlist_has_prev(dlist_head *head, dlist_node *node) +{ + return node->prev != &head->head; +} + +/* + * Return the next node in the list. + * + * Undefined behaviour when no next node exists. Use dlist_has_next to make + * sure. + */ +STATIC_IF_INLINE dlist_node * +dlist_next_node(dlist_head *head, dlist_node *node) +{ + Assert(dlist_has_next(head, node)); + return node->next; +} + +/* + * Return previous node in the list. + * + * Undefined behaviour when no prev node exists. Use dlist_has_prev to make + * sure. + */ +STATIC_IF_INLINE dlist_node * +dlist_prev_node(dlist_head *head, dlist_node *node) +{ + Assert(dlist_has_prev(head, node)); + return node->prev; +} + +/* + * Return whether the list is empty. + * + * An empty list has either its first 'next' pointer set to NULL, or to itself. + */ +STATIC_IF_INLINE bool +dlist_is_empty(dlist_head *head) +{ + dlist_check(head); + + return head->head.next == NULL || head->head.next == &(head->head); +} + +/* internal support function */ +STATIC_IF_INLINE void * +dlist_head_element_off(dlist_head *head, size_t off) +{ + Assert(!dlist_is_empty(head)); + return (char *) head->head.next - off; +} + +/* + * Return the first node in the list. + * + * Use dlist_is_empty to make sure the list is not empty if not sure. + */ +STATIC_IF_INLINE dlist_node * +dlist_head_node(dlist_head *head) +{ + return dlist_head_element_off(head, 0); +} + +/* internal support function */ +STATIC_IF_INLINE void * +dlist_tail_element_off(dlist_head *head, size_t off) +{ + Assert(!dlist_is_empty(head)); + return (char *) head->head.prev - off; +} + +/* + * Return the last node in the list. + * + * Use dlist_is_empty to make sure the list is not empty if not sure. + */ +STATIC_IF_INLINE dlist_node * +dlist_tail_node(dlist_head *head) +{ + return dlist_tail_element_off(head, 0); +} +#endif /* PG_USE_INLINE || ILIST_INCLUDE_DEFINITIONS */ + +/* + * 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 that AssertVariableIsOfTypeMacro is a compile-time only check, so we + * don't have multiple evaluation dangers here. + */ +#define dlist_container(type, membername, ptr) \ + (AssertVariableIsOfTypeMacro(ptr, dlist_node *), \ + AssertVariableIsOfTypeMacro(((type *) NULL)->membername, dlist_node), \ + ((type *)((char *)(ptr) - offsetof(type, membername)))) + +/* + * Return the value of first element in the list. + * + * The list must not be empty. + * + * Note that AssertVariableIsOfTypeMacro is a compile-time only check, so we + * don't have multiple evaluation dangers here. + */ +#define dlist_head_element(type, membername, ptr) \ + (AssertVariableIsOfTypeMacro(((type *) NULL)->membername, dlist_node), \ + ((type *)dlist_head_element_off(ptr, offsetof(type, membername)))) + +/* + * Return the value of first element in the list. + * + * The list must not be empty. + * + * Note that AssertVariableIsOfTypeMacro is a compile-time only check, so we + * don't have multiple evaluation dangers here. + */ +#define dlist_tail_element(type, membername, ptr) \ + (AssertVariableIsOfTypeMacro(((type *) NULL)->membername, dlist_node), \ + ((type *)dlist_tail_element_off(ptr, offsetof(type, membername)))) + +/* + * Iterate through the list pointed at by 'ptr' storing the state in 'iter'. + * + * Access the current element with iter.cur. + * + * It is *not* allowed to manipulate the list during iteration. + */ +#define dlist_foreach(iter, ptr) \ + AssertVariableIsOfType(iter, dlist_iter); \ + AssertVariableIsOfType(ptr, dlist_head *); \ + for (iter.end = &(ptr)->head, \ + iter.cur = iter.end->next ? iter.end->next : iter.end; \ + iter.cur != iter.end; \ + iter.cur = iter.cur->next) + +/* + * Iterate through the list pointed at by 'ptr' storing the state in 'iter'. + * + * Access the current element with iter.cur. + * + * It is allowed to delete the current element from the list. Every other + * manipulation can lead to corruption. + */ +#define dlist_foreach_modify(iter, ptr) \ + AssertVariableIsOfType(iter, dlist_mutable_iter); \ + AssertVariableIsOfType(ptr, dlist_head *); \ + for (iter.end = &(ptr)->head, \ + iter.cur = iter.end->next ? iter.end->next : iter.end, \ + iter.next = iter.cur->next; \ + iter.cur != iter.end; \ + iter.cur = iter.next, iter.next = iter.cur->next) + +/* + * Iterate through the list in reverse order. + * + * It is *not* allowed to manipulate the list during iteration. + */ +#define dlist_reverse_foreach(iter, ptr) \ + AssertVariableIsOfType(iter, dlist_iter); \ + AssertVariableIsOfType(ptr, dlist_head *); \ + for (iter.end = &(ptr)->head, \ + iter.cur = iter.end->prev ? iter.end->prev : iter.end; \ + iter.cur != iter.end; \ + iter.cur = iter.cur->prev) + + +/* + * We want the functions below to be inline; but if the compiler doesn't + * support that, fall back on providing them as regular functions. See + * STATIC_IF_INLINE in c.h. + */ +#ifndef PG_USE_INLINE +extern void slist_init(slist_head *head); +extern bool slist_is_empty(slist_head *head); +extern slist_node *slist_head_node(slist_head *head); +extern void slist_push_head(slist_head *head, slist_node *node); +extern slist_node *slist_pop_head_node(slist_head *head); +extern void slist_insert_after(slist_head *head, + slist_node *after, slist_node *node); +extern bool slist_has_next(slist_head *head, slist_node *node); +extern slist_node *slist_next_node(slist_head *head, slist_node *node); + +/* slist macro support function */ +extern void *slist_head_element_off(slist_head *head, size_t off); +#endif + +#if defined(PG_USE_INLINE) || defined(ILIST_INCLUDE_DEFINITIONS) +/* + * Initialize a singly linked list. + */ +STATIC_IF_INLINE void +slist_init(slist_head *head) +{ + head->head.next = NULL; + + slist_check(head); +} + +/* + * Is the list empty? + */ +STATIC_IF_INLINE bool +slist_is_empty(slist_head *head) +{ + slist_check(head); + + return head->head.next == NULL; +} + +/* internal support function */ +STATIC_IF_INLINE void * +slist_head_element_off(slist_head *head, size_t off) +{ + Assert(!slist_is_empty(head)); + return (char *) head->head.next - off; +} + +/* + * Push 'node' as the new first node in the list, pushing the original head to + * the second position. + */ +STATIC_IF_INLINE void +slist_push_head(slist_head *head, slist_node *node) +{ + node->next = head->head.next; + head->head.next = node; + + slist_check(head); +} + +/* + * Remove and return the first node in the list + * + * Undefined behaviour if the list is empty. + */ +STATIC_IF_INLINE slist_node * +slist_pop_head_node(slist_head *head) +{ + slist_node *node; + + Assert(!slist_is_empty(head)); + + node = head->head.next; + head->head.next = head->head.next->next; + + slist_check(head); + + return node; +} + +/* + * Insert a new node after another one + * + * Undefined behaviour if 'after' is not part of the list already. + */ +STATIC_IF_INLINE void +slist_insert_after(slist_head *head, slist_node *after, + slist_node *node) +{ + node->next = after->next; + after->next = node; + + slist_check(head); +} + +/* + * Return whether 'node' has a following node + */ +STATIC_IF_INLINE bool +slist_has_next(slist_head *head, + slist_node *node) +{ + slist_check(head); + + return node->next != NULL; +} +#endif /* PG_USE_INLINE || ILIST_INCLUDE_DEFINITIONS */ + +/* + * Return the containing struct of 'type' where 'membername' is the slist_node + * pointed at by 'ptr'. + * + * This is used to convert a slist_node * back to its containing struct. + * + * Note that AssertVariableIsOfTypeMacro is a compile-time only check, so we + * don't have multiple evaluation dangers here. + */ +#define slist_container(type, membername, ptr) \ + (AssertVariableIsOfTypeMacro(ptr, slist_node *), \ + AssertVariableIsOfTypeMacro(((type *) NULL)->membername, slist_node), \ + ((type *)((char *)(ptr) - offsetof(type, membername)))) + +/* + * Return the value of first element in the list. + */ +#define slist_head_element(type, membername, ptr) \ + (AssertVariableIsOfTypeMacro(((type *) NULL)->membername, slist_node), \ + slist_head_element_off(ptr, offsetoff(type, membername))) + +/* + * Iterate through the list 'ptr' using the iterator 'iter'. + * + * It is *not* allowed to manipulate the list during iteration. + */ +#define slist_foreach(iter, ptr) \ + AssertVariableIsOfType(iter, slist_iter); \ + AssertVariableIsOfType(ptr, slist_head *); \ + for (iter.cur = (ptr)->head.next; \ + iter.cur != NULL; \ + iter.cur = iter.cur->next) + +/* + * Iterate through the list 'ptr' using the iterator 'iter' allowing some + * modifications. + * + * It is allowed to delete the current element from the list and add new nodes + * before the current position. Other manipulations can lead to corruption. + */ +#define slist_foreach_modify(iter, ptr) \ + AssertVariableIsOfType(iter, slist_mutable_iter); \ + AssertVariableIsOfType(ptr, slist_head *); \ + for (iter.cur = (ptr)->head.next, \ + iter.next = iter.cur ? iter.cur->next : NULL; \ + iter.cur != NULL; \ + iter.cur = iter.next, \ + iter.next = iter.next ? iter.next->next : NULL) + +#endif /* ILIST_H */ diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h index d91700a07e3..cc6dab24649 100644 --- a/src/include/utils/catcache.h +++ b/src/include/utils/catcache.h @@ -22,7 +22,7 @@ #include "access/htup.h" #include "access/skey.h" -#include "lib/dllist.h" +#include "lib/ilist.h" #include "utils/relcache.h" /* @@ -37,7 +37,7 @@ typedef struct catcache { int id; /* cache identifier --- see syscache.h */ - struct catcache *cc_next; /* link to next catcache */ + slist_node cc_next; /* list link */ const char *cc_relname; /* name of relation the tuples come from */ Oid cc_reloid; /* OID of relation the tuples come from */ Oid cc_indexoid; /* OID of index matching cache keys */ @@ -51,7 +51,7 @@ typedef struct catcache ScanKeyData cc_skey[CATCACHE_MAXKEYS]; /* precomputed key info for * heap scans */ bool cc_isname[CATCACHE_MAXKEYS]; /* flag "name" key columns */ - Dllist cc_lists; /* list of CatCList structs */ + dlist_head cc_lists; /* list of CatCList structs */ #ifdef CATCACHE_STATS long cc_searches; /* total # searches against this cache */ long cc_hits; /* # of matches against existing entry */ @@ -66,7 +66,7 @@ typedef struct catcache long cc_lsearches; /* total # list-searches */ long cc_lhits; /* # of matches against existing lists */ #endif - Dllist cc_bucket[1]; /* hash buckets --- VARIABLE LENGTH ARRAY */ + dlist_head cc_bucket[1]; /* hash buckets --- VARIABLE LENGTH ARRAY */ } CatCache; /* VARIABLE LENGTH STRUCT */ @@ -77,11 +77,12 @@ typedef struct catctup CatCache *my_cache; /* link to owning catcache */ /* - * Each tuple in a cache is a member of a Dllist that stores the elements - * of its hash bucket. We keep each Dllist in LRU order to speed repeated + * Each tuple in a cache is a member of a dlist that stores the elements + * of its hash bucket. We keep each dlist in LRU order to speed repeated * lookups. */ - Dlelem cache_elem; /* list member of per-bucket list */ + dlist_node cache_elem; /* list member of per-bucket list */ + dlist_head *cache_bucket; /* containing bucket dlist */ /* * The tuple may also be a member of at most one CatCList. (If a single @@ -139,7 +140,7 @@ typedef struct catclist * might not be true during bootstrap or recovery operations. (namespace.c * is able to save some cycles when it is true.) */ - Dlelem cache_elem; /* list member of per-catcache list */ + dlist_node cache_elem; /* list member of per-catcache list */ int refcount; /* number of active references */ bool dead; /* dead but not yet removed? */ bool ordered; /* members listed in index order? */ @@ -153,7 +154,7 @@ typedef struct catclist typedef struct catcacheheader { - CatCache *ch_caches; /* head of list of CatCache structs */ + slist_head ch_caches; /* head of list of CatCache structs */ int ch_ntup; /* # of tuples in all caches */ } CatCacheHeader;