summaryrefslogtreecommitdiff
path: root/src/backend/utils/cache
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/cache')
-rw-r--r--src/backend/utils/cache/catcache.c187
-rw-r--r--src/backend/utils/cache/inval.c230
-rw-r--r--src/backend/utils/cache/relcache.c143
3 files changed, 462 insertions, 98 deletions
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 5e91a7283ec..8bfa3610bdb 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/catcache.c,v 1.112 2004/05/26 04:41:40 neilc Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/catcache.c,v 1.113 2004/07/01 00:51:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -360,6 +360,8 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct)
/* free associated tuple data */
if (ct->tuple.t_data != NULL)
pfree(ct->tuple.t_data);
+ if (ct->prev_refcount != NULL)
+ pfree(ct->prev_refcount);
pfree(ct);
--cache->cc_ntup;
@@ -394,6 +396,8 @@ CatCacheRemoveCList(CatCache *cache, CatCList *cl)
/* free associated tuple data */
if (cl->tuple.t_data != NULL)
pfree(cl->tuple.t_data);
+ if (cl->prev_refcount != NULL)
+ pfree(cl->prev_refcount);
pfree(cl);
}
@@ -518,9 +522,9 @@ CreateCacheMemoryContext(void)
if (!CacheMemoryContext)
CacheMemoryContext = AllocSetContextCreate(TopMemoryContext,
"CacheMemoryContext",
- ALLOCSET_DEFAULT_MINSIZE,
- ALLOCSET_DEFAULT_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
}
@@ -560,6 +564,13 @@ AtEOXact_CatCache(bool isCommit)
cl->refcount = 0;
}
+ /*
+ * Reset the refcount stack. Drop the item count to zero,
+ * but don't deallocate the stack itself, so it can be used by
+ * future subtransactions.
+ */
+ cl->numpushes = 0;
+
/* Clean up any now-deletable dead entries */
if (cl->dead)
CatCacheRemoveCList(ccp, cl);
@@ -585,6 +596,13 @@ AtEOXact_CatCache(bool isCommit)
ct->refcount = 0;
}
+ /*
+ * Reset the refcount stack. Drop the item count to zero,
+ * but don't deallocate the stack itself, so it can be used by
+ * future subtransactions.
+ */
+ ct->numpushes = 0;
+
/* Clean up any now-deletable dead entries */
if (ct->dead)
CatCacheRemoveCTup(ct->my_cache, ct);
@@ -592,6 +610,161 @@ AtEOXact_CatCache(bool isCommit)
}
/*
+ * AtSubStart_CatCache
+ *
+ * Saves reference counts of each entry at subtransaction start so they
+ * can be restored if the subtransaction later aborts.
+ */
+void
+AtSubStart_CatCache(void)
+{
+ CatCache *ccp;
+ Dlelem *elt,
+ *nextelt;
+ MemoryContext old_cxt;
+
+
+ old_cxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+ /*
+ * Prepare CLists
+ */
+ for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next)
+ {
+ for (elt = DLGetHead(&ccp->cc_lists); elt; elt = nextelt)
+ {
+ CatCList *cl = (CatCList *) DLE_VAL(elt);
+
+ nextelt = DLGetSucc(elt);
+
+ if (cl->numpushes == cl->numalloc)
+ {
+ if (cl->numalloc == 0)
+ {
+ cl->numalloc = 8;
+ cl->prev_refcount = palloc(sizeof(int) * cl->numalloc);
+ }
+ else
+ {
+ cl->numalloc *= 2;
+ cl->prev_refcount = repalloc(cl->prev_refcount, cl->numalloc * sizeof(int));
+ }
+ }
+
+ cl->prev_refcount[cl->numpushes++] = cl->refcount;
+ }
+ }
+
+ /*
+ * Prepare CTuples
+ */
+ for (elt = DLGetHead(&CacheHdr->ch_lrulist); elt; elt = nextelt)
+ {
+ CatCTup *ct = (CatCTup *) DLE_VAL(elt);
+
+ nextelt = DLGetSucc(elt);
+
+ if (ct->numpushes == ct->numalloc)
+ {
+ if (ct->numalloc == 0)
+ {
+ ct->numalloc = 8;
+ ct->prev_refcount = palloc(sizeof(int) * ct->numalloc);
+ }
+ else
+ {
+ ct->numalloc *= 2;
+ ct->prev_refcount = repalloc(ct->prev_refcount, sizeof(int) * ct->numalloc);
+ }
+ }
+
+ ct->prev_refcount[ct->numpushes++] = ct->refcount;
+ }
+
+ MemoryContextSwitchTo(old_cxt);
+}
+
+void
+AtEOSubXact_CatCache(bool isCommit)
+{
+ CatCache *ccp;
+ Dlelem *elt,
+ *nextelt;
+
+ /*
+ * Restore CLists
+ */
+ for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next)
+ {
+ for (elt = DLGetHead(&ccp->cc_lists); elt; elt = nextelt)
+ {
+ CatCList *cl = (CatCList *) DLE_VAL(elt);
+
+ nextelt = DLGetSucc(elt);
+
+ /*
+ * During commit, check whether the count is what
+ * we expect.
+ */
+ if (isCommit)
+ {
+ int expected_refcount;
+ if (cl->numpushes > 0)
+ expected_refcount = cl->prev_refcount[cl->numpushes - 1];
+ else
+ expected_refcount = 0;
+
+ if (cl->refcount != expected_refcount)
+ elog(WARNING, "catcache reference leak");
+ }
+
+ /*
+ * During abort we have to restore the original count;
+ * during commit, we have to restore in case of a leak,
+ * and it won't harm if this is the expected count.
+ */
+ if (cl->numpushes > 0)
+ cl->refcount = cl->prev_refcount[--cl->numpushes];
+ else
+ cl->refcount = 0;
+ }
+ }
+
+ /*
+ * Prepare CTuples
+ */
+ for (elt = DLGetHead(&CacheHdr->ch_lrulist); elt; elt = nextelt)
+ {
+ CatCTup *ct = (CatCTup *) DLE_VAL(elt);
+
+ nextelt = DLGetSucc(elt);
+
+ if (isCommit)
+ {
+ int expected_refcount;
+
+ if (ct->numpushes > 0)
+ expected_refcount = ct->prev_refcount[ct->numpushes - 1];
+ else
+ expected_refcount = 0;
+
+ if (ct->refcount != expected_refcount)
+ elog(WARNING, "catcache reference leak");
+ }
+
+ /*
+ * During abort we have to restore the original count;
+ * during commit, we have to restore in case of a leak,
+ * and it won't harm if this is the expected count.
+ */
+ if (ct->numpushes > 0)
+ ct->refcount = ct->prev_refcount[--ct->numpushes];
+ else
+ ct->refcount = 0;
+ }
+}
+
+/*
* ResetCatalogCache
*
* Reset one catalog cache to empty.
@@ -1505,6 +1678,9 @@ SearchCatCacheList(CatCache *cache,
cl->my_cache = cache;
DLInitElem(&cl->cache_elem, (void *) cl);
cl->refcount = 1; /* count this first reference */
+ cl->prev_refcount = NULL;
+ cl->numpushes = 0;
+ cl->numalloc = 0;
cl->dead = false;
cl->ordered = ordered;
cl->nkeys = nkeys;
@@ -1603,6 +1779,9 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
ct->dead = false;
ct->negative = negative;
ct->hash_value = hashValue;
+ ct->prev_refcount = NULL;
+ ct->numpushes = 0;
+ ct->numalloc = 0;
DLAddHead(&CacheHdr->ch_lrulist, &ct->lrulist_elem);
DLAddHead(&cache->cc_bucket[hashIndex], &ct->cache_elem);
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index ea958a27b46..e54a74fae4b 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -33,6 +33,10 @@
* to record the transaction commit before sending SI messages, otherwise
* the other backends won't see our updated tuples as good.
*
+ * When a subtransaction aborts, we can process and discard any events
+ * it has queued. When a subtransaction commits, we just add its events
+ * to the pending lists of the parent transaction.
+ *
* In short, we need to remember until xact end every insert or delete
* of a tuple that might be in the system caches. Updates are treated as
* two events, delete + insert, for simplicity. (There are cases where
@@ -66,15 +70,17 @@
* manipulating the init file is in relcache.c, but we keep track of the
* need for it here.
*
- * All the request lists are kept in TopTransactionContext memory, since
- * they need not live beyond the end of the current transaction.
+ * The request lists proper are kept in CurTransactionContext of their
+ * creating (sub)transaction, since they can be forgotten on abort of that
+ * transaction but must be kept till top-level commit otherwise. For
+ * simplicity we keep the controlling list-of-lists in TopTransactionContext.
*
*
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.62 2004/06/18 06:13:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.63 2004/07/01 00:51:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -95,7 +101,7 @@
* To minimize palloc traffic, we keep pending requests in successively-
* larger chunks (a slightly more sophisticated version of an expansible
* array). All request types can be stored as SharedInvalidationMessage
- * records.
+ * records. The ordering of requests within a list is never significant.
*/
typedef struct InvalidationChunk
{
@@ -112,12 +118,15 @@ typedef struct InvalidationListHeader
} InvalidationListHeader;
/*----------------
- * Invalidation info is divided into two lists:
+ * Invalidation info is divided into two lists:
* 1) events so far in current command, not yet reflected to caches.
* 2) events in previous commands of current transaction; these have
* been reflected to local caches, and must be either broadcast to
* other backends or rolled back from local cache when we commit
* or abort the transaction.
+ * Actually, we need two such lists for each level of nested transaction,
+ * so that we can discard events from an aborted subtransaction. When
+ * a subtransaction commits, we append its lists to the parent's lists.
*
* The relcache-file-invalidated flag can just be a simple boolean,
* since we only act on it at transaction commit; we don't care which
@@ -125,13 +134,22 @@ typedef struct InvalidationListHeader
*----------------
*/
-/* head of current-command event list */
-static InvalidationListHeader CurrentCmdInvalidMsgs;
+typedef struct TransInvalidationInfo
+{
+ /* Back link to parent transaction's info */
+ struct TransInvalidationInfo *parent;
+
+ /* head of current-command event list */
+ InvalidationListHeader CurrentCmdInvalidMsgs;
-/* head of previous-commands event list */
-static InvalidationListHeader PriorCmdInvalidMsgs;
+ /* head of previous-commands event list */
+ InvalidationListHeader PriorCmdInvalidMsgs;
-static bool RelcacheInitFileInval; /* init file must be invalidated? */
+ /* init file must be invalidated? */
+ bool RelcacheInitFileInval;
+} TransInvalidationInfo;
+
+static TransInvalidationInfo *transInvalInfo = NULL;
/*
* Dynamically-registered callback functions. Current implementation
@@ -176,7 +194,7 @@ AddInvalidationMessage(InvalidationChunk **listHdr,
/* First time through; create initial chunk */
#define FIRSTCHUNKSIZE 16
chunk = (InvalidationChunk *)
- MemoryContextAlloc(TopTransactionContext,
+ MemoryContextAlloc(CurTransactionContext,
sizeof(InvalidationChunk) +
(FIRSTCHUNKSIZE - 1) *sizeof(SharedInvalidationMessage));
chunk->nitems = 0;
@@ -190,7 +208,7 @@ AddInvalidationMessage(InvalidationChunk **listHdr,
int chunksize = 2 * chunk->maxitems;
chunk = (InvalidationChunk *)
- MemoryContextAlloc(TopTransactionContext,
+ MemoryContextAlloc(CurTransactionContext,
sizeof(InvalidationChunk) +
(chunksize - 1) *sizeof(SharedInvalidationMessage));
chunk->nitems = 0;
@@ -204,29 +222,6 @@ AddInvalidationMessage(InvalidationChunk **listHdr,
}
/*
- * Free a list of inval message chunks.
- *
- * NOTE: when we are about to commit or abort a transaction, it's
- * not really necessary to pfree the lists explicitly, since they will
- * go away anyway when TopTransactionContext is destroyed.
- */
-static void
-FreeInvalidationMessageList(InvalidationChunk **listHdr)
-{
- InvalidationChunk *chunk = *listHdr;
-
- *listHdr = NULL;
-
- while (chunk != NULL)
- {
- InvalidationChunk *nextchunk = chunk->next;
-
- pfree(chunk);
- chunk = nextchunk;
- }
-}
-
-/*
* Append one list of invalidation message chunks to another, resetting
* the source chunk-list pointer to NULL.
*/
@@ -332,31 +327,6 @@ AppendInvalidationMessages(InvalidationListHeader *dest,
}
/*
- * Reset an invalidation list to empty
- *
- * physicalFree may be set false if caller knows transaction is ending
- */
-static void
-DiscardInvalidationMessages(InvalidationListHeader *hdr, bool physicalFree)
-{
- if (physicalFree)
- {
- /* Physically pfree the list data */
- FreeInvalidationMessageList(&hdr->cclist);
- FreeInvalidationMessageList(&hdr->rclist);
- }
- else
- {
- /*
- * Assume the storage will go away at xact end, just reset
- * pointers
- */
- hdr->cclist = NULL;
- hdr->rclist = NULL;
- }
-}
-
-/*
* Execute the given function for all the messages in an invalidation list.
* The list is not altered.
*
@@ -386,7 +356,7 @@ RegisterCatcacheInvalidation(int cacheId,
ItemPointer tuplePtr,
Oid dbId)
{
- AddCatcacheInvalidationMessage(&CurrentCmdInvalidMsgs,
+ AddCatcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs,
cacheId, hashValue, tuplePtr, dbId);
}
@@ -398,7 +368,7 @@ RegisterCatcacheInvalidation(int cacheId,
static void
RegisterRelcacheInvalidation(Oid dbId, Oid relId, RelFileNode physId)
{
- AddRelcacheInvalidationMessage(&CurrentCmdInvalidMsgs,
+ AddRelcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs,
dbId, relId, physId);
/*
@@ -406,7 +376,7 @@ RegisterRelcacheInvalidation(Oid dbId, Oid relId, RelFileNode physId)
* relcache init file, mark that we need to zap that file at commit.
*/
if (RelationIdIsInInitFile(relId))
- RelcacheInitFileInval = true;
+ transInvalInfo->RelcacheInitFileInval = true;
}
/*
@@ -619,8 +589,38 @@ AcceptInvalidationMessages(void)
}
/*
- * AtEOXactInvalidationMessages
- * Process queued-up invalidation messages at end of transaction.
+ * AtStart_Inval
+ * Initialize inval lists at start of a main transaction.
+ */
+void
+AtStart_Inval(void)
+{
+ Assert(transInvalInfo == NULL);
+ transInvalInfo = (TransInvalidationInfo *)
+ MemoryContextAllocZero(TopTransactionContext,
+ sizeof(TransInvalidationInfo));
+}
+
+/*
+ * AtSubStart_Inval
+ * Initialize inval lists at start of a subtransaction.
+ */
+void
+AtSubStart_Inval(void)
+{
+ TransInvalidationInfo *myInfo;
+
+ Assert(transInvalInfo != NULL);
+ myInfo = (TransInvalidationInfo *)
+ MemoryContextAllocZero(TopTransactionContext,
+ sizeof(TransInvalidationInfo));
+ myInfo->parent = transInvalInfo;
+ transInvalInfo = myInfo;
+}
+
+/*
+ * AtEOXact_Inval
+ * Process queued-up invalidation messages at end of main transaction.
*
* If isCommit, we must send out the messages in our PriorCmdInvalidMsgs list
* to the shared invalidation message queue. Note that these will be read
@@ -643,8 +643,11 @@ AcceptInvalidationMessages(void)
* This should be called as the last step in processing a transaction.
*/
void
-AtEOXactInvalidationMessages(bool isCommit)
+AtEOXact_Inval(bool isCommit)
{
+ /* Must be at top of stack */
+ Assert(transInvalInfo != NULL && transInvalInfo->parent == NULL);
+
if (isCommit)
{
/*
@@ -652,28 +655,77 @@ AtEOXactInvalidationMessages(bool isCommit)
* and after we send the SI messages. However, we need not do
* anything unless we committed.
*/
- if (RelcacheInitFileInval)
+ if (transInvalInfo->RelcacheInitFileInval)
RelationCacheInitFileInvalidate(true);
- AppendInvalidationMessages(&PriorCmdInvalidMsgs,
- &CurrentCmdInvalidMsgs);
+ AppendInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
+ &transInvalInfo->CurrentCmdInvalidMsgs);
- ProcessInvalidationMessages(&PriorCmdInvalidMsgs,
+ ProcessInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
SendSharedInvalidMessage);
- if (RelcacheInitFileInval)
+ if (transInvalInfo->RelcacheInitFileInval)
RelationCacheInitFileInvalidate(false);
}
else
{
- ProcessInvalidationMessages(&PriorCmdInvalidMsgs,
+ ProcessInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
LocalExecuteInvalidationMessage);
}
- RelcacheInitFileInval = false;
+ /* Need not free anything explicitly */
+ transInvalInfo = NULL;
+}
+
+/*
+ * AtSubEOXact_Inval
+ * Process queued-up invalidation messages at end of subtransaction.
+ *
+ * If isCommit, process CurrentCmdInvalidMsgs if any (there probably aren't),
+ * and then attach both CurrentCmdInvalidMsgs and PriorCmdInvalidMsgs to the
+ * parent's PriorCmdInvalidMsgs list.
+ *
+ * If not isCommit, we are aborting, and must locally process the messages
+ * in PriorCmdInvalidMsgs. No messages need be sent to other backends.
+ * We can forget about CurrentCmdInvalidMsgs too, since those changes haven't
+ * touched the caches yet.
+ *
+ * In any case, pop the transaction stack. We need not physically free memory
+ * here, since CurTransactionContext is about to be emptied anyway
+ * (if aborting).
+ */
+void
+AtSubEOXact_Inval(bool isCommit)
+{
+ TransInvalidationInfo *myInfo = transInvalInfo;
+
+ /* Must be at non-top of stack */
+ Assert(myInfo != NULL && myInfo->parent != NULL);
+
+ if (isCommit)
+ {
+ /* If CurrentCmdInvalidMsgs still has anything, fix it */
+ CommandEndInvalidationMessages();
+
+ /* Pass up my inval messages to parent */
+ AppendInvalidationMessages(&myInfo->parent->PriorCmdInvalidMsgs,
+ &myInfo->PriorCmdInvalidMsgs);
- DiscardInvalidationMessages(&PriorCmdInvalidMsgs, false);
- DiscardInvalidationMessages(&CurrentCmdInvalidMsgs, false);
+ /* Pending relcache inval becomes parent's problem too */
+ if (myInfo->RelcacheInitFileInval)
+ myInfo->parent->RelcacheInitFileInval = true;
+ }
+ else
+ {
+ ProcessInvalidationMessages(&myInfo->PriorCmdInvalidMsgs,
+ LocalExecuteInvalidationMessage);
+ }
+
+ /* Pop the transaction state stack */
+ transInvalInfo = myInfo->parent;
+
+ /* Need not free anything else explicitly */
+ pfree(myInfo);
}
/*
@@ -687,27 +739,25 @@ AtEOXactInvalidationMessages(bool isCommit)
* current command. We then move the current-cmd list over to become part
* of the prior-cmds list.
*
- * The isCommit = false case is not currently used, but may someday be
- * needed to support rollback to a savepoint within a transaction.
- *
* Note:
* This should be called during CommandCounterIncrement(),
* after we have advanced the command ID.
*/
void
-CommandEndInvalidationMessages(bool isCommit)
+CommandEndInvalidationMessages(void)
{
- if (isCommit)
- {
- ProcessInvalidationMessages(&CurrentCmdInvalidMsgs,
- LocalExecuteInvalidationMessage);
- AppendInvalidationMessages(&PriorCmdInvalidMsgs,
- &CurrentCmdInvalidMsgs);
- }
- else
- {
- /* XXX what needs to be done here? */
- }
+ /*
+ * You might think this shouldn't be called outside any transaction,
+ * but bootstrap does it, and also ABORT issued when not in a transaction.
+ * So just quietly return if no state to work on.
+ */
+ if (transInvalInfo == NULL)
+ return;
+
+ ProcessInvalidationMessages(&transInvalInfo->CurrentCmdInvalidMsgs,
+ LocalExecuteInvalidationMessage);
+ AppendInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
+ &transInvalInfo->CurrentCmdInvalidMsgs);
}
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ee8b46407e1..23428992724 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.205 2004/06/18 06:13:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.206 2004/07/01 00:51:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -273,6 +273,8 @@ static void IndexSupportInitialize(Form_pg_index iform,
static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numStrats,
StrategyNumber numSupport);
+static inline void RelationPushReferenceCount(Relation rel);
+static inline void RelationPopReferenceCount(Relation rel);
/*
@@ -1678,6 +1680,8 @@ RelationClearRelation(Relation relation, bool rebuild)
list_free(relation->rd_indexlist);
if (relation->rd_indexcxt)
MemoryContextDelete(relation->rd_indexcxt);
+ if (relation->rd_prevrefcnt)
+ pfree(relation->rd_prevrefcnt);
/*
* If we're really done with the relcache entry, blow it away. But if
@@ -1968,7 +1972,7 @@ RelationCacheInvalidate(void)
* we must reset refcnts before handling pending invalidations.
*/
void
-AtEOXact_RelationCache(bool commit)
+AtEOXact_RelationCache(bool isCommit)
{
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
@@ -1993,7 +1997,7 @@ AtEOXact_RelationCache(bool commit)
*/
if (relation->rd_isnew)
{
- if (commit)
+ if (isCommit)
relation->rd_isnew = false;
else
{
@@ -2019,7 +2023,7 @@ AtEOXact_RelationCache(bool commit)
*/
expected_refcnt = relation->rd_isnailed ? 1 : 0;
- if (commit)
+ if (isCommit)
{
if (relation->rd_refcnt != expected_refcnt &&
!IsBootstrapProcessingMode())
@@ -2037,6 +2041,12 @@ AtEOXact_RelationCache(bool commit)
}
/*
+ * Reset the refcount stack. Just drop the item count; don't deallocate
+ * the stack itself so it can be reused by future subtransactions.
+ */
+ relation->rd_numpushed = 0;
+
+ /*
* Flush any temporary index list.
*/
if (relation->rd_indexvalid == 2)
@@ -2049,6 +2059,131 @@ AtEOXact_RelationCache(bool commit)
}
/*
+ * RelationPushReferenceCount
+ *
+ * Push the current reference count into the stack. Don't modify the
+ * reference count itself.
+ */
+static inline void
+RelationPushReferenceCount(Relation rel)
+{
+ /* Enlarge the stack if we run out of space. */
+ if (rel->rd_numpushed == rel->rd_numalloc)
+ {
+ MemoryContext old_cxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+ if (rel->rd_numalloc == 0)
+ {
+ rel->rd_numalloc = 8;
+ rel->rd_prevrefcnt = palloc(rel->rd_numalloc * sizeof(int));
+ }
+ else
+ {
+ rel->rd_numalloc *= 2;
+ rel->rd_prevrefcnt = repalloc(rel->rd_prevrefcnt, rel->rd_numalloc * sizeof(int));
+ }
+
+ MemoryContextSwitchTo(old_cxt);
+ }
+
+ rel->rd_prevrefcnt[rel->rd_numpushed++] = rel->rd_refcnt;
+}
+
+/*
+ * RelationPopReferenceCount
+ *
+ * Pop the latest stored reference count. If there is none, drop it
+ * to zero; the entry was created in the current subtransaction.
+ */
+static inline void
+RelationPopReferenceCount(Relation rel)
+{
+ if (rel->rd_numpushed == 0)
+ {
+ rel->rd_refcnt = rel->rd_isnailed ? 1 : 0;
+ return;
+ }
+
+ rel->rd_refcnt = rel->rd_prevrefcnt[--rel->rd_numpushed];
+}
+
+/*
+ * AtEOSubXact_RelationCache
+ */
+void
+AtEOSubXact_RelationCache(bool isCommit)
+{
+ HASH_SEQ_STATUS status;
+ RelIdCacheEnt *idhentry;
+
+ /* We'd better not be bootstrapping. */
+ Assert(!IsBootstrapProcessingMode());
+
+ hash_seq_init(&status, RelationIdCache);
+
+ while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Relation relation = idhentry->reldesc;
+
+ /*
+ * During subtransaction commit, we first check whether the
+ * current refcount is correct: if there is no item in the stack,
+ * the relcache entry was created during this subtransaction, it should
+ * be 0 (or 1 for nailed relations). If the stack has at least one
+ * item, the expected count is whatever that item is.
+ */
+ if (isCommit)
+ {
+ int expected_refcnt;
+
+ if (relation->rd_numpushed == 0)
+ expected_refcnt = relation->rd_isnailed ? 1 : 0;
+ else
+ expected_refcnt = relation->rd_prevrefcnt[relation->rd_numpushed - 1];
+
+ if (relation->rd_refcnt != expected_refcnt)
+ {
+ elog(WARNING, "relcache reference leak: relation \"%s\" has refcnt %d instead of %d",
+ RelationGetRelationName(relation),
+ relation->rd_refcnt, expected_refcnt);
+ }
+ }
+
+ /*
+ * On commit, the expected count is stored so there's no harm in
+ * popping it (and we may need to fix if there was a leak); and during
+ * abort, the correct refcount has to be restored.
+ */
+ RelationPopReferenceCount(relation);
+ }
+}
+
+/*
+ * AtSubStart_RelationCache
+ *
+ * At subtransaction start, we push the current reference count into
+ * the refcount stack, so it can be restored if the subtransaction aborts.
+ */
+void
+AtSubStart_RelationCache(void)
+{
+ HASH_SEQ_STATUS status;
+ RelIdCacheEnt *idhentry;
+
+ /* We'd better not be bootstrapping. */
+ Assert(!IsBootstrapProcessingMode());
+
+ hash_seq_init(&status, RelationIdCache);
+
+ while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Relation relation = idhentry->reldesc;
+
+ RelationPushReferenceCount(relation);
+ }
+}
+
+/*
* RelationBuildLocalRelation
* Build a relcache entry for an about-to-be-created relation,
* and enter it into the relcache.