/*------------------------------------------------------------------------- * * 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.45 2001/07/12 04:11:13 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 = NullTransactionId; 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 AmiTransactionId and we initialize the * variable relation by setting the next available transaction id to * FirstTransactionId. OID initialization happens as a side * effect of bootstrapping in varsup.c. */ SpinAcquire(OidGenLockId); if (!TransactionIdDidCommit(AmiTransactionId)) { TransactionLogUpdate(AmiTransactionId, XID_COMMIT); Assert(!IsUnderPostmaster && ShmemVariableCache->nextXid <= FirstTransactionId); ShmemVariableCache->nextXid = FirstTransactionId; } 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); }