Commit bbbc00af authored by Tom Lane's avatar Tom Lane

Clean up some longstanding problems in shared-cache invalidation.

SI messages now include the relevant database OID, so that operations
in one database do not cause useless cache flushes in backends attached
to other databases.  Declare SI messages properly using a union, to
eliminate the former assumption that Oid is the same size as int or Index.
Rewrite the nearly-unreadable code in inval.c, and document it better.
Arrange for catcache flushes at end of command/transaction to happen before
relcache flushes do --- this avoids loading a new tuple into the catcache
while setting up new relcache entry, only to have it be flushed again
immediately.
parent d9a069e2
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.102 2001/05/04 18:39:16 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.103 2001/06/19 19:42:15 tgl Exp $
* *
* NOTES * NOTES
* Transaction aborts can now occur two ways: * Transaction aborts can now occur two ways:
...@@ -544,7 +544,6 @@ CommandCounterIncrement(void) ...@@ -544,7 +544,6 @@ CommandCounterIncrement(void)
*/ */
AtCommit_LocalCache(); AtCommit_LocalCache();
AtStart_Cache(); AtStart_Cache();
} }
void void
...@@ -577,7 +576,7 @@ InitializeTransactionSystem(void) ...@@ -577,7 +576,7 @@ InitializeTransactionSystem(void)
static void static void
AtStart_Cache(void) AtStart_Cache(void)
{ {
DiscardInvalid(); AcceptInvalidationMessages();
} }
/* -------------------------------- /* --------------------------------
...@@ -725,11 +724,10 @@ RecordTransactionCommit() ...@@ -725,11 +724,10 @@ RecordTransactionCommit()
static void static void
AtCommit_Cache(void) AtCommit_Cache(void)
{ {
/* /*
* Make catalog changes visible to all backend. * Make catalog changes visible to all backends.
*/ */
RegisterInvalid(true); AtEOXactInvalidationMessages(true);
} }
/* -------------------------------- /* --------------------------------
...@@ -739,11 +737,10 @@ AtCommit_Cache(void) ...@@ -739,11 +737,10 @@ AtCommit_Cache(void)
static void static void
AtCommit_LocalCache(void) AtCommit_LocalCache(void)
{ {
/* /*
* Make catalog changes visible to me for the next command. * Make catalog changes visible to me for the next command.
*/ */
ImmediateLocalInvalidation(true); CommandEndInvalidationMessages(true);
} }
/* -------------------------------- /* --------------------------------
...@@ -753,7 +750,6 @@ AtCommit_LocalCache(void) ...@@ -753,7 +750,6 @@ AtCommit_LocalCache(void)
static void static void
AtCommit_Locks(void) AtCommit_Locks(void)
{ {
/* /*
* XXX What if ProcReleaseLocks fails? (race condition?) * XXX What if ProcReleaseLocks fails? (race condition?)
* *
...@@ -769,7 +765,6 @@ AtCommit_Locks(void) ...@@ -769,7 +765,6 @@ AtCommit_Locks(void)
static void static void
AtCommit_Memory(void) AtCommit_Memory(void)
{ {
/* /*
* Now that we're "out" of a transaction, have the system allocate * Now that we're "out" of a transaction, have the system allocate
* things in the top memory context instead of per-transaction * things in the top memory context instead of per-transaction
...@@ -844,7 +839,7 @@ static void ...@@ -844,7 +839,7 @@ static void
AtAbort_Cache(void) AtAbort_Cache(void)
{ {
RelationCacheAbort(); RelationCacheAbort();
RegisterInvalid(false); AtEOXactInvalidationMessages(false);
} }
/* -------------------------------- /* --------------------------------
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.33 2001/06/16 22:58:13 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.34 2001/06/19 19:42:15 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -55,56 +55,31 @@ InitBackendSharedInvalidationState(void) ...@@ -55,56 +55,31 @@ InitBackendSharedInvalidationState(void)
} }
/* /*
* RegisterSharedInvalid * SendSharedInvalidMessage
* Add a shared-cache-invalidation message to the global SI message queue. * Add a shared-cache-invalidation message to the global SI message queue.
*
* Note:
* Assumes hash index is valid.
* Assumes item pointer is valid.
*/ */
void void
RegisterSharedInvalid(int cacheId, /* XXX */ SendSharedInvalidMessage(SharedInvalidationMessage *msg)
Index hashIndex,
ItemPointer pointer)
{ {
SharedInvalidData newInvalid;
bool insertOK; bool insertOK;
/*
* This code has been hacked to accept two types of messages. This
* might be treated more generally in the future.
*
* (1) cacheId= system cache id hashIndex= system cache hash index for a
* (possibly) cached tuple pointer= pointer of (possibly) cached tuple
*
* (2) cacheId= special non-syscache id hashIndex= object id contained in
* (possibly) cached relation descriptor pointer= null
*/
newInvalid.cacheId = cacheId;
newInvalid.hashIndex = hashIndex;
if (ItemPointerIsValid(pointer))
ItemPointerCopy(pointer, &newInvalid.pointerData);
else
ItemPointerSetInvalid(&newInvalid.pointerData);
SpinAcquire(SInvalLock); SpinAcquire(SInvalLock);
insertOK = SIInsertDataEntry(shmInvalBuffer, &newInvalid); insertOK = SIInsertDataEntry(shmInvalBuffer, msg);
SpinRelease(SInvalLock); SpinRelease(SInvalLock);
if (!insertOK) if (!insertOK)
elog(DEBUG, "RegisterSharedInvalid: SI buffer overflow"); elog(DEBUG, "SendSharedInvalidMessage: SI buffer overflow");
} }
/* /*
* InvalidateSharedInvalid * ReceiveSharedInvalidMessages
* Process shared-cache-invalidation messages waiting for this backend * Process shared-cache-invalidation messages waiting for this backend
*/ */
void void
InvalidateSharedInvalid(void (*invalFunction) (), ReceiveSharedInvalidMessages(
void (*resetFunction) ()) void (*invalFunction) (SharedInvalidationMessage *msg),
void (*resetFunction) (void))
{ {
SharedInvalidData data; SharedInvalidationMessage data;
int getResult; int getResult;
bool gotMessage = false; bool gotMessage = false;
...@@ -118,15 +93,13 @@ void ...@@ -118,15 +93,13 @@ void
if (getResult < 0) if (getResult < 0)
{ {
/* got a reset message */ /* got a reset message */
elog(DEBUG, "InvalidateSharedInvalid: cache state reset"); elog(DEBUG, "ReceiveSharedInvalidMessages: cache state reset");
resetFunction(); resetFunction();
} }
else else
{ {
/* got a normal data message */ /* got a normal data message */
invalFunction(data.cacheId, invalFunction(&data);
data.hashIndex,
&data.pointerData);
} }
gotMessage = true; gotMessage = true;
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinvaladt.c,v 1.39 2001/06/16 22:58:15 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinvaladt.c,v 1.40 2001/06/19 19:42:15 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
#include "miscadmin.h" #include "miscadmin.h"
#include "storage/backendid.h" #include "storage/backendid.h"
#include "storage/proc.h" #include "storage/proc.h"
#include "storage/sinval.h"
#include "storage/sinvaladt.h" #include "storage/sinvaladt.h"
SISeg *shmInvalBuffer; SISeg *shmInvalBuffer;
...@@ -35,7 +34,6 @@ static void SISetProcStateInvalid(SISeg *segP); ...@@ -35,7 +34,6 @@ static void SISetProcStateInvalid(SISeg *segP);
int int
SInvalShmemSize(int maxBackends) SInvalShmemSize(int maxBackends)
{ {
/* /*
* Figure space needed. Note sizeof(SISeg) includes the first * Figure space needed. Note sizeof(SISeg) includes the first
* ProcState entry. * ProcState entry.
...@@ -183,14 +181,13 @@ CleanupInvalidationState(int status, Datum arg) ...@@ -183,14 +181,13 @@ CleanupInvalidationState(int status, Datum arg)
* Returns true for normal successful insertion, false if had to reset. * Returns true for normal successful insertion, false if had to reset.
*/ */
bool bool
SIInsertDataEntry(SISeg *segP, SharedInvalidData *data) SIInsertDataEntry(SISeg *segP, SharedInvalidationMessage *data)
{ {
int numMsgs = segP->maxMsgNum - segP->minMsgNum; int numMsgs = segP->maxMsgNum - segP->minMsgNum;
/* Is the buffer full? */ /* Is the buffer full? */
if (numMsgs >= MAXNUMMESSAGES) if (numMsgs >= MAXNUMMESSAGES)
{ {
/* /*
* Don't panic just yet: slowest backend might have consumed some * Don't panic just yet: slowest backend might have consumed some
* messages but not yet have done SIDelExpiredDataEntries() to * messages but not yet have done SIDelExpiredDataEntries() to
...@@ -273,13 +270,12 @@ SISetProcStateInvalid(SISeg *segP) ...@@ -273,13 +270,12 @@ SISetProcStateInvalid(SISeg *segP)
*/ */
int int
SIGetDataEntry(SISeg *segP, int backendId, SIGetDataEntry(SISeg *segP, int backendId,
SharedInvalidData *data) SharedInvalidationMessage *data)
{ {
ProcState *stateP = &segP->procState[backendId - 1]; ProcState *stateP = &segP->procState[backendId - 1];
if (stateP->resetState) if (stateP->resetState)
{ {
/* /*
* Force reset. We can say we have dealt with any messages added * Force reset. We can say we have dealt with any messages added
* since the reset, as well... * since the reset, as well...
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/storage/lmgr/lmgr.c,v 1.46 2001/06/12 05:55:49 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/lmgr.c,v 1.47 2001/06/19 19:42:16 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -144,7 +144,7 @@ LockRelation(Relation relation, LOCKMODE lockmode) ...@@ -144,7 +144,7 @@ LockRelation(Relation relation, LOCKMODE lockmode)
* rebuild it and not just delete it. * rebuild it and not just delete it.
*/ */
RelationIncrementReferenceCount(relation); RelationIncrementReferenceCount(relation);
DiscardInvalid(); AcceptInvalidationMessages();
RelationDecrementReferenceCount(relation); RelationDecrementReferenceCount(relation);
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/cache/catcache.c,v 1.79 2001/06/18 03:35:07 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/utils/cache/catcache.c,v 1.80 2001/06/19 19:42:16 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -31,8 +31,18 @@ ...@@ -31,8 +31,18 @@
/* #define CACHEDEBUG */ /* turns DEBUG elogs on */ /* #define CACHEDEBUG */ /* turns DEBUG elogs on */
/* voodoo constants */ /*
#define NCCBUCKETS 257 /* Hash buckets per CatCache (prime!) */ * Constants related to size of the catcache.
*
* NCCBUCKETS should be prime and must be less than 64K (because
* SharedInvalCatcacheMsg crams hash indexes into a uint16 field). In
* practice it should be a lot less, anyway, to avoid chewing up too much
* space on hash bucket headers.
*
* MAXCCTUPLES could be as small as a few hundred, if per-backend memory
* consumption is at a premium.
*/
#define NCCBUCKETS 257 /* Hash buckets per CatCache */
#define MAXCCTUPLES 5000 /* Maximum # of tuples in all caches */ #define MAXCCTUPLES 5000 /* Maximum # of tuples in all caches */
...@@ -218,6 +228,11 @@ CatalogCacheInitializeCache(CatCache *cache) ...@@ -218,6 +228,11 @@ CatalogCacheInitializeCache(CatCache *cache)
*/ */
tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation)); tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation));
/*
* get the relation's relisshared flag, too
*/
cache->cc_relisshared = RelationGetForm(relation)->relisshared;
/* /*
* return to the caller's memory context and close the rel * return to the caller's memory context and close the rel
*/ */
...@@ -737,6 +752,7 @@ InitCatCache(int id, ...@@ -737,6 +752,7 @@ InitCatCache(int id,
cp->cc_relname = relname; cp->cc_relname = relname;
cp->cc_indname = indname; cp->cc_indname = indname;
cp->cc_reloidattr = reloidattr; cp->cc_reloidattr = reloidattr;
cp->cc_relisshared = false; /* temporary */
cp->cc_tupdesc = (TupleDesc) NULL; cp->cc_tupdesc = (TupleDesc) NULL;
cp->cc_ntup = 0; cp->cc_ntup = 0;
cp->cc_size = NCCBUCKETS; cp->cc_size = NCCBUCKETS;
...@@ -1116,7 +1132,8 @@ ReleaseCatCache(HeapTuple tuple) ...@@ -1116,7 +1132,8 @@ ReleaseCatCache(HeapTuple tuple)
* *
* Note that it is irrelevant whether the given tuple is actually loaded * Note that it is irrelevant whether the given tuple is actually loaded
* into the catcache at the moment. Even if it's not there now, it might * into the catcache at the moment. Even if it's not there now, it might
* be by the end of the command, so we have to be prepared to flush it. * be by the end of the command --- or might be in other backends' caches
* --- so we have to be prepared to flush it.
* *
* Also note that it's not an error if there are no catcaches for the * Also note that it's not an error if there are no catcaches for the
* specified relation. inval.c doesn't know exactly which rels have * specified relation. inval.c doesn't know exactly which rels have
...@@ -1126,7 +1143,7 @@ ReleaseCatCache(HeapTuple tuple) ...@@ -1126,7 +1143,7 @@ ReleaseCatCache(HeapTuple tuple)
void void
PrepareToInvalidateCacheTuple(Relation relation, PrepareToInvalidateCacheTuple(Relation relation,
HeapTuple tuple, HeapTuple tuple,
void (*function) (int, Index, ItemPointer)) void (*function) (int, Index, ItemPointer, Oid))
{ {
CatCache *ccp; CatCache *ccp;
...@@ -1159,6 +1176,7 @@ PrepareToInvalidateCacheTuple(Relation relation, ...@@ -1159,6 +1176,7 @@ PrepareToInvalidateCacheTuple(Relation relation,
(*function) (ccp->id, (*function) (ccp->id,
CatalogCacheComputeTupleHashIndex(ccp, tuple), CatalogCacheComputeTupleHashIndex(ccp, tuple),
&tuple->t_self); &tuple->t_self,
ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId);
} }
} }
...@@ -22,22 +22,41 @@ ...@@ -22,22 +22,41 @@
* second lives till end of transaction. Finally, we need a third list of * second lives till end of transaction. Finally, we need a third list of
* all tuples outdated in the current transaction; if we commit, we send * all tuples outdated in the current transaction; if we commit, we send
* those invalidation events to all other backends (via the SI message queue) * those invalidation events to all other backends (via the SI message queue)
* so that they can flush obsolete entries from their caches. * so that they can flush obsolete entries from their caches. This list
* definitely can't be processed until after we commit, otherwise the other
* backends won't see our updated tuples as good.
* *
* We do not need to register EVERY tuple operation in this way, just those * We do not need to register EVERY tuple operation in this way, just those
* on tuples in relations that have associated catcaches. Also, whenever * on tuples in relations that have associated catcaches. We do, however,
* we see an operation on a pg_class or pg_attribute tuple, we register * have to register every operation on every tuple that *could* be in a
* a relcache flush operation for the relation described by that tuple. * catcache, whether or not it currently is in our cache. Also, if the
* tuple is in a relation that has multiple catcaches, we need to register
* an invalidation message for each such catcache. catcache.c's
* PrepareToInvalidateCacheTuple() routine provides the knowledge of which
* catcaches may need invalidation for a given tuple.
*
* Also, whenever we see an operation on a pg_class or pg_attribute tuple,
* we register a relcache flush operation for the relation described by that
* tuple.
*
* We keep the relcache flush requests in lists separate from the catcache
* tuple flush requests. This allows us to issue all the pending catcache
* flushes before we issue relcache flushes, which saves us from loading
* a catcache tuple during relcache load only to flush it again right away.
* Also, we avoid queuing multiple relcache flush requests for the same
* relation, since a relcache flush is relatively expensive to do.
* (XXX is it worth testing likewise for duplicate catcache flush entries?
* Probably not.)
*
* All the request lists are kept in TopTransactionContext memory, since
* they need not live beyond the end of the current transaction.
* *
* *
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/cache/inval.c,v 1.44 2001/06/18 03:35:07 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/utils/cache/inval.c,v 1.45 2001/06/19 19:42:16 tgl Exp $
*
* Note - this code is real crufty... badly needs a rewrite to improve
* readability and portability. (Shouldn't assume Oid == Index, for example)
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -48,65 +67,34 @@ ...@@ -48,65 +67,34 @@
#include "storage/sinval.h" #include "storage/sinval.h"
#include "utils/catcache.h" #include "utils/catcache.h"
#include "utils/inval.h" #include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/relcache.h" #include "utils/relcache.h"
/* /*
* private invalidation structures * 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.
*/ */
typedef struct InvalidationChunk
typedef struct InvalidationUserData
{
struct InvalidationUserData *dataP[1]; /* VARIABLE LENGTH */
} InvalidationUserData; /* VARIABLE LENGTH STRUCTURE */
typedef struct InvalidationEntryData
{
InvalidationUserData *nextP;
InvalidationUserData userData; /* VARIABLE LENGTH ARRAY */
} InvalidationEntryData; /* VARIABLE LENGTH STRUCTURE */
typedef Pointer InvalidationEntry;
typedef InvalidationEntry LocalInvalid;
#define EmptyLocalInvalid NULL
typedef struct CatalogInvalidationData
{ {
Index cacheId; struct InvalidationChunk *next; /* list link */
Index hashIndex; int nitems; /* # items currently stored in chunk */
ItemPointerData pointerData; int maxitems; /* size of allocated array in this chunk */
} CatalogInvalidationData; SharedInvalidationMessage msgs[1]; /* VARIABLE LENGTH ARRAY */
} InvalidationChunk; /* VARIABLE LENGTH STRUCTURE */
typedef struct RelationInvalidationData typedef struct InvalidationListHeader
{ {
Oid relationId; InvalidationChunk *cclist; /* list of chunks holding catcache msgs */
Oid objectId; InvalidationChunk *rclist; /* list of chunks holding relcache msgs */
} RelationInvalidationData; } InvalidationListHeader;
typedef union AnyInvalidation
{
CatalogInvalidationData catalog;
RelationInvalidationData relation;
} AnyInvalidation;
typedef struct InvalidationMessageData
{
char kind;
AnyInvalidation any;
} InvalidationMessageData;
typedef InvalidationMessageData *InvalidationMessage;
/*
* variables and macros
*/
/* /*
* ---------------- * ----------------
* Invalidation info is divided into three parts. * Invalidation info is divided into three parts.
* 1) shared invalidation to be registered for all backends * 1) shared invalidation to be sent to all backends at commit
* 2) local invalidation for the transaction itself (actually, just * 2) local invalidation for the transaction itself (actually, just
* for the current command within the transaction) * for the current command within the transaction)
* 3) rollback information for the transaction itself (in case we abort) * 3) rollback information for the transaction itself (in case we abort)
...@@ -114,367 +102,297 @@ typedef InvalidationMessageData *InvalidationMessage; ...@@ -114,367 +102,297 @@ typedef InvalidationMessageData *InvalidationMessage;
*/ */
/* /*
* head of invalidation linked list for all backends * head of invalidation message list for all backends
* eaten by AtCommit_Cache() in CommitTransaction() * eaten by AtCommit_Cache() in CommitTransaction()
*/ */
static LocalInvalid InvalidForall = EmptyLocalInvalid; static InvalidationListHeader GlobalInvalidMsgs;
/* /*
* head of invalidation linked list for the backend itself * head of invalidation message list for the current command
* eaten by AtCommit_LocalCache() in CommandCounterIncrement() * eaten by AtCommit_LocalCache() in CommandCounterIncrement()
*/ */
static LocalInvalid InvalidLocal = EmptyLocalInvalid; static InvalidationListHeader LocalInvalidMsgs;
/* /*
* head of rollback linked list for the backend itself * head of rollback message list for abort-time processing
* eaten by AtAbort_Cache() in AbortTransaction() * eaten by AtAbort_Cache() in AbortTransaction()
*/ */
static LocalInvalid RollbackStack = EmptyLocalInvalid; static InvalidationListHeader RollbackMsgs;
static InvalidationEntry InvalidationEntryAllocate(uint16 size);
static void LocalInvalidInvalidate(LocalInvalid invalid,
void (*function) (InvalidationMessage),
bool freemember);
static LocalInvalid LocalInvalidRegister(LocalInvalid invalid,
InvalidationEntry entry);
static void DiscardInvalidStack(LocalInvalid *invalid);
static void InvalidationMessageRegisterSharedInvalid(InvalidationMessage message);
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* "local" invalidation support functions * Invalidation list support functions
*
* These three routines encapsulate processing of the "chunked"
* representation of what is logically just a list of messages.
* ---------------------------------------------------------------- * ----------------------------------------------------------------
*/ */
/* /*
* InvalidationEntryAllocate * AddInvalidationMessage
* Allocates an invalidation entry. * Add an invalidation message to a list (of chunks).
*/ *
static InvalidationEntry * Note that we do not pay any great attention to maintaining the original
InvalidationEntryAllocate(uint16 size) * ordering of the messages.
{
InvalidationEntryData *entryDataP;
entryDataP = (InvalidationEntryData *)
malloc(sizeof(char *) + size); /* XXX alignment */
if (entryDataP == NULL)
elog(ERROR, "Memory exhausted in InvalidationEntryAllocate");
entryDataP->nextP = NULL;
return (Pointer) &entryDataP->userData;
}
/*
* LocalInvalidRegister
* Link an invalidation entry into a chain of them. Really ugly
* coding here.
*/ */
static LocalInvalid static void
LocalInvalidRegister(LocalInvalid invalid, AddInvalidationMessage(InvalidationChunk **listHdr,
InvalidationEntry entry) SharedInvalidationMessage *msg)
{ {
Assert(PointerIsValid(entry)); InvalidationChunk *chunk = *listHdr;
((InvalidationUserData *) entry)->dataP[-1] = if (chunk == NULL)
(InvalidationUserData *) invalid; {
/* First time through; create initial chunk */
return entry; #define FIRSTCHUNKSIZE 16
chunk = (InvalidationChunk *)
MemoryContextAlloc(TopTransactionContext,
sizeof(InvalidationChunk) +
(FIRSTCHUNKSIZE-1) * sizeof(SharedInvalidationMessage));
chunk->nitems = 0;
chunk->maxitems = FIRSTCHUNKSIZE;
chunk->next = *listHdr;
*listHdr = chunk;
}
else if (chunk->nitems >= chunk->maxitems)
{
/* Need another chunk; double size of last chunk */
int chunksize = 2 * chunk->maxitems;
chunk = (InvalidationChunk *)
MemoryContextAlloc(TopTransactionContext,
sizeof(InvalidationChunk) +
(chunksize-1) * sizeof(SharedInvalidationMessage));
chunk->nitems = 0;
chunk->maxitems = chunksize;
chunk->next = *listHdr;
*listHdr = chunk;
}
/* Okay, add message to current chunk */
chunk->msgs[chunk->nitems] = *msg;
chunk->nitems++;
} }
/* /*
* LocalInvalidInvalidate * Free a list of inval message chunks.
* Processes, then frees all entries in a local cache *
* invalidation list unless freemember parameter is false. * 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 static void
LocalInvalidInvalidate(LocalInvalid invalid, FreeInvalidationMessageList(InvalidationChunk **listHdr)
void (*function) (InvalidationMessage),
bool freemember)
{ {
InvalidationEntryData *entryDataP; InvalidationChunk *chunk = *listHdr;
while (PointerIsValid(invalid))
{
entryDataP = (InvalidationEntryData *)
&((InvalidationUserData *) invalid)->dataP[-1];
if (PointerIsValid(function)) *listHdr = NULL;
(*function) ((InvalidationMessage) &entryDataP->userData);
invalid = (Pointer) entryDataP->nextP; while (chunk != NULL)
{
if (!freemember) InvalidationChunk *nextchunk = chunk->next;
continue;
/* help catch errors */
entryDataP->nextP = (InvalidationUserData *) NULL;
free((Pointer) entryDataP); pfree(chunk);
chunk = nextchunk;
} }
} }
static void /*
DiscardInvalidStack(LocalInvalid *invalid) * Process a list of invalidation messages.
{ *
LocalInvalid locinv; * This is a macro that executes the given code fragment for each message in
* a message chunk list. The fragment should refer to the message as *msg.
locinv = *invalid; */
*invalid = EmptyLocalInvalid; #define ProcessMessageList(listHdr, codeFragment) \
if (locinv) do { \
LocalInvalidInvalidate(locinv, InvalidationChunk *_chunk; \
(void (*) (InvalidationMessage)) NULL, for (_chunk = (listHdr); _chunk != NULL; _chunk = _chunk->next) \
true); { \
} int _cindex; \
for (_cindex = 0; _cindex < _chunk->nitems; _cindex++) \
{ \
SharedInvalidationMessage *msg = &_chunk->msgs[_cindex]; \
codeFragment; \
} \
} \
} while (0)
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* private support functions * Invalidation set support functions
*
* These routines understand about the division of a logical invalidation
* list into separate physical lists for catcache and relcache entries.
* ---------------------------------------------------------------- * ----------------------------------------------------------------
*/ */
/* --------------------------------
* CacheIdRegister.......
* RelationIdRegister....
* --------------------------------
*/
#ifdef INVALIDDEBUG
#define CacheIdRegisterSpecifiedLocalInvalid_DEBUG1 \
elog(DEBUG, "CacheIdRegisterSpecifiedLocalInvalid(%d, %d, [%d, %d])", \
cacheId, hashIndex, ItemPointerGetBlockNumber(pointer), \
ItemPointerGetOffsetNumber(pointer))
#define CacheIdRegisterLocalInvalid_DEBUG1 \
elog(DEBUG, "CacheIdRegisterLocalInvalid(%d, %d, [%d, %d])", \
cacheId, hashIndex, ItemPointerGetBlockNumber(pointer), \
ItemPointerGetOffsetNumber(pointer))
#define CacheIdRegisterLocalRollback_DEBUG1 \
elog(DEBUG, "CacheIdRegisterLocalRollback(%d, %d, [%d, %d])", \
cacheId, hashIndex, ItemPointerGetBlockNumber(pointer), \
ItemPointerGetOffsetNumber(pointer))
#else
#define CacheIdRegisterSpecifiedLocalInvalid_DEBUG1
#define CacheIdRegisterLocalInvalid_DEBUG1
#define CacheIdRegisterLocalRollback_DEBUG1
#endif /* INVALIDDEBUG */
/* /*
* CacheIdRegisterSpecifiedLocalInvalid * Add a catcache inval entry
*/ */
static LocalInvalid static void
CacheIdRegisterSpecifiedLocalInvalid(LocalInvalid invalid, AddCatcacheInvalidationMessage(InvalidationListHeader *hdr,
Index cacheId, int id, Index hashIndex,
Index hashIndex, ItemPointer tuplePtr, Oid dbId)
ItemPointer pointer)
{ {
InvalidationMessage message; SharedInvalidationMessage msg;
/*
* debugging stuff
*/
CacheIdRegisterSpecifiedLocalInvalid_DEBUG1;
/* msg.cc.id = (int16) id;
* create a message describing the system catalog tuple we wish to msg.cc.hashIndex = (uint16) hashIndex;
* invalidate. msg.cc.dbId = dbId;
*/ msg.cc.tuplePtr = *tuplePtr;
message = (InvalidationMessage) AddInvalidationMessage(&hdr->cclist, &msg);
InvalidationEntryAllocate(sizeof(InvalidationMessageData));
message->kind = 'c';
message->any.catalog.cacheId = cacheId;
message->any.catalog.hashIndex = hashIndex;
ItemPointerCopy(pointer, &message->any.catalog.pointerData);
/*
* Add message to linked list of unprocessed messages.
*/
invalid = LocalInvalidRegister(invalid, (InvalidationEntry) message);
return invalid;
} }
/* /*
* CacheIdRegisterLocalInvalid * Add a relcache inval entry
*/ */
static void static void
CacheIdRegisterLocalInvalid(int cacheId, AddRelcacheInvalidationMessage(InvalidationListHeader *hdr,
Index hashIndex, Oid dbId, Oid relId)
ItemPointer pointer)
{ {
SharedInvalidationMessage msg;
/*
* debugging stuff /* Don't add a duplicate item */
*/ /* We assume comparing relId is sufficient, needn't check dbId */
CacheIdRegisterLocalInvalid_DEBUG1; ProcessMessageList(hdr->rclist,
if (msg->rc.relId == relId) return);
/*
* Add message to InvalidForall linked list. /* OK, add the item */
*/ msg.rc.id = SHAREDINVALRELCACHE_ID;
InvalidForall = CacheIdRegisterSpecifiedLocalInvalid(InvalidForall, msg.rc.dbId = dbId;
cacheId, hashIndex, pointer); msg.rc.relId = relId;
AddInvalidationMessage(&hdr->rclist, &msg);
/*
* Add message to InvalidLocal linked list.
*/
InvalidLocal = CacheIdRegisterSpecifiedLocalInvalid(InvalidLocal,
cacheId, hashIndex, pointer);
} }
/* /*
* CacheIdRegisterLocalRollback * Reset an invalidation list to empty
*
* physicalFree may be set false if caller knows transaction is ending
*/ */
static void static void
CacheIdRegisterLocalRollback(int cacheId, DiscardInvalidationMessages(InvalidationListHeader *hdr, bool physicalFree)
Index hashIndex,
ItemPointer pointer)
{ {
if (physicalFree)
/* {
* debugging stuff /* Physically pfree the list data */
*/ FreeInvalidationMessageList(&hdr->cclist);
CacheIdRegisterLocalRollback_DEBUG1; FreeInvalidationMessageList(&hdr->rclist);
}
/* else
* Add message to RollbackStack linked list. {
*/ /* Assume the storage will go away at xact end, just reset pointers */
RollbackStack = CacheIdRegisterSpecifiedLocalInvalid( hdr->cclist = NULL;
RollbackStack, cacheId, hashIndex, pointer); hdr->rclist = NULL;
}
} }
/* /*
* RelationIdRegisterSpecifiedLocalInvalid * Execute the given function for all the messages in an invalidation list.
* The list is not altered.
*
* catcache entries are processed first, for reasons mentioned above.
*/ */
static LocalInvalid static void
RelationIdRegisterSpecifiedLocalInvalid(LocalInvalid invalid, ProcessInvalidationMessages(InvalidationListHeader *hdr,
Oid relationId, Oid objectId) void (*func) (SharedInvalidationMessage *msg))
{ {
InvalidationMessage message; ProcessMessageList(hdr->cclist, func(msg));
ProcessMessageList(hdr->rclist, func(msg));
/* }
* debugging stuff
*/
#ifdef INVALIDDEBUG
elog(DEBUG, "RelationRegisterSpecifiedLocalInvalid(%u, %u)", relationId,
objectId);
#endif /* defined(INVALIDDEBUG) */
/* /* ----------------------------------------------------------------
* create a message describing the relation descriptor we wish to * private support functions
* invalidate. * ----------------------------------------------------------------
*/ */
message = (InvalidationMessage)
InvalidationEntryAllocate(sizeof(InvalidationMessageData));
message->kind = 'r'; /*
message->any.relation.relationId = relationId; * RegisterCatcacheInvalidation
message->any.relation.objectId = objectId; *
* Register an invalidation event for an updated/deleted catcache entry.
/* * We insert the event into both GlobalInvalidMsgs (for transmission
* Add message to linked list of unprocessed messages. * to other backends at transaction commit) and LocalInvalidMsgs (for
* my local invalidation at end of command within xact).
*/ */
invalid = LocalInvalidRegister(invalid, (InvalidationEntry) message); static void
return invalid; RegisterCatcacheInvalidation(int cacheId,
Index hashIndex,
ItemPointer tuplePtr,
Oid dbId)
{
AddCatcacheInvalidationMessage(&GlobalInvalidMsgs,
cacheId, hashIndex, tuplePtr, dbId);
AddCatcacheInvalidationMessage(&LocalInvalidMsgs,
cacheId, hashIndex, tuplePtr, dbId);
} }
/* /*
* RelationIdRegisterLocalInvalid * RegisterRelcacheInvalidation
*
* As above, but register a relcache invalidation event.
*/ */
static void static void
RelationIdRegisterLocalInvalid(Oid relationId, Oid objectId) RegisterRelcacheInvalidation(Oid dbId, Oid relId)
{ {
AddRelcacheInvalidationMessage(&GlobalInvalidMsgs,
/* dbId, relId);
* debugging stuff AddRelcacheInvalidationMessage(&LocalInvalidMsgs,
*/ dbId, relId);
#ifdef INVALIDDEBUG
elog(DEBUG, "RelationRegisterLocalInvalid(%u, %u)", relationId,
objectId);
#endif /* defined(INVALIDDEBUG) */
/*
* Add message to InvalidForall linked list.
*/
InvalidForall = RelationIdRegisterSpecifiedLocalInvalid(InvalidForall,
relationId, objectId);
/*
* Add message to InvalidLocal linked list.
*/
InvalidLocal = RelationIdRegisterSpecifiedLocalInvalid(InvalidLocal,
relationId, objectId);
} }
/* /*
* RelationIdRegisterLocalRollback * RegisterCatcacheRollback
*
* Register an invalidation event for an inserted catcache entry.
* This only needs to be flushed out of my local catcache, if I abort.
*/ */
static void static void
RelationIdRegisterLocalRollback(Oid relationId, Oid objectId) RegisterCatcacheRollback(int cacheId,
Index hashIndex,
ItemPointer tuplePtr,
Oid dbId)
{ {
AddCatcacheInvalidationMessage(&RollbackMsgs,
/* cacheId, hashIndex, tuplePtr, dbId);
* debugging stuff
*/
#ifdef INVALIDDEBUG
elog(DEBUG, "RelationRegisterLocalRollback(%u, %u)", relationId,
objectId);
#endif /* defined(INVALIDDEBUG) */
/*
* Add message to RollbackStack linked list.
*/
RollbackStack = RelationIdRegisterSpecifiedLocalInvalid(
RollbackStack, relationId, objectId);
} }
/* /*
* CacheIdInvalidate * RegisterRelcacheRollback
* *
* This routine can invalidate a tuple in a system catalog cache * As above, but register a relcache invalidation event.
* or a cached relation descriptor. You pay your money and you
* take your chances...
*/ */
#ifdef INVALIDDEBUG
#define CacheIdInvalidate_DEBUG1 \
elog(DEBUG, "CacheIdInvalidate(%d, %d, 0x%x[%d])", cacheId, hashIndex,\
pointer, ItemPointerIsValid(pointer))
#else
#define CacheIdInvalidate_DEBUG1
#endif /* defined(INVALIDDEBUG) */
static void static void
CacheIdInvalidate(Index cacheId, RegisterRelcacheRollback(Oid dbId, Oid relId)
Index hashIndex,
ItemPointer pointer)
{ {
AddRelcacheInvalidationMessage(&RollbackMsgs,
dbId, relId);
}
/* /*
* assume that if the item pointer is valid, then we are invalidating * LocalExecuteInvalidationMessage
* an item in the specified system catalog cache. *
* Process a single invalidation message (which could be either type).
* Only the local caches are flushed; this does not transmit the message
* to other backends.
*/ */
if (ItemPointerIsValid(pointer)) static void
LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg)
{
if (msg->id >= 0)
{ {
CatalogCacheIdInvalidate(cacheId, hashIndex, pointer); if (msg->cc.dbId == MyDatabaseId || msg->cc.dbId == 0)
return; CatalogCacheIdInvalidate(msg->cc.id,
msg->cc.hashIndex,
&msg->cc.tuplePtr);
} }
else if (msg->id == SHAREDINVALRELCACHE_ID)
CacheIdInvalidate_DEBUG1;
/*
* if the cacheId is the oid of any of the following system relations,
* then assume we are invalidating a relation descriptor
*/
if (cacheId == RelOid_pg_class)
{ {
RelationIdInvalidateRelationCacheByRelationId(hashIndex); if (msg->rc.dbId == MyDatabaseId || msg->rc.dbId == 0)
return; RelationIdInvalidateRelationCacheByRelationId(msg->rc.relId);
} }
else
if (cacheId == RelOid_pg_attribute)
{ {
RelationIdInvalidateRelationCacheByRelationId(hashIndex); elog(FATAL, "ExecuteInvalidationMessage: bogus message id %d",
return; msg->id);
} }
/*
* Yow! the caller asked us to invalidate something else.
*/
elog(FATAL, "CacheIdInvalidate: cacheId=%d relation id?", cacheId);
} }
/* /*
...@@ -496,299 +414,160 @@ InvalidateSystemCaches(void) ...@@ -496,299 +414,160 @@ InvalidateSystemCaches(void)
} }
/* /*
* InvalidationMessageRegisterSharedInvalid * PrepareForTupleInvalidation
*/ * Invoke functions for the tuple which register invalidation
#ifdef INVALIDDEBUG * of catalog/relation cache.
#define InvalidationMessageRegisterSharedInvalid_DEBUG1 \ */
elog(DEBUG,\
"InvalidationMessageRegisterSharedInvalid(c, %d, %d, [%d, %d])",\
message->any.catalog.cacheId,\
message->any.catalog.hashIndex,\
ItemPointerGetBlockNumber(&message->any.catalog.pointerData),\
ItemPointerGetOffsetNumber(&message->any.catalog.pointerData))
#define InvalidationMessageRegisterSharedInvalid_DEBUG2 \
elog(DEBUG, \
"InvalidationMessageRegisterSharedInvalid(r, %u, %u)", \
message->any.relation.relationId, \
message->any.relation.objectId)
#else
#define InvalidationMessageRegisterSharedInvalid_DEBUG1
#define InvalidationMessageRegisterSharedInvalid_DEBUG2
#endif /* INVALIDDEBUG */
static void
InvalidationMessageRegisterSharedInvalid(InvalidationMessage message)
{
Assert(PointerIsValid(message));
switch (message->kind)
{
case 'c': /* cached system catalog tuple */
InvalidationMessageRegisterSharedInvalid_DEBUG1;
RegisterSharedInvalid(message->any.catalog.cacheId,
message->any.catalog.hashIndex,
&message->any.catalog.pointerData);
break;
case 'r': /* cached relation descriptor */
InvalidationMessageRegisterSharedInvalid_DEBUG2;
RegisterSharedInvalid(message->any.relation.relationId,
message->any.relation.objectId,
(ItemPointer) NULL);
break;
default:
elog(FATAL,
"InvalidationMessageRegisterSharedInvalid: `%c' kind",
message->kind);
}
}
/*
* InvalidationMessageCacheInvalidate
*/
#ifdef INVALIDDEBUG
#define InvalidationMessageCacheInvalidate_DEBUG1 \
elog(DEBUG, "InvalidationMessageCacheInvalidate(c, %d, %d, [%d, %d])",\
message->any.catalog.cacheId,\
message->any.catalog.hashIndex,\
ItemPointerGetBlockNumber(&message->any.catalog.pointerData),\
ItemPointerGetOffsetNumber(&message->any.catalog.pointerData))
#define InvalidationMessageCacheInvalidate_DEBUG2 \
elog(DEBUG, "InvalidationMessageCacheInvalidate(r, %u, %u)", \
message->any.relation.relationId, \
message->any.relation.objectId)
#else
#define InvalidationMessageCacheInvalidate_DEBUG1
#define InvalidationMessageCacheInvalidate_DEBUG2
#endif /* defined(INVALIDDEBUG) */
static void static void
InvalidationMessageCacheInvalidate(InvalidationMessage message) PrepareForTupleInvalidation(Relation relation, HeapTuple tuple,
void (*CacheIdRegisterFunc) (int, Index,
ItemPointer, Oid),
void (*RelationIdRegisterFunc) (Oid, Oid))
{ {
Assert(PointerIsValid(message)); Oid tupleRelId;
Oid relationId;
switch (message->kind)
{
case 'c': /* cached system catalog tuple */
InvalidationMessageCacheInvalidate_DEBUG1;
CacheIdInvalidate(message->any.catalog.cacheId,
message->any.catalog.hashIndex,
&message->any.catalog.pointerData);
break;
case 'r': /* cached relation descriptor */
InvalidationMessageCacheInvalidate_DEBUG2;
CacheIdInvalidate(message->any.relation.relationId,
message->any.relation.objectId,
(ItemPointer) NULL);
break;
default: if (IsBootstrapProcessingMode())
elog(FATAL, "InvalidationMessageCacheInvalidate: `%c' kind", return;
message->kind);
}
}
/* /*
* PrepareToInvalidateRelationCache * We only need to worry about invalidation for tuples that are in
* system relations; user-relation tuples are never in catcaches and
* can't affect the relcache either.
*/ */
static void if (!IsSystemRelationName(NameStr(RelationGetForm(relation)->relname)))
PrepareToInvalidateRelationCache(Relation relation, return;
HeapTuple tuple,
void (*function) (Oid, Oid))
{
Oid relationId;
Oid objectId;
/* /*
* get the relation object id * First let the catcache do its thing
*/ */
relationId = RelationGetRelid(relation); PrepareToInvalidateCacheTuple(relation, tuple,
CacheIdRegisterFunc);
/* /*
* is it one of the ones we need to send an SI message for? * Now, is this tuple one of the primary definers of a relcache entry?
*/ */
if (relationId == RelOid_pg_class) tupleRelId = RelationGetRelid(relation);
objectId = tuple->t_data->t_oid;
else if (relationId == RelOid_pg_attribute) if (tupleRelId == RelOid_pg_class)
objectId = ((Form_pg_attribute) GETSTRUCT(tuple))->attrelid; relationId = tuple->t_data->t_oid;
else if (tupleRelId == RelOid_pg_attribute)
relationId = ((Form_pg_attribute) GETSTRUCT(tuple))->attrelid;
else else
return; return;
/* /*
* register the relcache-invalidation action in the appropriate list * Yes. We need to register a relcache invalidation event for the
*/ * relation identified by relationId.
Assert(PointerIsValid(function)); *
* KLUGE ALERT: we always send the relcache event with MyDatabaseId,
(*function) (relationId, objectId); * even if the rel in question is shared. This essentially means that
* only backends in this same database will react to the relcache flush
* request. This is in fact appropriate, since only those backends could
* see our pg_class or pg_attribute change anyway. It looks a bit ugly
* though.
*/
(*RelationIdRegisterFunc) (MyDatabaseId, relationId);
} }
/* ----------------------------------------------------------------
* public functions
* ----------------------------------------------------------------
*/
/* /*
* DiscardInvalid * AcceptInvalidationMessages
* Causes the invalidated cache state to be discarded. * Read and process invalidation messages from the shared invalidation
* message queue.
* *
* Note: * Note:
* This should be called as the first step in processing a transaction. * This should be called as the first step in processing a transaction.
*/ */
void void
DiscardInvalid(void) AcceptInvalidationMessages(void)
{ {
ReceiveSharedInvalidMessages(LocalExecuteInvalidationMessage,
/* InvalidateSystemCaches);
* debugging stuff
*/
#ifdef INVALIDDEBUG
elog(DEBUG, "DiscardInvalid called");
#endif /* defined(INVALIDDEBUG) */
InvalidateSharedInvalid(CacheIdInvalidate, InvalidateSystemCaches);
} }
/* /*
* RegisterInvalid * AtEOXactInvalidationMessages
* Causes registration of invalidated state with other backends iff true. * Process queued-up invalidation messages at end of transaction.
*
* If isCommit, we must send out the messages in our GlobalInvalidMsgs list
* to the shared invalidation message queue. Note that these will be read
* not only by other backends, but also by our own backend at the next
* transaction start (via AcceptInvalidationMessages). Therefore, it's okay
* to discard any pending LocalInvalidMsgs, since these will be redundant
* with the global list.
*
* If not isCommit, we are aborting, and must locally process the messages
* in our RollbackMsgs list. No messages need be sent to other backends,
* since they'll not have seen our changed tuples anyway.
*
* In any case, reset the various lists to empty. We need not physically
* free memory here, since TopTransactionContext is about to be emptied
* anyway.
* *
* Note: * Note:
* This should be called as the last step in processing a transaction. * This should be called as the last step in processing a transaction.
*/ */
void void
RegisterInvalid(bool send) AtEOXactInvalidationMessages(bool isCommit)
{ {
LocalInvalid invalid; if (isCommit)
/*
* debugging stuff
*/
#ifdef INVALIDDEBUG
elog(DEBUG, "RegisterInvalid(%d) called", send);
#endif /* defined(INVALIDDEBUG) */
/*
* Process and free the current list of inval messages.
*/
DiscardInvalidStack(&InvalidLocal);
if (send)
{ {
DiscardInvalidStack(&RollbackStack); ProcessInvalidationMessages(&GlobalInvalidMsgs,
invalid = InvalidForall; SendSharedInvalidMessage);
InvalidForall = EmptyLocalInvalid; /* clear InvalidForall */
LocalInvalidInvalidate(invalid, InvalidationMessageRegisterSharedInvalid, true);
} }
else else
{ {
DiscardInvalidStack(&InvalidForall); ProcessInvalidationMessages(&RollbackMsgs,
invalid = RollbackStack; LocalExecuteInvalidationMessage);
RollbackStack = EmptyLocalInvalid; /* clear RollbackStack */
LocalInvalidInvalidate(invalid, InvalidationMessageCacheInvalidate, true);
} }
DiscardInvalidationMessages(&GlobalInvalidMsgs, false);
DiscardInvalidationMessages(&LocalInvalidMsgs, false);
DiscardInvalidationMessages(&RollbackMsgs, false);
} }
/* /*
* ImmediateLocalInvalidation * CommandEndInvalidationMessages
* Causes invalidation immediately for the next command of the transaction. * Process queued-up invalidation messages at end of one command
* in a transaction.
*
* Here, we send no messages to the shared queue, since we don't know yet if
* we will commit. But we do need to locally process the LocalInvalidMsgs
* list, so as to flush our caches of any tuples we have outdated in the
* current command.
*
* The isCommit = false case is not currently used, but may someday be
* needed to support rollback to a savepoint within a transaction.
* (I suspect it needs more work first --- tgl.)
* *
* Note: * Note:
* This should be called during CommandCounterIncrement(), * This should be called during CommandCounterIncrement(),
* after we have advanced the command ID. * after we have advanced the command ID.
*/ */
void void
ImmediateLocalInvalidation(bool send) CommandEndInvalidationMessages(bool isCommit)
{ {
LocalInvalid invalid; if (isCommit)
/*
* debugging stuff
*/
#ifdef INVALIDDEBUG
elog(DEBUG, "ImmediateLocalInvalidation(%d) called", send);
#endif /* defined(INVALIDDEBUG) */
/*
* Process and free the local list of inval messages.
*/
if (send)
{ {
invalid = InvalidLocal; ProcessInvalidationMessages(&LocalInvalidMsgs,
InvalidLocal = EmptyLocalInvalid; /* clear InvalidLocal */ LocalExecuteInvalidationMessage);
LocalInvalidInvalidate(invalid, InvalidationMessageCacheInvalidate, true);
} }
else else
{ {
ProcessInvalidationMessages(&RollbackMsgs,
/* LocalExecuteInvalidationMessage);
* This may be used for rollback to a savepoint. Don't clear
* InvalidForall and RollbackStack here.
*/
DiscardInvalidStack(&InvalidLocal);
invalid = RollbackStack;
LocalInvalidInvalidate(invalid, InvalidationMessageCacheInvalidate, false);
} }
}
/*
* PrepareForTupleInvalidation
* Invoke functions for the tuple which register invalidation
* of catalog/relation cache.
* Note:
* Assumes object id is valid.
* Assumes tuple is valid.
*/
#ifdef INVALIDDEBUG
#define PrepareForTupleInvalidation_DEBUG1 \
elog(DEBUG, "%s(%s, [%d,%d])", \
funcname,\
RelationGetPhysicalRelationName(relation), \
ItemPointerGetBlockNumber(&tuple->t_self), \
ItemPointerGetOffsetNumber(&tuple->t_self))
#else
#define PrepareForTupleInvalidation_DEBUG1
#endif /* defined(INVALIDDEBUG) */
static void
PrepareForTupleInvalidation(Relation relation, HeapTuple tuple,
void (*CacheIdRegisterFunc) (int, Index,
ItemPointer),
void (*RelationIdRegisterFunc) (Oid, Oid),
const char *funcname)
{
/*
* sanity checks
*/
Assert(RelationIsValid(relation));
Assert(HeapTupleIsValid(tuple));
if (IsBootstrapProcessingMode())
return;
/* /*
* We only need to worry about invalidation for tuples that are in * LocalInvalidMsgs list is not interesting anymore, so flush it
* system relations; user-relation tuples are never in catcaches and * (for real). Do *not* clear GlobalInvalidMsgs or RollbackMsgs.
* can't affect the relcache either.
*/ */
if (!IsSystemRelationName(NameStr(RelationGetForm(relation)->relname))) DiscardInvalidationMessages(&LocalInvalidMsgs, true);
return;
/*
* debugging stuff
*/
PrepareForTupleInvalidation_DEBUG1;
PrepareToInvalidateCacheTuple(relation, tuple,
CacheIdRegisterFunc);
PrepareToInvalidateRelationCache(relation, tuple,
RelationIdRegisterFunc);
} }
/* /*
...@@ -800,9 +579,8 @@ void ...@@ -800,9 +579,8 @@ void
RelationInvalidateHeapTuple(Relation relation, HeapTuple tuple) RelationInvalidateHeapTuple(Relation relation, HeapTuple tuple)
{ {
PrepareForTupleInvalidation(relation, tuple, PrepareForTupleInvalidation(relation, tuple,
CacheIdRegisterLocalInvalid, RegisterCatcacheInvalidation,
RelationIdRegisterLocalInvalid, RegisterRelcacheInvalidation);
"RelationInvalidateHeapTuple");
} }
/* /*
...@@ -814,7 +592,6 @@ void ...@@ -814,7 +592,6 @@ void
RelationMark4RollbackHeapTuple(Relation relation, HeapTuple tuple) RelationMark4RollbackHeapTuple(Relation relation, HeapTuple tuple)
{ {
PrepareForTupleInvalidation(relation, tuple, PrepareForTupleInvalidation(relation, tuple,
CacheIdRegisterLocalRollback, RegisterCatcacheRollback,
RelationIdRegisterLocalRollback, RegisterRelcacheRollback);
"RelationMark4RollbackHeapTuple");
} }
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $Id: sinval.h,v 1.18 2001/02/26 00:50:08 tgl Exp $ * $Id: sinval.h,v 1.19 2001/06/19 19:42:16 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -17,15 +17,61 @@ ...@@ -17,15 +17,61 @@
#include "storage/itemptr.h" #include "storage/itemptr.h"
#include "storage/spin.h" #include "storage/spin.h"
/*
* We currently support two types of shared-invalidation messages: one that
* invalidates an entry in a catcache, and one that invalidates a relcache
* entry. More types could be added if needed. The message type is
* identified by the first "int16" field of the message struct. Zero or
* positive means a catcache inval message (and also serves as the catcache
* ID field). -1 means a relcache inval message. Other negative values
* are available to identify other inval message types.
*
* Shared-inval events are initially driven by detecting tuple inserts,
* updates and deletions in system catalogs (see RelationInvalidateHeapTuple
* and RelationMark4RollbackHeapTuple). Note that some system catalogs have
* multiple caches on them (with different indexes). On detecting a tuple
* invalidation in such a catalog, a separate catcache inval message must be
* generated for each of its caches. The catcache inval message carries the
* hash index for the target tuple, so that the catcache only needs to search
* one hash chain not all its chains. Of course this assumes that all the
* backends are using identical hashing code, but that should be OK.
*/
typedef struct
{
int16 id; /* cache ID --- must be first */
uint16 hashIndex; /* hashchain index within this catcache */
Oid dbId; /* database ID, or 0 if a shared relation */
ItemPointerData tuplePtr; /* tuple identifier in cached relation */
} SharedInvalCatcacheMsg;
#define SHAREDINVALRELCACHE_ID (-1)
typedef struct
{
int16 id; /* type field --- must be first */
Oid dbId; /* database ID, or 0 if a shared relation */
Oid relId; /* relation ID */
} SharedInvalRelcacheMsg;
typedef union
{
int16 id; /* type field --- must be first */
SharedInvalCatcacheMsg cc;
SharedInvalRelcacheMsg rc;
} SharedInvalidationMessage;
extern SPINLOCK SInvalLock; extern SPINLOCK SInvalLock;
extern int SInvalShmemSize(int maxBackends); extern int SInvalShmemSize(int maxBackends);
extern void CreateSharedInvalidationState(int maxBackends); extern void CreateSharedInvalidationState(int maxBackends);
extern void InitBackendSharedInvalidationState(void); extern void InitBackendSharedInvalidationState(void);
extern void RegisterSharedInvalid(int cacheId, Index hashIndex, extern void SendSharedInvalidMessage(SharedInvalidationMessage *msg);
ItemPointer pointer); extern void ReceiveSharedInvalidMessages(
extern void InvalidateSharedInvalid(void (*invalFunction) (), void (*invalFunction) (SharedInvalidationMessage *msg),
void (*resetFunction) ()); void (*resetFunction) (void));
extern bool DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself); extern bool DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself);
extern bool TransactionIdIsInProgress(TransactionId xid); extern bool TransactionIdIsInProgress(TransactionId xid);
......
...@@ -7,15 +7,15 @@ ...@@ -7,15 +7,15 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $Id: sinvaladt.h,v 1.26 2001/03/22 04:01:09 momjian Exp $ * $Id: sinvaladt.h,v 1.27 2001/06/19 19:42:16 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
#ifndef SINVALADT_H #ifndef SINVALADT_H
#define SINVALADT_H #define SINVALADT_H
#include "storage/itemptr.h"
#include "storage/shmem.h" #include "storage/shmem.h"
#include "storage/sinval.h"
/* /*
* The shared cache invalidation manager is responsible for transmitting * The shared cache invalidation manager is responsible for transmitting
...@@ -45,6 +45,9 @@ ...@@ -45,6 +45,9 @@
* large so that we don't need to do this often. It must be a multiple of * large so that we don't need to do this often. It must be a multiple of
* MAXNUMMESSAGES so that the existing circular-buffer entries don't need * MAXNUMMESSAGES so that the existing circular-buffer entries don't need
* to be moved when we do it. * to be moved when we do it.
*
* The struct type SharedInvalidationMessage, defining the contents of
* a single message, is defined in sinval.h.
*/ */
...@@ -61,15 +64,6 @@ ...@@ -61,15 +64,6 @@
#define MAXNUMMESSAGES 4096 #define MAXNUMMESSAGES 4096
#define MSGNUMWRAPAROUND (MAXNUMMESSAGES * 4096) #define MSGNUMWRAPAROUND (MAXNUMMESSAGES * 4096)
/* The content of one shared-invalidation message */
typedef struct SharedInvalidData
{
int cacheId; /* XXX */
Index hashIndex;
ItemPointerData pointerData;
} SharedInvalidData;
typedef SharedInvalidData *SharedInvalid;
/* Per-backend state in shared invalidation structure */ /* Per-backend state in shared invalidation structure */
typedef struct ProcState typedef struct ProcState
...@@ -83,7 +77,6 @@ typedef struct ProcState ...@@ -83,7 +77,6 @@ typedef struct ProcState
/* Shared cache invalidation memory segment */ /* Shared cache invalidation memory segment */
typedef struct SISeg typedef struct SISeg
{ {
/* /*
* General state information * General state information
*/ */
...@@ -96,7 +89,7 @@ typedef struct SISeg ...@@ -96,7 +89,7 @@ typedef struct SISeg
/* /*
* Circular buffer holding shared-inval messages * Circular buffer holding shared-inval messages
*/ */
SharedInvalidData buffer[MAXNUMMESSAGES]; SharedInvalidationMessage buffer[MAXNUMMESSAGES];
/* /*
* Per-backend state info. * Per-backend state info.
...@@ -117,9 +110,9 @@ extern SISeg *shmInvalBuffer; /* pointer to the shared inval buffer */ ...@@ -117,9 +110,9 @@ extern SISeg *shmInvalBuffer; /* pointer to the shared inval buffer */
extern void SIBufferInit(int maxBackends); extern void SIBufferInit(int maxBackends);
extern int SIBackendInit(SISeg *segP); extern int SIBackendInit(SISeg *segP);
extern bool SIInsertDataEntry(SISeg *segP, SharedInvalidData *data); extern bool SIInsertDataEntry(SISeg *segP, SharedInvalidationMessage *data);
extern int SIGetDataEntry(SISeg *segP, int backendId, extern int SIGetDataEntry(SISeg *segP, int backendId,
SharedInvalidData *data); SharedInvalidationMessage *data);
extern void SIDelExpiredDataEntries(SISeg *segP); extern void SIDelExpiredDataEntries(SISeg *segP);
#endif /* SINVALADT_H */ #endif /* SINVALADT_H */
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $Id: catcache.h,v 1.33 2001/06/18 03:35:07 tgl Exp $ * $Id: catcache.h,v 1.34 2001/06/19 19:42:16 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -36,6 +36,7 @@ typedef struct catcache ...@@ -36,6 +36,7 @@ typedef struct catcache
char *cc_relname; /* name of relation the tuples come from */ char *cc_relname; /* name of relation the tuples come from */
char *cc_indname; /* name of index matching cache keys */ char *cc_indname; /* name of index matching cache keys */
int cc_reloidattr; /* AttrNumber of relation OID, or 0 */ int cc_reloidattr; /* AttrNumber of relation OID, or 0 */
bool cc_relisshared; /* is relation shared? */
TupleDesc cc_tupdesc; /* tuple descriptor (copied from reldesc) */ TupleDesc cc_tupdesc; /* tuple descriptor (copied from reldesc) */
int cc_ntup; /* # of tuples currently in this cache */ int cc_ntup; /* # of tuples currently in this cache */
int cc_size; /* # of hash buckets in this cache */ int cc_size; /* # of hash buckets in this cache */
...@@ -99,6 +100,6 @@ extern void CatalogCacheIdInvalidate(int cacheId, Index hashIndex, ...@@ -99,6 +100,6 @@ extern void CatalogCacheIdInvalidate(int cacheId, Index hashIndex,
ItemPointer pointer); ItemPointer pointer);
extern void PrepareToInvalidateCacheTuple(Relation relation, extern void PrepareToInvalidateCacheTuple(Relation relation,
HeapTuple tuple, HeapTuple tuple,
void (*function) (int, Index, ItemPointer)); void (*function) (int, Index, ItemPointer, Oid));
#endif /* CATCACHE_H */ #endif /* CATCACHE_H */
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $Id: inval.h,v 1.19 2001/01/24 19:43:28 momjian Exp $ * $Id: inval.h,v 1.20 2001/06/19 19:42:16 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -16,11 +16,12 @@ ...@@ -16,11 +16,12 @@
#include "access/htup.h" #include "access/htup.h"
extern void DiscardInvalid(void);
extern void RegisterInvalid(bool send); extern void AcceptInvalidationMessages(void);
extern void ImmediateLocalInvalidation(bool send); extern void AtEOXactInvalidationMessages(bool isCommit);
extern void CommandEndInvalidationMessages(bool isCommit);
extern void RelationInvalidateHeapTuple(Relation relation, HeapTuple tuple); extern void RelationInvalidateHeapTuple(Relation relation, HeapTuple tuple);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment