1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* sinval.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* POSTGRES shared cache invalidation communication code.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
* Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
1999-09-24 02:25:33 +02:00
|
|
|
* $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.19 1999/09/24 00:24:35 tgl Exp $
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
1997-09-07 07:04:48 +02:00
|
|
|
/* #define INVALIDDEBUG 1 */
|
1996-07-09 08:22:35 +02:00
|
|
|
|
1996-11-03 06:08:01 +01:00
|
|
|
#include <sys/types.h>
|
1996-10-31 06:58:01 +01:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
#include "postgres.h"
|
|
|
|
|
1996-11-06 07:52:23 +01:00
|
|
|
#include "storage/backendid.h"
|
1999-09-24 02:25:33 +02:00
|
|
|
#include "storage/proc.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
#include "storage/sinval.h"
|
|
|
|
#include "storage/sinvaladt.h"
|
1999-09-24 02:25:33 +02:00
|
|
|
#include "utils/tqual.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
|
1997-09-08 04:41:22 +02:00
|
|
|
SPINLOCK SInvalLock = (SPINLOCK) NULL;
|
1996-07-09 08:22:35 +02:00
|
|
|
|
|
|
|
/****************************************************************************/
|
1999-05-28 19:03:31 +02:00
|
|
|
/* CreateSharedInvalidationState() Create a buffer segment */
|
1997-09-07 07:04:48 +02:00
|
|
|
/* */
|
|
|
|
/* should be called only by the POSTMASTER */
|
1996-07-09 08:22:35 +02:00
|
|
|
/****************************************************************************/
|
|
|
|
void
|
1999-05-28 19:03:31 +02:00
|
|
|
CreateSharedInvalidationState(IPCKey key, int maxBackends)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
int status;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/* SInvalLock gets set in spin.c, during spinlock init */
|
1999-05-28 19:03:31 +02:00
|
|
|
status = SISegmentInit(true, IPCKeyGetSIBufferMemoryBlock(key),
|
|
|
|
maxBackends);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (status == -1)
|
|
|
|
elog(FATAL, "CreateSharedInvalidationState: failed segment init");
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/****************************************************************************/
|
1999-09-06 21:37:38 +02:00
|
|
|
/* AttachSharedInvalidationState(key) Attach to existing buffer segment */
|
1997-09-07 07:04:48 +02:00
|
|
|
/* */
|
1999-09-06 21:37:38 +02:00
|
|
|
/* should be called by each backend during startup */
|
1996-07-09 08:22:35 +02:00
|
|
|
/****************************************************************************/
|
|
|
|
void
|
|
|
|
AttachSharedInvalidationState(IPCKey key)
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
int status;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (key == PrivateIPCKey)
|
|
|
|
{
|
1999-05-28 19:03:31 +02:00
|
|
|
CreateSharedInvalidationState(key, 16);
|
1997-09-07 07:04:48 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* SInvalLock gets set in spin.c, during spinlock init */
|
1999-05-28 19:03:31 +02:00
|
|
|
status = SISegmentInit(false, IPCKeyGetSIBufferMemoryBlock(key), 0);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (status == -1)
|
|
|
|
elog(FATAL, "AttachSharedInvalidationState: failed segment init");
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
1999-09-06 21:37:38 +02:00
|
|
|
/*
|
|
|
|
* InitSharedInvalidationState
|
|
|
|
* Initialize new backend's state info in buffer segment.
|
|
|
|
* Must be called after AttachSharedInvalidationState().
|
|
|
|
*/
|
1996-07-09 08:22:35 +02:00
|
|
|
void
|
1996-11-10 04:06:38 +01:00
|
|
|
InitSharedInvalidationState(void)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
SpinAcquire(SInvalLock);
|
|
|
|
if (!SIBackendInit(shmInvalBuffer))
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
SpinRelease(SInvalLock);
|
|
|
|
elog(FATAL, "Backend cache invalidation initialization failed");
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
SpinRelease(SInvalLock);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
1999-05-25 18:15:34 +02:00
|
|
|
* RegisterSharedInvalid
|
1999-09-06 21:37:38 +02:00
|
|
|
* Add a shared-cache-invalidation message to the global SI message queue.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
* Note:
|
1997-09-07 07:04:48 +02:00
|
|
|
* Assumes hash index is valid.
|
|
|
|
* Assumes item pointer is valid.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
1997-09-07 07:04:48 +02:00
|
|
|
RegisterSharedInvalid(int cacheId, /* XXX */
|
|
|
|
Index hashIndex,
|
|
|
|
ItemPointer pointer)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1999-09-06 21:37:38 +02:00
|
|
|
SharedInvalidData newInvalid;
|
|
|
|
bool insertOK;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
1999-09-06 21:37:38 +02:00
|
|
|
insertOK = SIInsertDataEntry(shmInvalBuffer, &newInvalid);
|
1997-09-07 07:04:48 +02:00
|
|
|
SpinRelease(SInvalLock);
|
1999-09-06 21:37:38 +02:00
|
|
|
if (! insertOK)
|
|
|
|
elog(NOTICE, "RegisterSharedInvalid: SI buffer overflow");
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
1999-05-25 18:15:34 +02:00
|
|
|
* InvalidateSharedInvalid
|
1999-09-06 21:37:38 +02:00
|
|
|
* Process shared-cache-invalidation messages waiting for this backend
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
1999-09-04 20:36:45 +02:00
|
|
|
InvalidateSharedInvalid(void (*invalFunction) (),
|
|
|
|
void (*resetFunction) ())
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1999-09-04 20:36:45 +02:00
|
|
|
SharedInvalidData data;
|
|
|
|
int getResult;
|
|
|
|
bool gotMessage = false;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1999-09-04 20:36:45 +02:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
SpinAcquire(SInvalLock);
|
|
|
|
getResult = SIGetDataEntry(shmInvalBuffer, MyBackendId, &data);
|
|
|
|
SpinRelease(SInvalLock);
|
|
|
|
if (getResult == 0)
|
|
|
|
break; /* nothing more to do */
|
|
|
|
if (getResult < 0)
|
|
|
|
{
|
|
|
|
/* got a reset message */
|
|
|
|
elog(NOTICE, "InvalidateSharedInvalid: cache state reset");
|
|
|
|
resetFunction();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* got a normal data message */
|
|
|
|
invalFunction(data.cacheId,
|
|
|
|
data.hashIndex,
|
|
|
|
&data.pointerData);
|
|
|
|
}
|
|
|
|
gotMessage = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we got any messages, try to release dead messages */
|
|
|
|
if (gotMessage)
|
|
|
|
{
|
|
|
|
SpinAcquire(SInvalLock);
|
|
|
|
SIDelExpiredDataEntries(shmInvalBuffer);
|
|
|
|
SpinRelease(SInvalLock);
|
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1999-09-24 02:25:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************************/
|
|
|
|
/* Functions that need to scan the PROC structures of all running backends. */
|
|
|
|
/* It's a bit strange to keep these in sinval.c, since they don't have any */
|
|
|
|
/* direct relationship to shared-cache invalidation. But the procState */
|
|
|
|
/* array in the SI segment is the only place in the system where we have */
|
|
|
|
/* an array of per-backend data, so it is the most convenient place to keep */
|
|
|
|
/* pointers to the backends' PROC structures. We used to implement these */
|
|
|
|
/* functions with a slow, ugly search through the ShmemIndex hash table --- */
|
|
|
|
/* now they are simple loops over the SI ProcState array. */
|
|
|
|
/****************************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DatabaseHasActiveBackends -- are there any backends running in the given DB
|
|
|
|
*
|
|
|
|
* This function is used to interlock DROP DATABASE against there being
|
|
|
|
* any active backends in the target DB --- dropping the DB while active
|
|
|
|
* backends remain would be a Bad Thing. Note that we cannot detect here
|
|
|
|
* the possibility of a newly-started backend that is trying to connect
|
|
|
|
* to the doomed database, so additional interlocking is needed during
|
|
|
|
* backend startup.
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool
|
|
|
|
DatabaseHasActiveBackends(Oid databaseId)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
SISeg *segP = shmInvalBuffer;
|
|
|
|
ProcState *stateP = segP->procState;
|
|
|
|
int index;
|
|
|
|
|
|
|
|
SpinAcquire(SInvalLock);
|
|
|
|
|
|
|
|
for (index = 0; index < segP->maxBackends; index++)
|
|
|
|
{
|
|
|
|
SHMEM_OFFSET pOffset = stateP[index].procStruct;
|
|
|
|
|
|
|
|
if (pOffset != INVALID_OFFSET)
|
|
|
|
{
|
|
|
|
PROC *proc = (PROC *) MAKE_PTR(pOffset);
|
|
|
|
|
|
|
|
if (proc->databaseId == databaseId)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SpinRelease(SInvalLock);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TransactionIdIsInProgress -- is given transaction running by some backend
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
TransactionIdIsInProgress(TransactionId xid)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
SISeg *segP = shmInvalBuffer;
|
|
|
|
ProcState *stateP = segP->procState;
|
|
|
|
int index;
|
|
|
|
|
|
|
|
SpinAcquire(SInvalLock);
|
|
|
|
|
|
|
|
for (index = 0; index < segP->maxBackends; index++)
|
|
|
|
{
|
|
|
|
SHMEM_OFFSET pOffset = stateP[index].procStruct;
|
|
|
|
|
|
|
|
if (pOffset != INVALID_OFFSET)
|
|
|
|
{
|
|
|
|
PROC *proc = (PROC *) MAKE_PTR(pOffset);
|
|
|
|
|
|
|
|
if (proc->xid == xid)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SpinRelease(SInvalLock);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* GetXmaxRecent -- returns oldest transaction that was running
|
|
|
|
* when all current transaction was started.
|
|
|
|
* It's used by vacuum to decide what deleted
|
|
|
|
* tuples must be preserved in a table.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
GetXmaxRecent(TransactionId *XmaxRecent)
|
|
|
|
{
|
|
|
|
SISeg *segP = shmInvalBuffer;
|
|
|
|
ProcState *stateP = segP->procState;
|
|
|
|
int index;
|
|
|
|
|
|
|
|
*XmaxRecent = GetCurrentTransactionId();
|
|
|
|
|
|
|
|
SpinAcquire(SInvalLock);
|
|
|
|
|
|
|
|
for (index = 0; index < segP->maxBackends; index++)
|
|
|
|
{
|
|
|
|
SHMEM_OFFSET pOffset = stateP[index].procStruct;
|
|
|
|
|
|
|
|
if (pOffset != INVALID_OFFSET)
|
|
|
|
{
|
|
|
|
PROC *proc = (PROC *) MAKE_PTR(pOffset);
|
|
|
|
TransactionId xmin;
|
|
|
|
|
|
|
|
xmin = proc->xmin; /* we don't use spin-locking in AbortTransaction() ! */
|
|
|
|
if (proc == MyProc || xmin < FirstTransactionId)
|
|
|
|
continue;
|
|
|
|
if (xmin < *XmaxRecent)
|
|
|
|
*XmaxRecent = xmin;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SpinRelease(SInvalLock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* GetSnapshotData -- returns information about running transactions.
|
|
|
|
*/
|
|
|
|
Snapshot
|
|
|
|
GetSnapshotData(bool serializable)
|
|
|
|
{
|
|
|
|
Snapshot snapshot = (Snapshot) malloc(sizeof(SnapshotData));
|
|
|
|
SISeg *segP = shmInvalBuffer;
|
|
|
|
ProcState *stateP = segP->procState;
|
|
|
|
int index;
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
/* There can be no more than maxBackends active transactions,
|
|
|
|
* so this is enough space:
|
|
|
|
*/
|
|
|
|
snapshot->xip = (TransactionId *)
|
|
|
|
malloc(segP->maxBackends * sizeof(TransactionId));
|
|
|
|
snapshot->xmin = GetCurrentTransactionId();
|
|
|
|
|
|
|
|
SpinAcquire(SInvalLock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Unfortunately, we have to call ReadNewTransactionId()
|
|
|
|
* after acquiring SInvalLock above. It's not good because
|
|
|
|
* ReadNewTransactionId() does SpinAcquire(OidGenLockId) but
|
|
|
|
* _necessary_.
|
|
|
|
*/
|
|
|
|
ReadNewTransactionId(&(snapshot->xmax));
|
|
|
|
|
|
|
|
for (index = 0; index < segP->maxBackends; index++)
|
|
|
|
{
|
|
|
|
SHMEM_OFFSET pOffset = stateP[index].procStruct;
|
|
|
|
|
|
|
|
if (pOffset != INVALID_OFFSET)
|
|
|
|
{
|
|
|
|
PROC *proc = (PROC *) MAKE_PTR(pOffset);
|
|
|
|
TransactionId xid;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We don't use spin-locking when changing proc->xid
|
|
|
|
* in GetNewTransactionId() and in AbortTransaction() !..
|
|
|
|
*/
|
|
|
|
xid = proc->xid;
|
|
|
|
if (proc == MyProc ||
|
|
|
|
xid < FirstTransactionId || xid >= snapshot->xmax)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Seems that there is no sense to store xid >= snapshot->xmax
|
|
|
|
* (what we got from ReadNewTransactionId above) in
|
|
|
|
* snapshot->xip - we just assume that all xacts with such
|
|
|
|
* xid-s are running and may be ignored.
|
|
|
|
*/
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (xid < snapshot->xmin)
|
|
|
|
snapshot->xmin = xid;
|
|
|
|
snapshot->xip[count] = xid;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (serializable)
|
|
|
|
MyProc->xmin = snapshot->xmin;
|
|
|
|
/* Serializable snapshot must be computed before any other... */
|
|
|
|
Assert(MyProc->xmin != InvalidTransactionId);
|
|
|
|
|
|
|
|
SpinRelease(SInvalLock);
|
|
|
|
|
|
|
|
snapshot->xcnt = count;
|
|
|
|
return snapshot;
|
|
|
|
}
|