diff options
Diffstat (limited to 'src/backend/utils/cache')
-rw-r--r-- | src/backend/utils/cache/catcache.c | 187 | ||||
-rw-r--r-- | src/backend/utils/cache/inval.c | 230 | ||||
-rw-r--r-- | src/backend/utils/cache/relcache.c | 143 |
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. |