Tom Lane 7326e78c42 Ensure that all TransactionId comparisons are encapsulated in macros
(TransactionIdPrecedes, TransactionIdFollows, etc).  First step on the
way to transaction ID wrap solution ...
2001-08-23 23:06:38 +00:00

475 lines
13 KiB
C

/*-------------------------------------------------------------------------
*
* transam.c
* postgres transaction log/time interface routines
*
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/access/transam/transam.c,v 1.46 2001/08/23 23:06:37 tgl Exp $
*
* NOTES
* This file contains the high level access-method interface to the
* transaction system.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "access/transam.h"
#include "catalog/catname.h"
#include "miscadmin.h"
static int RecoveryCheckingEnabled(void);
static void TransRecover(Relation logRelation);
static bool TransactionLogTest(TransactionId transactionId, XidStatus status);
static void TransactionLogUpdate(TransactionId transactionId,
XidStatus status);
/* ----------------
* global variables holding pointers to relations used
* by the transaction system. These are initialized by
* InitializeTransactionLog().
* ----------------
*/
Relation LogRelation = (Relation) NULL;
/* ----------------
* Single-item cache for results of TransactionLogTest.
* ----------------
*/
static TransactionId cachedTestXid = InvalidTransactionId;
static XidStatus cachedTestXidStatus;
/* ----------------
* transaction recovery state variables
*
* When the transaction system is initialized, we may
* need to do recovery checking. This decision is decided
* by the postmaster or the user by supplying the backend
* with a special flag. In general, we want to do recovery
* checking whenever we are running without a postmaster
* or when the number of backends running under the postmaster
* goes from zero to one. -cim 3/21/90
* ----------------
*/
static int RecoveryCheckingEnableState = 0;
/* ----------------
* recovery checking accessors
* ----------------
*/
static int
RecoveryCheckingEnabled(void)
{
return RecoveryCheckingEnableState;
}
#ifdef NOT_USED
static void
SetRecoveryCheckingEnabled(bool state)
{
RecoveryCheckingEnableState = (state == true);
}
#endif
/* ----------------------------------------------------------------
* postgres log access method interface
*
* TransactionLogTest
* TransactionLogUpdate
* ========
* these functions do work for the interface
* functions - they search/retrieve and append/update
* information in the log and time relations.
* ----------------------------------------------------------------
*/
/* --------------------------------
* TransactionLogTest
* --------------------------------
*/
static bool /* true/false: does transaction id have
* specified status? */
TransactionLogTest(TransactionId transactionId, /* transaction id to test */
XidStatus status) /* transaction status */
{
BlockNumber blockNumber;
XidStatus xidstatus; /* recorded status of xid */
bool fail = false; /* success/failure */
/*
* during initialization consider all transactions as having been
* committed
*/
if (!RelationIsValid(LogRelation))
return (bool) (status == XID_COMMIT);
/*
* before going to the buffer manager, check our single item cache to
* see if we didn't just check the transaction status a moment ago.
*/
if (TransactionIdEquals(transactionId, cachedTestXid))
return (bool)
(status == cachedTestXidStatus);
/*
* compute the item pointer corresponding to the page containing our
* transaction id. We save the item in our cache to speed up things
* if we happen to ask for the same xid's status more than once.
*/
TransComputeBlockNumber(LogRelation, transactionId, &blockNumber);
xidstatus = TransBlockNumberGetXidStatus(LogRelation,
blockNumber,
transactionId,
&fail);
if (!fail)
{
/*
* DO NOT cache status for transactions in unknown state !!!
*/
if (xidstatus == XID_COMMIT || xidstatus == XID_ABORT)
{
TransactionIdStore(transactionId, &cachedTestXid);
cachedTestXidStatus = xidstatus;
}
return (bool) (status == xidstatus);
}
/*
* here the block didn't contain the information we wanted
*/
elog(ERROR, "TransactionLogTest: failed to get xidstatus");
/*
* so lint is happy...
*/
return false;
}
/* --------------------------------
* TransactionLogUpdate
* --------------------------------
*/
static void
TransactionLogUpdate(TransactionId transactionId, /* trans id to update */
XidStatus status) /* new trans status */
{
BlockNumber blockNumber;
bool fail = false; /* success/failure */
/*
* during initialization we don't record any updates.
*/
if (!RelationIsValid(LogRelation))
return;
/*
* update the log relation
*/
TransComputeBlockNumber(LogRelation, transactionId, &blockNumber);
TransBlockNumberSetXidStatus(LogRelation,
blockNumber,
transactionId,
status,
&fail);
/*
* update (invalidate) our single item TransactionLogTest cache.
*/
TransactionIdStore(transactionId, &cachedTestXid);
cachedTestXidStatus = status;
}
/* ----------------------------------------------------------------
* transaction recovery code
* ----------------------------------------------------------------
*/
/* --------------------------------
* TransRecover
*
* preform transaction recovery checking.
*
* Note: this should only be preformed if no other backends
* are running. This is known by the postmaster and
* conveyed by the postmaster passing a "do recovery checking"
* flag to the backend.
*
* here we get the last recorded transaction from the log,
* get the "last" and "next" transactions from the variable relation
* and then preform some integrity tests:
*
* 1) No transaction may exist higher then the "next" available
* transaction recorded in the variable relation. If this is the
* case then it means either the log or the variable relation
* has become corrupted.
*
* 2) The last committed transaction may not be higher then the
* next available transaction for the same reason.
*
* 3) The last recorded transaction may not be lower then the
* last committed transaction. (the reverse is ok - it means
* that some transactions have aborted since the last commit)
*
* Here is what the proper situation looks like. The line
* represents the data stored in the log. 'c' indicates the
* transaction was recorded as committed, 'a' indicates an
* abortted transaction and '.' represents information not
* recorded. These may correspond to in progress transactions.
*
* c c a c . . a . . . . . . . . . .
* | |
* last next
*
* Since "next" is only incremented by GetNewTransactionId() which
* is called when transactions are started. Hence if there
* are commits or aborts after "next", then it means we committed
* or aborted BEFORE we started the transaction. This is the
* rational behind constraint (1).
*
* Likewise, "last" should never greater then "next" for essentially
* the same reason - it would imply we committed before we started.
* This is the reasoning for (2).
*
* (3) implies we may never have a situation such as:
*
* c c a c . . a c . . . . . . . . .
* | |
* last next
*
* where there is a 'c' greater then "last".
*
* Recovery checking is more difficult in the case where
* several backends are executing concurrently because the
* transactions may be executing in the other backends.
* So, we only do recovery stuff when the backend is explicitly
* passed a flag on the command line.
* --------------------------------
*/
static void
TransRecover(Relation logRelation)
{
}
/* ----------------------------------------------------------------
* Interface functions
*
* InitializeTransactionLog
* ========
* this function (called near cinit) initializes
* the transaction log, time and variable relations.
*
* TransactionId DidCommit
* TransactionId DidAbort
* TransactionId IsInProgress
* ========
* these functions test the transaction status of
* a specified transaction id.
*
* TransactionId Commit
* TransactionId Abort
* TransactionId SetInProgress
* ========
* these functions set the transaction status
* of the specified xid. TransactionIdCommit() also
* records the current time in the time relation
* and updates the variable relation counter.
*
* ----------------------------------------------------------------
*/
/*
* InitializeTransactionLog
* Initializes transaction logging.
*/
void
InitializeTransactionLog(void)
{
Relation logRelation;
MemoryContext oldContext;
/*
* don't do anything during bootstrapping
*/
if (AMI_OVERRIDE)
return;
/*
* disable the transaction system so the access methods don't
* interfere during initialization.
*/
OverrideTransactionSystem(true);
/*
* make sure allocations occur within the top memory context so that
* our log management structures are protected from garbage collection
* at the end of every transaction.
*/
oldContext = MemoryContextSwitchTo(TopMemoryContext);
/*
* first open the log and time relations (these are created by amiint
* so they are guaranteed to exist)
*/
logRelation = heap_openr(LogRelationName, NoLock);
/*
* XXX TransactionLogUpdate requires that LogRelation is valid so we
* temporarily set it so we can initialize things properly. This could
* be done cleaner.
*/
LogRelation = logRelation;
/*
* if we have a virgin database, we initialize the log relation by
* committing the BootstrapTransactionId and we initialize the
* variable relation by setting the next available transaction id to
* FirstNormalTransactionId. OID initialization happens as a side
* effect of bootstrapping in varsup.c.
*/
SpinAcquire(OidGenLockId);
if (!TransactionIdDidCommit(BootstrapTransactionId))
{
TransactionLogUpdate(BootstrapTransactionId, XID_COMMIT);
Assert(!IsUnderPostmaster &&
TransactionIdEquals(ShmemVariableCache->nextXid,
FirstNormalTransactionId));
ShmemVariableCache->nextXid = FirstNormalTransactionId;
}
else if (RecoveryCheckingEnabled())
{
/*
* if we have a pre-initialized database and if the perform
* recovery checking flag was passed then we do our database
* integrity checking.
*/
TransRecover(logRelation);
}
LogRelation = (Relation) NULL;
SpinRelease(OidGenLockId);
/*
* now re-enable the transaction system
*/
OverrideTransactionSystem(false);
/*
* instantiate the global variables
*/
LogRelation = logRelation;
/*
* restore the memory context to the previous context before we return
* from initialization.
*/
MemoryContextSwitchTo(oldContext);
}
/* --------------------------------
* TransactionId DidCommit
* TransactionId DidAbort
* TransactionId IsInProgress
* --------------------------------
*/
/*
* TransactionIdDidCommit
* True iff transaction associated with the identifier did commit.
*
* Note:
* Assumes transaction identifier is valid.
*/
bool /* true if given transaction committed */
TransactionIdDidCommit(TransactionId transactionId)
{
if (AMI_OVERRIDE)
return true;
return TransactionLogTest(transactionId, XID_COMMIT);
}
/*
* TransactionIdDidAborted
* True iff transaction associated with the identifier did abort.
*
* Note:
* Assumes transaction identifier is valid.
* XXX Is this unneeded?
*/
bool /* true if given transaction aborted */
TransactionIdDidAbort(TransactionId transactionId)
{
if (AMI_OVERRIDE)
return false;
return TransactionLogTest(transactionId, XID_ABORT);
}
/*
* Now this func in shmem.c and gives quality answer by scanning
* PROC structures of all running backend. - vadim 11/26/96
*
* Old comments:
* true if given transaction neither committed nor aborted
bool
TransactionIdIsInProgress(TransactionId transactionId)
{
if (AMI_OVERRIDE)
return false;
return TransactionLogTest(transactionId, XID_INPROGRESS);
}
*/
/* --------------------------------
* TransactionId Commit
* TransactionId Abort
* TransactionId SetInProgress
* --------------------------------
*/
/*
* TransactionIdCommit
* Commits the transaction associated with the identifier.
*
* Note:
* Assumes transaction identifier is valid.
*/
void
TransactionIdCommit(TransactionId transactionId)
{
if (AMI_OVERRIDE)
return;
TransactionLogUpdate(transactionId, XID_COMMIT);
}
/*
* TransactionIdAbort
* Aborts the transaction associated with the identifier.
*
* Note:
* Assumes transaction identifier is valid.
*/
void
TransactionIdAbort(TransactionId transactionId)
{
if (AMI_OVERRIDE)
return;
TransactionLogUpdate(transactionId, XID_ABORT);
}