mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Nested transactions. There is still much left to do, especially on the
performance front, but with feature freeze upon us I think it's time to drive a stake in the ground and say that this will be in 7.5. Alvaro Herrera, with some help from Tom Lane.
This commit is contained in:
187
src/backend/utils/cache/catcache.c
vendored
187
src/backend/utils/cache/catcache.c
vendored
@ -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,12 +596,174 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
@ -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);
|
||||
|
230
src/backend/utils/cache/inval.c
vendored
230
src/backend/utils/cache/inval.c
vendored
@ -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 previous-commands event list */
|
||||
static InvalidationListHeader PriorCmdInvalidMsgs;
|
||||
/* head of current-command event list */
|
||||
InvalidationListHeader CurrentCmdInvalidMsgs;
|
||||
|
||||
static bool RelcacheInitFileInval; /* init file must be invalidated? */
|
||||
/* head of previous-commands event list */
|
||||
InvalidationListHeader PriorCmdInvalidMsgs;
|
||||
|
||||
/* 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;
|
||||
@ -203,29 +221,6 @@ AddInvalidationMessage(InvalidationChunk **listHdr,
|
||||
chunk->nitems++;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
@ -331,31 +326,6 @@ AppendInvalidationMessages(InvalidationListHeader *dest,
|
||||
AppendInvalidationMessageList(&dest->rclist, &src->rclist);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
DiscardInvalidationMessages(&PriorCmdInvalidMsgs, false);
|
||||
DiscardInvalidationMessages(&CurrentCmdInvalidMsgs, false);
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
143
src/backend/utils/cache/relcache.c
vendored
143
src/backend/utils/cache/relcache.c
vendored
@ -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())
|
||||
@ -2036,6 +2040,12 @@ AtEOXact_RelationCache(bool commit)
|
||||
RelationSetReferenceCount(relation, expected_refcnt);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
@ -2048,6 +2058,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,
|
||||
|
Reference in New Issue
Block a user