diff options
Diffstat (limited to 'src/backend/access/transam/xact.c')
-rw-r--r-- | src/backend/access/transam/xact.c | 1656 |
1 files changed, 0 insertions, 1656 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c deleted file mode 100644 index 43beb6b6066..00000000000 --- a/src/backend/access/transam/xact.c +++ /dev/null @@ -1,1656 +0,0 @@ -/*------------------------------------------------------------------------- - * - * xact.c - * top level transaction system support routines - * - * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.128 2002/06/20 20:29:25 momjian Exp $ - * - * NOTES - * Transaction aborts can now occur two ways: - * - * 1) system dies from some internal cause (Assert, etc..) - * 2) user types abort - * - * These two cases used to be treated identically, but now - * we need to distinguish them. Why? consider the following - * two situations: - * - * case 1 case 2 - * ------ ------ - * 1) user types BEGIN 1) user types BEGIN - * 2) user does something 2) user does something - * 3) user does not like what 3) system aborts for some reason - * she sees and types ABORT - * - * In case 1, we want to abort the transaction and return to the - * default state. In case 2, there may be more commands coming - * our way which are part of the same transaction block and we have - * to ignore these commands until we see an END transaction. - * (or an ABORT! --djm) - * - * Internal aborts are now handled by AbortTransactionBlock(), just as - * they always have been, and user aborts are now handled by - * UserAbortTransactionBlock(). Both of them rely on AbortTransaction() - * to do all the real work. The only difference is what state we - * enter after AbortTransaction() does its work: - * - * * AbortTransactionBlock() leaves us in TBLOCK_ABORT and - * * UserAbortTransactionBlock() leaves us in TBLOCK_ENDABORT - * - * Low-level transaction abort handling is divided into two phases: - * * AbortTransaction() executes as soon as we realize the transaction - * has failed. It should release all shared resources (locks etc) - * so that we do not delay other backends unnecessarily. - * * CleanupTransaction() executes when we finally see a user COMMIT - * or ROLLBACK command; it cleans things up and gets us out of - * the transaction internally. In particular, we mustn't destroy - * TransactionCommandContext until this point. - * - * NOTES - * This file is an attempt at a redesign of the upper layer - * of the V1 transaction system which was too poorly thought - * out to describe. This new system hopes to be both simpler - * in design, simpler to extend and needs to contain added - * functionality to solve problems beyond the scope of the V1 - * system. (In particuler, communication of transaction - * information between parallel backends has to be supported) - * - * The essential aspects of the transaction system are: - * - * o transaction id generation - * o transaction log updating - * o memory cleanup - * o cache invalidation - * o lock cleanup - * - * Hence, the functional division of the transaction code is - * based on what of the above things need to be done during - * a start/commit/abort transaction. For instance, the - * routine AtCommit_Memory() takes care of all the memory - * cleanup stuff done at commit time. - * - * The code is layered as follows: - * - * StartTransaction - * CommitTransaction - * AbortTransaction - * CleanupTransaction - * - * are provided to do the lower level work like recording - * the transaction status in the log and doing memory cleanup. - * above these routines are another set of functions: - * - * StartTransactionCommand - * CommitTransactionCommand - * AbortCurrentTransaction - * - * These are the routines used in the postgres main processing - * loop. They are sensitive to the current transaction block state - * and make calls to the lower level routines appropriately. - * - * Support for transaction blocks is provided via the functions: - * - * StartTransactionBlock - * CommitTransactionBlock - * AbortTransactionBlock - * - * These are invoked only in responce to a user "BEGIN", "END", - * or "ABORT" command. The tricky part about these functions - * is that they are called within the postgres main loop, in between - * the StartTransactionCommand() and CommitTransactionCommand(). - * - * For example, consider the following sequence of user commands: - * - * 1) begin - * 2) retrieve (foo.all) - * 3) append foo (bar = baz) - * 4) end - * - * in the main processing loop, this results in the following - * transaction sequence: - * - * / StartTransactionCommand(); - * 1) / ProcessUtility(); << begin - * \ StartTransactionBlock(); - * \ CommitTransactionCommand(); - * - * / StartTransactionCommand(); - * 2) < ProcessQuery(); << retrieve (foo.all) - * \ CommitTransactionCommand(); - * - * / StartTransactionCommand(); - * 3) < ProcessQuery(); << append foo (bar = baz) - * \ CommitTransactionCommand(); - * - * / StartTransactionCommand(); - * 4) / ProcessUtility(); << end - * \ CommitTransactionBlock(); - * \ CommitTransactionCommand(); - * - * The point of this example is to demonstrate the need for - * StartTransactionCommand() and CommitTransactionCommand() to - * be state smart -- they should do nothing in between the calls - * to StartTransactionBlock() and EndTransactionBlock() and - * outside these calls they need to do normal start/commit - * processing. - * - * Furthermore, suppose the "retrieve (foo.all)" caused an abort - * condition. We would then want to abort the transaction and - * ignore all subsequent commands up to the "end". - * -cim 3/23/90 - * - *------------------------------------------------------------------------- - */ - -/* - * Large object clean up added in CommitTransaction() to prevent buffer leaks. - * [PA, 7/17/98] - * [PA] is Pascal André <andre@via.ecp.fr> - */ -#include "postgres.h" - -#include <unistd.h> -#include <sys/time.h> - -#include "access/gistscan.h" -#include "access/hash.h" -#include "access/nbtree.h" -#include "access/rtree.h" -#include "access/xact.h" -#include "catalog/heap.h" -#include "catalog/index.h" -#include "catalog/namespace.h" -#include "commands/async.h" -#include "commands/trigger.h" -#include "executor/spi.h" -#include "libpq/be-fsstubs.h" -#include "miscadmin.h" -#include "storage/proc.h" -#include "storage/sinval.h" -#include "storage/smgr.h" -#include "utils/guc.h" -#include "utils/inval.h" -#include "utils/memutils.h" -#include "utils/portal.h" -#include "utils/catcache.h" -#include "utils/relcache.h" -#include "pgstat.h" - - -static void AbortTransaction(void); -static void AtAbort_Cache(void); -static void AtAbort_Locks(void); -static void AtAbort_Memory(void); -static void AtCleanup_Memory(void); -static void AtCommit_Cache(void); -static void AtCommit_LocalCache(void); -static void AtCommit_Locks(void); -static void AtCommit_Memory(void); -static void AtStart_Cache(void); -static void AtStart_Locks(void); -static void AtStart_Memory(void); -static void CleanupTransaction(void); -static void CommitTransaction(void); -static void RecordTransactionAbort(void); -static void StartTransaction(void); - -/* ---------------- - * global variables holding the current transaction state. - * ---------------- - */ -static TransactionStateData CurrentTransactionStateData = { - 0, /* transaction id */ - FirstCommandId, /* command id */ - 0, /* scan command id */ - 0x0, /* start time */ - TRANS_DEFAULT, /* transaction state */ - TBLOCK_DEFAULT /* transaction block state */ -}; - -TransactionState CurrentTransactionState = &CurrentTransactionStateData; - -/* - * User-tweakable parameters - */ -int DefaultXactIsoLevel = XACT_READ_COMMITTED; -int XactIsoLevel; - -int CommitDelay = 0; /* precommit delay in microseconds */ -int CommitSiblings = 5; /* number of concurrent xacts needed to - * sleep */ - -static void (*_RollbackFunc) (void *) = NULL; -static void *_RollbackData = NULL; - - -/* ---------------------------------------------------------------- - * transaction state accessors - * ---------------------------------------------------------------- - */ - -#ifdef NOT_USED - -/* -------------------------------- - * TransactionFlushEnabled() - * SetTransactionFlushEnabled() - * - * These are used to test and set the "TransactionFlushState" - * varable. If this variable is true (the default), then - * the system will flush all dirty buffers to disk at the end - * of each transaction. If false then we are assuming the - * buffer pool resides in stable main memory, in which case we - * only do writes as necessary. - * -------------------------------- - */ -static int TransactionFlushState = 1; - -int -TransactionFlushEnabled(void) -{ - return TransactionFlushState; -} - -void -SetTransactionFlushEnabled(bool state) -{ - TransactionFlushState = (state == true); -} - -#endif - - -/* -------------------------------- - * IsTransactionState - * - * This returns true if we are currently running a query - * within an executing transaction. - * -------------------------------- - */ -bool -IsTransactionState(void) -{ - TransactionState s = CurrentTransactionState; - - switch (s->state) - { - case TRANS_DEFAULT: - return false; - case TRANS_START: - return true; - case TRANS_INPROGRESS: - return true; - case TRANS_COMMIT: - return true; - case TRANS_ABORT: - return true; - } - - /* - * Shouldn't get here, but lint is not happy with this... - */ - return false; -} - -/* -------------------------------- - * IsAbortedTransactionBlockState - * - * This returns true if we are currently running a query - * within an aborted transaction block. - * -------------------------------- - */ -bool -IsAbortedTransactionBlockState(void) -{ - TransactionState s = CurrentTransactionState; - - if (s->blockState == TBLOCK_ABORT) - return true; - - return false; -} - - -/* -------------------------------- - * GetCurrentTransactionId - * -------------------------------- - */ -TransactionId -GetCurrentTransactionId(void) -{ - TransactionState s = CurrentTransactionState; - - return s->transactionIdData; -} - - -/* -------------------------------- - * GetCurrentCommandId - * -------------------------------- - */ -CommandId -GetCurrentCommandId(void) -{ - TransactionState s = CurrentTransactionState; - - return s->commandId; -} - - -/* -------------------------------- - * GetCurrentTransactionStartTime - * -------------------------------- - */ -AbsoluteTime -GetCurrentTransactionStartTime(void) -{ - TransactionState s = CurrentTransactionState; - - return s->startTime; -} - - -/* -------------------------------- - * GetCurrentTransactionStartTimeUsec - * -------------------------------- - */ -AbsoluteTime -GetCurrentTransactionStartTimeUsec(int *msec) -{ - TransactionState s = CurrentTransactionState; - - *msec = s->startTimeUsec; - - return s->startTime; -} - - -/* -------------------------------- - * TransactionIdIsCurrentTransactionId - * - * During bootstrap, we cheat and say "it's not my transaction ID" even though - * it is. Along with transam.c's cheat to say that the bootstrap XID is - * already committed, this causes the tqual.c routines to see previously - * inserted tuples as committed, which is what we need during bootstrap. - * -------------------------------- - */ -bool -TransactionIdIsCurrentTransactionId(TransactionId xid) -{ - TransactionState s = CurrentTransactionState; - - if (AMI_OVERRIDE) - { - Assert(xid == BootstrapTransactionId); - return false; - } - - return TransactionIdEquals(xid, s->transactionIdData); -} - - -/* -------------------------------- - * CommandIdIsCurrentCommandId - * -------------------------------- - */ -bool -CommandIdIsCurrentCommandId(CommandId cid) -{ - TransactionState s = CurrentTransactionState; - - return (cid == s->commandId) ? true : false; -} - - -/* -------------------------------- - * CommandCounterIncrement - * -------------------------------- - */ -void -CommandCounterIncrement(void) -{ - TransactionState s = CurrentTransactionState; - - s->commandId += 1; - if (s->commandId == FirstCommandId) /* check for overflow */ - elog(ERROR, "You may only have 2^32-1 commands per transaction"); - - /* Propagate new command ID into query snapshots, if set */ - if (QuerySnapshot) - QuerySnapshot->curcid = s->commandId; - if (SerializableSnapshot) - SerializableSnapshot->curcid = s->commandId; - - /* - * make cache changes visible to me. AtCommit_LocalCache() instead of - * AtCommit_Cache() is called here. - */ - AtCommit_LocalCache(); - AtStart_Cache(); -} - - -/* ---------------------------------------------------------------- - * StartTransaction stuff - * ---------------------------------------------------------------- - */ - -/* -------------------------------- - * AtStart_Cache - * -------------------------------- - */ -static void -AtStart_Cache(void) -{ - AcceptInvalidationMessages(); -} - -/* -------------------------------- - * AtStart_Locks - * -------------------------------- - */ -static void -AtStart_Locks(void) -{ - /* - * at present, it is unknown to me what belongs here -cim 3/18/90 - * - * There isn't anything to do at the start of a xact for locks. -mer - * 5/24/92 - */ -} - -/* -------------------------------- - * AtStart_Memory - * -------------------------------- - */ -static void -AtStart_Memory(void) -{ - /* - * We shouldn't have any transaction contexts already. - */ - Assert(TopTransactionContext == NULL); - Assert(TransactionCommandContext == NULL); - - /* - * Create a toplevel context for the transaction. - */ - TopTransactionContext = - AllocSetContextCreate(TopMemoryContext, - "TopTransactionContext", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); - - /* - * Create a statement-level context and make it active. - */ - TransactionCommandContext = - AllocSetContextCreate(TopTransactionContext, - "TransactionCommandContext", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); - MemoryContextSwitchTo(TransactionCommandContext); -} - - -/* ---------------------------------------------------------------- - * CommitTransaction stuff - * ---------------------------------------------------------------- - */ - -/* -------------------------------- - * RecordTransactionCommit - * - * Note: the two calls to BufferManagerFlush() exist to ensure - * that data pages are written before log pages. These - * explicit calls should be replaced by a more efficient - * ordered page write scheme in the buffer manager - * -cim 3/18/90 - * -------------------------------- - */ -void -RecordTransactionCommit(void) -{ - TransactionId xid; - bool leak; - - leak = BufferPoolCheckLeak(); - - xid = GetCurrentTransactionId(); - - /* - * We only need to log the commit in xlog and clog if the transaction made - * any transaction-controlled XLOG entries. (Otherwise, its XID appears - * nowhere in permanent storage, so no one will ever care if it - * committed.) However, we must flush XLOG to disk if we made any XLOG - * entries, whether in or out of transaction control. For example, if we - * reported a nextval() result to the client, this ensures that any XLOG - * record generated by nextval will hit the disk before we report the - * transaction committed. - */ - if (MyXactMadeXLogEntry) - { - XLogRecPtr recptr; - - BufmgrCommit(); - - START_CRIT_SECTION(); - - if (MyLastRecPtr.xrecoff != 0) - { - /* Need to emit a commit record */ - XLogRecData rdata; - xl_xact_commit xlrec; - - xlrec.xtime = time(NULL); - rdata.buffer = InvalidBuffer; - rdata.data = (char *) (&xlrec); - rdata.len = SizeOfXactCommit; - rdata.next = NULL; - - /* - * XXX SHOULD SAVE ARRAY OF RELFILENODE-s TO DROP - */ - recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT, &rdata); - } - else - { - /* Just flush through last record written by me */ - recptr = ProcLastRecEnd; - } - - /* - * Sleep before flush! So we can flush more than one commit - * records per single fsync. (The idea is some other backend may - * do the XLogFlush while we're sleeping. This needs work still, - * because on most Unixen, the minimum select() delay is 10msec or - * more, which is way too long.) - * - * We do not sleep if enableFsync is not turned on, nor if there are - * fewer than CommitSiblings other backends with active - * transactions. - */ - if (CommitDelay > 0 && enableFsync && - CountActiveBackends() >= CommitSiblings) - { - struct timeval delay; - - delay.tv_sec = 0; - delay.tv_usec = CommitDelay; - (void) select(0, NULL, NULL, NULL, &delay); - } - - XLogFlush(recptr); - - /* Mark the transaction committed in clog, if needed */ - if (MyLastRecPtr.xrecoff != 0) - TransactionIdCommit(xid); - - END_CRIT_SECTION(); - } - - /* Break the chain of back-links in the XLOG records I output */ - MyLastRecPtr.xrecoff = 0; - MyXactMadeXLogEntry = false; - - /* Show myself as out of the transaction in PGPROC array */ - MyProc->logRec.xrecoff = 0; - - if (leak) - ResetBufferPool(true); -} - - -/* -------------------------------- - * AtCommit_Cache - * -------------------------------- - */ -static void -AtCommit_Cache(void) -{ - /* - * Make catalog changes visible to all backends. - */ - AtEOXactInvalidationMessages(true); -} - -/* -------------------------------- - * AtCommit_LocalCache - * -------------------------------- - */ -static void -AtCommit_LocalCache(void) -{ - /* - * Make catalog changes visible to me for the next command. - */ - CommandEndInvalidationMessages(true); -} - -/* -------------------------------- - * AtCommit_Locks - * -------------------------------- - */ -static void -AtCommit_Locks(void) -{ - /* - * XXX What if ProcReleaseLocks fails? (race condition?) - * - * Then you're up a creek! -mer 5/24/92 - */ - ProcReleaseLocks(true); -} - -/* -------------------------------- - * AtCommit_Memory - * -------------------------------- - */ -static void -AtCommit_Memory(void) -{ - /* - * Now that we're "out" of a transaction, have the system allocate - * things in the top memory context instead of per-transaction - * contexts. - */ - MemoryContextSwitchTo(TopMemoryContext); - - /* - * Release all transaction-local memory. - */ - Assert(TopTransactionContext != NULL); - MemoryContextDelete(TopTransactionContext); - TopTransactionContext = NULL; - TransactionCommandContext = NULL; -} - -/* ---------------------------------------------------------------- - * AbortTransaction stuff - * ---------------------------------------------------------------- - */ - -/* -------------------------------- - * RecordTransactionAbort - * -------------------------------- - */ -static void -RecordTransactionAbort(void) -{ - TransactionId xid = GetCurrentTransactionId(); - - /* - * We only need to log the abort in xlog and clog if the transaction made - * any transaction-controlled XLOG entries. (Otherwise, its XID appears - * nowhere in permanent storage, so no one will ever care if it - * committed.) We do not flush XLOG to disk in any case, since the - * default assumption after a crash would be that we aborted, anyway. - * - * Extra check here is to catch case that we aborted partway through - * RecordTransactionCommit ... - */ - if (MyLastRecPtr.xrecoff != 0 && !TransactionIdDidCommit(xid)) - { - XLogRecData rdata; - xl_xact_abort xlrec; - XLogRecPtr recptr; - - xlrec.xtime = time(NULL); - rdata.buffer = InvalidBuffer; - rdata.data = (char *) (&xlrec); - rdata.len = SizeOfXactAbort; - rdata.next = NULL; - - START_CRIT_SECTION(); - - /* - * SHOULD SAVE ARRAY OF RELFILENODE-s TO DROP - */ - recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, &rdata); - - /* Mark the transaction aborted in clog */ - TransactionIdAbort(xid); - - END_CRIT_SECTION(); - } - - /* Break the chain of back-links in the XLOG records I output */ - MyLastRecPtr.xrecoff = 0; - MyXactMadeXLogEntry = false; - - /* Show myself as out of the transaction in PGPROC array */ - MyProc->logRec.xrecoff = 0; - - /* - * Tell bufmgr and smgr to release resources. - */ - ResetBufferPool(false); /* false -> is abort */ -} - -/* -------------------------------- - * AtAbort_Cache - * -------------------------------- - */ -static void -AtAbort_Cache(void) -{ - RelationCacheAbort(); - AtEOXactInvalidationMessages(false); -} - -/* -------------------------------- - * AtAbort_Locks - * -------------------------------- - */ -static void -AtAbort_Locks(void) -{ - /* - * XXX What if ProcReleaseLocks() fails? (race condition?) - * - * Then you're up a creek without a paddle! -mer - */ - ProcReleaseLocks(false); -} - - -/* -------------------------------- - * AtAbort_Memory - * -------------------------------- - */ -static void -AtAbort_Memory(void) -{ - /* - * Make sure we are in a valid context (not a child of - * TransactionCommandContext...). Note that it is possible for this - * code to be called when we aren't in a transaction at all; go - * directly to TopMemoryContext in that case. - */ - if (TransactionCommandContext != NULL) - { - MemoryContextSwitchTo(TransactionCommandContext); - - /* - * We do not want to destroy transaction contexts yet, but it - * should be OK to delete any command-local memory. - */ - MemoryContextResetAndDeleteChildren(TransactionCommandContext); - } - else - MemoryContextSwitchTo(TopMemoryContext); -} - - -/* ---------------------------------------------------------------- - * CleanupTransaction stuff - * ---------------------------------------------------------------- - */ - -/* -------------------------------- - * AtCleanup_Memory - * -------------------------------- - */ -static void -AtCleanup_Memory(void) -{ - /* - * Now that we're "out" of a transaction, have the system allocate - * things in the top memory context instead of per-transaction - * contexts. - */ - MemoryContextSwitchTo(TopMemoryContext); - - /* - * Release all transaction-local memory. - */ - if (TopTransactionContext != NULL) - MemoryContextDelete(TopTransactionContext); - TopTransactionContext = NULL; - TransactionCommandContext = NULL; -} - - -/* ---------------------------------------------------------------- - * interface routines - * ---------------------------------------------------------------- - */ - -/* -------------------------------- - * StartTransaction - * - * -------------------------------- - */ -static void -StartTransaction(void) -{ - TransactionState s = CurrentTransactionState; - - FreeXactSnapshot(); - XactIsoLevel = DefaultXactIsoLevel; - - /* - * Check the current transaction state. If the transaction system is - * switched off, or if we're already in a transaction, do nothing. - * We're already in a transaction when the monitor sends a null - * command to the backend to flush the comm channel. This is a hacky - * fix to a communications problem, and we keep having to deal with it - * here. We should fix the comm channel code. mao 080891 - */ - if (s->state == TRANS_INPROGRESS) - return; - - /* - * set the current transaction state information appropriately during - * start processing - */ - s->state = TRANS_START; - - SetReindexProcessing(false); - - /* - * generate a new transaction id - */ - s->transactionIdData = GetNewTransactionId(); - - XactLockTableInsert(s->transactionIdData); - - /* - * initialize current transaction state fields - */ - s->commandId = FirstCommandId; - s->startTime = GetCurrentAbsoluteTimeUsec(&(s->startTimeUsec)); - - /* - * initialize the various transaction subsystems - */ - AtStart_Memory(); - AtStart_Cache(); - AtStart_Locks(); - - /* - * Tell the trigger manager to we're starting a transaction - */ - DeferredTriggerBeginXact(); - - /* - * done with start processing, set current transaction state to "in - * progress" - */ - s->state = TRANS_INPROGRESS; - -} - -#ifdef NOT_USED -/* --------------- - * Tell me if we are currently in progress - * --------------- - */ -bool -CurrentXactInProgress(void) -{ - return CurrentTransactionState->state == TRANS_INPROGRESS; -} -#endif - -/* -------------------------------- - * CommitTransaction - * - * -------------------------------- - */ -static void -CommitTransaction(void) -{ - TransactionState s = CurrentTransactionState; - - /* - * check the current transaction state - */ - if (s->state != TRANS_INPROGRESS) - elog(WARNING, "CommitTransaction and not in in-progress state"); - - /* - * Tell the trigger manager that this transaction is about to be - * committed. He'll invoke all trigger deferred until XACT before we - * really start on committing the transaction. - */ - DeferredTriggerEndXact(); - - /* Prevent cancel/die interrupt while cleaning up */ - HOLD_INTERRUPTS(); - - /* - * set the current transaction state information appropriately during - * the abort processing - */ - s->state = TRANS_COMMIT; - - /* - * do commit processing - */ - - /* handle commit for large objects [ PA, 7/17/98 ] */ - lo_commit(true); - - /* NOTIFY commit must also come before lower-level cleanup */ - AtCommit_Notify(); - - AtEOXact_portals(); - - /* Here is where we really truly commit. */ - RecordTransactionCommit(); - - /* - * Let others know about no transaction in progress by me. Note that - * this must be done _before_ releasing locks we hold and _after_ - * RecordTransactionCommit. - * - * LWLockAcquire(SInvalLock) is required: UPDATE with xid 0 is blocked by - * xid 1' UPDATE, xid 1 is doing commit while xid 2 gets snapshot - if - * xid 2' GetSnapshotData sees xid 1 as running then it must see xid 0 - * as running as well or it will see two tuple versions - one deleted - * by xid 1 and one inserted by xid 0. See notes in GetSnapshotData. - */ - if (MyProc != (PGPROC *) NULL) - { - /* Lock SInvalLock because that's what GetSnapshotData uses. */ - LWLockAcquire(SInvalLock, LW_EXCLUSIVE); - MyProc->xid = InvalidTransactionId; - MyProc->xmin = InvalidTransactionId; - LWLockRelease(SInvalLock); - } - - /* - * This is all post-commit cleanup. Note that if an error is raised - * here, it's too late to abort the transaction. This should be just - * noncritical resource releasing. - */ - - RelationPurgeLocalRelation(true); - smgrDoPendingDeletes(true); - - AtEOXact_GUC(true); - AtEOXact_SPI(); - AtEOXact_gist(); - AtEOXact_hash(); - AtEOXact_nbtree(); - AtEOXact_rtree(); - AtEOXact_Namespace(true); - AtCommit_Cache(); - AtCommit_Locks(); - AtEOXact_CatCache(true); - AtCommit_Memory(); - AtEOXact_Files(); - - /* Count transaction commit in statistics collector */ - pgstat_count_xact_commit(); - - /* - * done with commit processing, set current transaction state back to - * default - */ - s->state = TRANS_DEFAULT; - - RESUME_INTERRUPTS(); -} - -/* -------------------------------- - * AbortTransaction - * - * -------------------------------- - */ -static void -AbortTransaction(void) -{ - TransactionState s = CurrentTransactionState; - - /* Prevent cancel/die interrupt while cleaning up */ - HOLD_INTERRUPTS(); - - /* - * Release any LW locks we might be holding as quickly as possible. - * (Regular locks, however, must be held till we finish aborting.) - * Releasing LW locks is critical since we might try to grab them - * again while cleaning up! - */ - LWLockReleaseAll(); - - /* Clean up buffer I/O and buffer context locks, too */ - AbortBufferIO(); - UnlockBuffers(); - - /* - * Also clean up any open wait for lock, since the lock manager will - * choke if we try to wait for another lock before doing this. - */ - LockWaitCancel(); - - /* - * check the current transaction state - */ - if (s->state != TRANS_INPROGRESS) - elog(WARNING, "AbortTransaction and not in in-progress state"); - - /* - * set the current transaction state information appropriately during - * the abort processing - */ - s->state = TRANS_ABORT; - - /* - * Reset user id which might have been changed transiently - */ - SetUserId(GetSessionUserId()); - - /* - * do abort processing - */ - DeferredTriggerAbortXact(); - lo_commit(false); /* 'false' means it's abort */ - AtAbort_Notify(); - AtEOXact_portals(); - - /* Advertise the fact that we aborted in pg_clog. */ - RecordTransactionAbort(); - - /* - * Let others know about no transaction in progress by me. Note that - * this must be done _before_ releasing locks we hold and _after_ - * RecordTransactionAbort. - */ - if (MyProc != (PGPROC *) NULL) - { - /* Lock SInvalLock because that's what GetSnapshotData uses. */ - LWLockAcquire(SInvalLock, LW_EXCLUSIVE); - MyProc->xid = InvalidTransactionId; - MyProc->xmin = InvalidTransactionId; - LWLockRelease(SInvalLock); - } - - RelationPurgeLocalRelation(false); - smgrDoPendingDeletes(false); - - AtEOXact_GUC(false); - AtEOXact_SPI(); - AtEOXact_gist(); - AtEOXact_hash(); - AtEOXact_nbtree(); - AtEOXact_rtree(); - AtEOXact_Namespace(false); - AtAbort_Cache(); - AtEOXact_CatCache(false); - AtAbort_Memory(); - AtEOXact_Files(); - AtAbort_Locks(); - - /* Count transaction abort in statistics collector */ - pgstat_count_xact_rollback(); - - /* - * State remains TRANS_ABORT until CleanupTransaction(). - */ - RESUME_INTERRUPTS(); -} - -/* -------------------------------- - * CleanupTransaction - * - * -------------------------------- - */ -static void -CleanupTransaction(void) -{ - TransactionState s = CurrentTransactionState; - - /* - * State should still be TRANS_ABORT from AbortTransaction(). - */ - if (s->state != TRANS_ABORT) - elog(FATAL, "CleanupTransaction and not in abort state"); - - /* - * do abort cleanup processing - */ - AtCleanup_Memory(); - - /* - * done with abort processing, set current transaction state back to - * default - */ - s->state = TRANS_DEFAULT; -} - -/* -------------------------------- - * StartTransactionCommand - * -------------------------------- - */ -void -StartTransactionCommand(void) -{ - TransactionState s = CurrentTransactionState; - - switch (s->blockState) - { - /* - * if we aren't in a transaction block, we just do our usual - * start transaction. - */ - case TBLOCK_DEFAULT: - StartTransaction(); - break; - - /* - * We should never experience this -- if we do it means the - * BEGIN state was not changed in the previous - * CommitTransactionCommand(). If we get it, we print a - * warning and change to the in-progress state. - */ - case TBLOCK_BEGIN: - elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_BEGIN"); - s->blockState = TBLOCK_INPROGRESS; - break; - - /* - * This is the case when are somewhere in a transaction block - * and about to start a new command. For now we do nothing - * but someday we may do command-local resource - * initialization. - */ - case TBLOCK_INPROGRESS: - break; - - /* - * As with BEGIN, we should never experience this if we do it - * means the END state was not changed in the previous - * CommitTransactionCommand(). If we get it, we print a - * warning, commit the transaction, start a new transaction - * and change to the default state. - */ - case TBLOCK_END: - elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_END"); - s->blockState = TBLOCK_DEFAULT; - CommitTransaction(); - StartTransaction(); - break; - - /* - * Here we are in the middle of a transaction block but one of - * the commands caused an abort so we do nothing but remain in - * the abort state. Eventually we will get to the "END - * TRANSACTION" which will set things straight. - */ - case TBLOCK_ABORT: - break; - - /* - * This means we somehow aborted and the last call to - * CommitTransactionCommand() didn't clear the state so we - * remain in the ENDABORT state and maybe next time we get to - * CommitTransactionCommand() the state will get reset to - * default. - */ - case TBLOCK_ENDABORT: - elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_ENDABORT"); - break; - } - - /* - * We must switch to TransactionCommandContext before returning. This - * is already done if we called StartTransaction, otherwise not. - */ - Assert(TransactionCommandContext != NULL); - MemoryContextSwitchTo(TransactionCommandContext); -} - -/* -------------------------------- - * CommitTransactionCommand - * -------------------------------- - */ -void -CommitTransactionCommand(void) -{ - TransactionState s = CurrentTransactionState; - - switch (s->blockState) - { - /* - * if we aren't in a transaction block, we just do our usual - * transaction commit - */ - case TBLOCK_DEFAULT: - CommitTransaction(); - break; - - /* - * This is the case right after we get a "BEGIN TRANSACTION" - * command, but the user hasn't done anything else yet, so we - * change to the "transaction block in progress" state and - * return. - */ - case TBLOCK_BEGIN: - s->blockState = TBLOCK_INPROGRESS; - break; - - /* - * This is the case when we have finished executing a command - * someplace within a transaction block. We increment the - * command counter and return. Someday we may free resources - * local to the command. - * - * That someday is today, at least for memory allocated in - * TransactionCommandContext. - vadim 03/25/97 - */ - case TBLOCK_INPROGRESS: - CommandCounterIncrement(); - MemoryContextResetAndDeleteChildren(TransactionCommandContext); - break; - - /* - * This is the case when we just got the "END TRANSACTION" - * statement, so we commit the transaction and go back to the - * default state. - */ - case TBLOCK_END: - CommitTransaction(); - s->blockState = TBLOCK_DEFAULT; - break; - - /* - * Here we are in the middle of a transaction block but one of - * the commands caused an abort so we do nothing but remain in - * the abort state. Eventually we will get to the "END - * TRANSACTION" which will set things straight. - */ - case TBLOCK_ABORT: - break; - - /* - * Here we were in an aborted transaction block which just - * processed the "END TRANSACTION" command from the user, so - * clean up and return to the default state. - */ - case TBLOCK_ENDABORT: - CleanupTransaction(); - s->blockState = TBLOCK_DEFAULT; - break; - } -} - -/* -------------------------------- - * AbortCurrentTransaction - * -------------------------------- - */ -void -AbortCurrentTransaction(void) -{ - TransactionState s = CurrentTransactionState; - - switch (s->blockState) - { - /* - * if we aren't in a transaction block, we just do the basic - * abort & cleanup transaction. - */ - case TBLOCK_DEFAULT: - AbortTransaction(); - CleanupTransaction(); - break; - - /* - * If we are in the TBLOCK_BEGIN it means something screwed up - * right after reading "BEGIN TRANSACTION" so we enter the - * abort state. Eventually an "END TRANSACTION" will fix - * things. - */ - case TBLOCK_BEGIN: - s->blockState = TBLOCK_ABORT; - AbortTransaction(); - /* CleanupTransaction happens when we exit TBLOCK_ABORT */ - break; - - /* - * This is the case when are somewhere in a transaction block - * which aborted so we abort the transaction and set the ABORT - * state. Eventually an "END TRANSACTION" will fix things and - * restore us to a normal state. - */ - case TBLOCK_INPROGRESS: - s->blockState = TBLOCK_ABORT; - AbortTransaction(); - /* CleanupTransaction happens when we exit TBLOCK_ABORT */ - break; - - /* - * Here, the system was fouled up just after the user wanted - * to end the transaction block so we abort the transaction - * and put us back into the default state. - */ - case TBLOCK_END: - s->blockState = TBLOCK_DEFAULT; - AbortTransaction(); - CleanupTransaction(); - break; - - /* - * Here, we are already in an aborted transaction state and - * are waiting for an "END TRANSACTION" to come along and lo - * and behold, we abort again! So we just remain in the abort - * state. - */ - case TBLOCK_ABORT: - break; - - /* - * Here we were in an aborted transaction block which just - * processed the "END TRANSACTION" command but somehow aborted - * again.. since we must have done the abort processing, we - * clean up and return to the default state. - */ - case TBLOCK_ENDABORT: - CleanupTransaction(); - s->blockState = TBLOCK_DEFAULT; - break; - } -} - -/* ---------------------------------------------------------------- - * transaction block support - * ---------------------------------------------------------------- - */ -/* -------------------------------- - * BeginTransactionBlock - * -------------------------------- - */ -void -BeginTransactionBlock(void) -{ - TransactionState s = CurrentTransactionState; - - /* - * check the current transaction state - */ - if (s->blockState != TBLOCK_DEFAULT) - elog(WARNING, "BEGIN: already a transaction in progress"); - - /* - * set the current transaction block state information appropriately - * during begin processing - */ - s->blockState = TBLOCK_BEGIN; - - /* - * do begin processing - */ - - /* - * done with begin processing, set block state to inprogress - */ - s->blockState = TBLOCK_INPROGRESS; -} - -/* -------------------------------- - * EndTransactionBlock - * -------------------------------- - */ -void -EndTransactionBlock(void) -{ - TransactionState s = CurrentTransactionState; - - /* - * check the current transaction state - */ - if (s->blockState == TBLOCK_INPROGRESS) - { - /* - * here we are in a transaction block which should commit when we - * get to the upcoming CommitTransactionCommand() so we set the - * state to "END". CommitTransactionCommand() will recognize this - * and commit the transaction and return us to the default state - */ - s->blockState = TBLOCK_END; - return; - } - - if (s->blockState == TBLOCK_ABORT) - { - /* - * here, we are in a transaction block which aborted and since the - * AbortTransaction() was already done, we do whatever is needed - * and change to the special "END ABORT" state. The upcoming - * CommitTransactionCommand() will recognise this and then put us - * back in the default state. - */ - s->blockState = TBLOCK_ENDABORT; - return; - } - - /* - * here, the user issued COMMIT when not inside a transaction. Issue a - * WARNING and go to abort state. The upcoming call to - * CommitTransactionCommand() will then put us back into the default - * state. - */ - elog(WARNING, "COMMIT: no transaction in progress"); - AbortTransaction(); - s->blockState = TBLOCK_ENDABORT; -} - -/* -------------------------------- - * AbortTransactionBlock - * -------------------------------- - */ -#ifdef NOT_USED -static void -AbortTransactionBlock(void) -{ - TransactionState s = CurrentTransactionState; - - /* - * check the current transaction state - */ - if (s->blockState == TBLOCK_INPROGRESS) - { - /* - * here we were inside a transaction block something screwed up - * inside the system so we enter the abort state, do the abort - * processing and then return. We remain in the abort state until - * we see an END TRANSACTION command. - */ - s->blockState = TBLOCK_ABORT; - AbortTransaction(); - return; - } - - /* - * here, the user issued ABORT when not inside a transaction. Issue a - * WARNING and go to abort state. The upcoming call to - * CommitTransactionCommand() will then put us back into the default - * state. - */ - elog(WARNING, "ROLLBACK: no transaction in progress"); - AbortTransaction(); - s->blockState = TBLOCK_ENDABORT; -} -#endif - -/* -------------------------------- - * UserAbortTransactionBlock - * -------------------------------- - */ -void -UserAbortTransactionBlock(void) -{ - TransactionState s = CurrentTransactionState; - - /* - * if the transaction has already been automatically aborted with an - * error, and the user subsequently types 'abort', allow it. (the - * behavior is the same as if they had typed 'end'.) - */ - if (s->blockState == TBLOCK_ABORT) - { - s->blockState = TBLOCK_ENDABORT; - return; - } - - if (s->blockState == TBLOCK_INPROGRESS) - { - /* - * here we were inside a transaction block and we got an abort - * command from the user, so we move to the abort state, do the - * abort processing and then change to the ENDABORT state so we - * will end up in the default state after the upcoming - * CommitTransactionCommand(). - */ - s->blockState = TBLOCK_ABORT; - AbortTransaction(); - s->blockState = TBLOCK_ENDABORT; - return; - } - - /* - * here, the user issued ABORT when not inside a transaction. Issue a - * WARNING and go to abort state. The upcoming call to - * CommitTransactionCommand() will then put us back into the default - * state. - */ - elog(WARNING, "ROLLBACK: no transaction in progress"); - AbortTransaction(); - s->blockState = TBLOCK_ENDABORT; -} - -/* -------------------------------- - * AbortOutOfAnyTransaction - * - * This routine is provided for error recovery purposes. It aborts any - * active transaction or transaction block, leaving the system in a known - * idle state. - * -------------------------------- - */ -void -AbortOutOfAnyTransaction(void) -{ - TransactionState s = CurrentTransactionState; - - /* - * Get out of any low-level transaction - */ - switch (s->state) - { - case TRANS_START: - case TRANS_INPROGRESS: - case TRANS_COMMIT: - /* In a transaction, so clean up */ - AbortTransaction(); - CleanupTransaction(); - break; - case TRANS_ABORT: - /* AbortTransaction already done, still need Cleanup */ - CleanupTransaction(); - break; - case TRANS_DEFAULT: - /* Not in a transaction, do nothing */ - break; - } - - /* - * Now reset the high-level state - */ - s->blockState = TBLOCK_DEFAULT; -} - -bool -IsTransactionBlock(void) -{ - TransactionState s = CurrentTransactionState; - - if (s->blockState == TBLOCK_INPROGRESS - || s->blockState == TBLOCK_ABORT - || s->blockState == TBLOCK_ENDABORT) - return true; - - return false; -} - -void -xact_redo(XLogRecPtr lsn, XLogRecord *record) -{ - uint8 info = record->xl_info & ~XLR_INFO_MASK; - - if (info == XLOG_XACT_COMMIT) - { - TransactionIdCommit(record->xl_xid); - /* SHOULD REMOVE FILES OF ALL DROPPED RELATIONS */ - } - else if (info == XLOG_XACT_ABORT) - { - TransactionIdAbort(record->xl_xid); - /* SHOULD REMOVE FILES OF ALL FAILED-TO-BE-CREATED RELATIONS */ - } - else - elog(PANIC, "xact_redo: unknown op code %u", info); -} - -void -xact_undo(XLogRecPtr lsn, XLogRecord *record) -{ - uint8 info = record->xl_info & ~XLR_INFO_MASK; - - if (info == XLOG_XACT_COMMIT) /* shouldn't be called by XLOG */ - elog(PANIC, "xact_undo: can't undo committed xaction"); - else if (info != XLOG_XACT_ABORT) - elog(PANIC, "xact_redo: unknown op code %u", info); -} - -void -xact_desc(char *buf, uint8 xl_info, char *rec) -{ - uint8 info = xl_info & ~XLR_INFO_MASK; - - if (info == XLOG_XACT_COMMIT) - { - xl_xact_commit *xlrec = (xl_xact_commit *) rec; - struct tm *tm = localtime(&xlrec->xtime); - - sprintf(buf + strlen(buf), "commit: %04u-%02u-%02u %02u:%02u:%02u", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); - } - else if (info == XLOG_XACT_ABORT) - { - xl_xact_abort *xlrec = (xl_xact_abort *) rec; - struct tm *tm = localtime(&xlrec->xtime); - - sprintf(buf + strlen(buf), "abort: %04u-%02u-%02u %02u:%02u:%02u", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); - } - else - strcat(buf, "UNKNOWN"); -} - -void - XactPushRollback(void (*func) (void *), void *data) -{ -#ifdef XLOG_II - if (_RollbackFunc != NULL) - elog(PANIC, "XactPushRollback: already installed"); -#endif - - _RollbackFunc = func; - _RollbackData = data; -} - -void -XactPopRollback(void) -{ - _RollbackFunc = NULL; -} |