postgresql/src/backend/access/transam/xact.c

1786 lines
46 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* xact.c
* top level transaction system support routines
*
2002-06-20 22:29:54 +02:00
* 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.134 2002/10/21 22:06:18 tgl 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
2000-01-05 19:23:54 +01:00
* 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
*
*-------------------------------------------------------------------------
*/
1996-10-21 09:15:18 +02:00
/*
* Large object clean up added in CommitTransaction() to prevent buffer leaks.
* [PA, 7/17/98]
* [PA] is Pascal Andr<EFBFBD> <andre@via.ecp.fr>
*/
#include "postgres.h"
1996-10-21 09:15:18 +02:00
2001-11-01 07:17:01 +01:00
#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"
2000-02-18 10:30:20 +01:00
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "commands/async.h"
#include "commands/trigger.h"
#include "commands/user.h"
#include "executor/spi.h"
#include "libpq/be-fsstubs.h"
#include "miscadmin.h"
1999-07-16 07:00:38 +02:00
#include "storage/proc.h"
#include "storage/sinval.h"
#include "storage/smgr.h"
#include "utils/guc.h"
1999-07-16 07:00:38 +02:00
#include "utils/inval.h"
#include "utils/memutils.h"
1999-07-16 07:00:38 +02:00
#include "utils/portal.h"
#include "utils/catcache.h"
1999-07-16 07:00:38 +02:00
#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
*/
1999-05-25 18:15:34 +02:00
int DefaultXactIsoLevel = XACT_READ_COMMITTED;
int XactIsoLevel;
bool autocommit = true;
int CommitDelay = 0; /* precommit delay in microseconds */
2001-03-22 05:01:46 +01:00
int CommitSiblings = 5; /* number of concurrent xacts needed to
* sleep */
static bool suppressChain = false;
2001-03-22 05:01:46 +01:00
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...
*/
1998-09-01 05:29:17 +02:00
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;
2002-09-04 22:31:48 +02:00
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
*/
2000-10-28 18:21:00 +02:00
void
RecordTransactionCommit(void)
{
/*
2002-09-04 22:31:48 +02:00
* If we made neither any XLOG entries nor any temp-rel updates, we
* can omit recording the transaction commit at all.
*/
if (MyXactMadeXLogEntry || MyXactMadeTempRelUpdate)
2000-10-24 11:56:23 +02:00
{
TransactionId xid = GetCurrentTransactionId();
2001-03-22 05:01:46 +01:00
XLogRecPtr recptr;
2000-10-24 11:56:23 +02:00
/* Tell bufmgr and smgr to prepare for commit */
2000-10-28 18:21:00 +02:00
BufmgrCommit();
START_CRIT_SECTION();
2001-03-22 05:01:46 +01:00
/*
2002-09-04 22:31:48 +02:00
* We only need to log the commit in xlog if the transaction made
* any transaction-controlled XLOG entries. (Otherwise, its XID
* appears nowhere in permanent storage, so no one else will ever
* care if it committed.)
*/
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;
}
2000-10-24 11:56:23 +02:00
2001-03-22 05:01:46 +01:00
/*
2002-09-04 22:31:48 +02:00
* We must flush our XLOG entries 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.
2000-10-24 11:56:23 +02:00
*/
if (MyXactMadeXLogEntry)
{
/*
* Sleep before flush! So we can flush more than one commit
2002-09-04 22:31:48 +02:00
* 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);
}
XLOG (and related) changes: * Store two past checkpoint locations, not just one, in pg_control. On startup, we fall back to the older checkpoint if the newer one is unreadable. Also, a physical copy of the newest checkpoint record is kept in pg_control for possible use in disaster recovery (ie, complete loss of pg_xlog). Also add a version number for pg_control itself. Remove archdir from pg_control; it ought to be a GUC parameter, not a special case (not that it's implemented yet anyway). * Suppress successive checkpoint records when nothing has been entered in the WAL log since the last one. This is not so much to avoid I/O as to make it actually useful to keep track of the last two checkpoints. If the things are right next to each other then there's not a lot of redundancy gained... * Change CRC scheme to a true 64-bit CRC, not a pair of 32-bit CRCs on alternate bytes. Polynomial borrowed from ECMA DLT1 standard. * Fix XLOG record length handling so that it will work at BLCKSZ = 32k. * Change XID allocation to work more like OID allocation. (This is of dubious necessity, but I think it's a good idea anyway.) * Fix a number of minor bugs, such as off-by-one logic for XLOG file wraparound at the 4 gig mark. * Add documentation and clean up some coding infelicities; move file format declarations out to include files where planned contrib utilities can get at them. * Checkpoint will now occur every CHECKPOINT_SEGMENTS log segments or every CHECKPOINT_TIMEOUT seconds, whichever comes first. It is also possible to force a checkpoint by sending SIGUSR1 to the postmaster (undocumented feature...) * Defend against kill -9 postmaster by storing shmem block's key and ID in postmaster.pid lockfile, and checking at startup to ensure that no processes are still connected to old shmem block (if it still exists). * Switch backends to accept SIGQUIT rather than SIGUSR1 for emergency stop, for symmetry with postmaster and xlog utilities. Clean up signal handling in bootstrap.c so that xlog utilities launched by postmaster will react to signals better. * Standalone bootstrap now grabs lockfile in target directory, as added insurance against running it in parallel with live postmaster.
2001-03-13 02:17:06 +01:00
/*
2002-09-04 22:31:48 +02:00
* We must mark the transaction committed in clog if its XID
* appears either in permanent rels or in local temporary rels.
* We test this by seeing if we made transaction-controlled
* entries *OR* local-rel tuple updates. Note that if we made
* only the latter, we have not emitted an XLOG record for our
* commit, and so in the event of a crash the clog update might be
* lost. This is okay because no one else will ever care whether
* we committed.
*/
if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate)
TransactionIdCommit(xid);
2000-10-24 11:56:23 +02:00
END_CRIT_SECTION();
2000-10-24 11:56:23 +02:00
}
/* Break the chain of back-links in the XLOG records I output */
MyLastRecPtr.xrecoff = 0;
MyXactMadeXLogEntry = false;
MyXactMadeTempRelUpdate = false;
/* Show myself as out of the transaction in PGPROC array */
XLOG (and related) changes: * Store two past checkpoint locations, not just one, in pg_control. On startup, we fall back to the older checkpoint if the newer one is unreadable. Also, a physical copy of the newest checkpoint record is kept in pg_control for possible use in disaster recovery (ie, complete loss of pg_xlog). Also add a version number for pg_control itself. Remove archdir from pg_control; it ought to be a GUC parameter, not a special case (not that it's implemented yet anyway). * Suppress successive checkpoint records when nothing has been entered in the WAL log since the last one. This is not so much to avoid I/O as to make it actually useful to keep track of the last two checkpoints. If the things are right next to each other then there's not a lot of redundancy gained... * Change CRC scheme to a true 64-bit CRC, not a pair of 32-bit CRCs on alternate bytes. Polynomial borrowed from ECMA DLT1 standard. * Fix XLOG record length handling so that it will work at BLCKSZ = 32k. * Change XID allocation to work more like OID allocation. (This is of dubious necessity, but I think it's a good idea anyway.) * Fix a number of minor bugs, such as off-by-one logic for XLOG file wraparound at the 4 gig mark. * Add documentation and clean up some coding infelicities; move file format declarations out to include files where planned contrib utilities can get at them. * Checkpoint will now occur every CHECKPOINT_SEGMENTS log segments or every CHECKPOINT_TIMEOUT seconds, whichever comes first. It is also possible to force a checkpoint by sending SIGUSR1 to the postmaster (undocumented feature...) * Defend against kill -9 postmaster by storing shmem block's key and ID in postmaster.pid lockfile, and checking at startup to ensure that no processes are still connected to old shmem block (if it still exists). * Switch backends to accept SIGQUIT rather than SIGUSR1 for emergency stop, for symmetry with postmaster and xlog utilities. Clean up signal handling in bootstrap.c so that xlog utilities launched by postmaster will react to signals better. * Standalone bootstrap now grabs lockfile in target directory, as added insurance against running it in parallel with live postmaster.
2001-03-13 02:17:06 +01:00
MyProc->logRec.xrecoff = 0;
}
/* --------------------------------
* AtCommit_Cache
* --------------------------------
*/
static void
AtCommit_Cache(void)
{
/*
* Clean up the relation cache.
*/
AtEOXact_RelationCache(true);
2002-09-04 22:31:48 +02:00
/*
* 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)
{
XLOG (and related) changes: * Store two past checkpoint locations, not just one, in pg_control. On startup, we fall back to the older checkpoint if the newer one is unreadable. Also, a physical copy of the newest checkpoint record is kept in pg_control for possible use in disaster recovery (ie, complete loss of pg_xlog). Also add a version number for pg_control itself. Remove archdir from pg_control; it ought to be a GUC parameter, not a special case (not that it's implemented yet anyway). * Suppress successive checkpoint records when nothing has been entered in the WAL log since the last one. This is not so much to avoid I/O as to make it actually useful to keep track of the last two checkpoints. If the things are right next to each other then there's not a lot of redundancy gained... * Change CRC scheme to a true 64-bit CRC, not a pair of 32-bit CRCs on alternate bytes. Polynomial borrowed from ECMA DLT1 standard. * Fix XLOG record length handling so that it will work at BLCKSZ = 32k. * Change XID allocation to work more like OID allocation. (This is of dubious necessity, but I think it's a good idea anyway.) * Fix a number of minor bugs, such as off-by-one logic for XLOG file wraparound at the 4 gig mark. * Add documentation and clean up some coding infelicities; move file format declarations out to include files where planned contrib utilities can get at them. * Checkpoint will now occur every CHECKPOINT_SEGMENTS log segments or every CHECKPOINT_TIMEOUT seconds, whichever comes first. It is also possible to force a checkpoint by sending SIGUSR1 to the postmaster (undocumented feature...) * Defend against kill -9 postmaster by storing shmem block's key and ID in postmaster.pid lockfile, and checking at startup to ensure that no processes are still connected to old shmem block (if it still exists). * Switch backends to accept SIGQUIT rather than SIGUSR1 for emergency stop, for symmetry with postmaster and xlog utilities. Clean up signal handling in bootstrap.c so that xlog utilities launched by postmaster will react to signals better. * Standalone bootstrap now grabs lockfile in target directory, as added insurance against running it in parallel with live postmaster.
2001-03-13 02:17:06 +01:00
/*
* If we made neither any transaction-controlled XLOG entries nor any
2002-09-04 22:31:48 +02:00
* temp-rel updates, we can omit recording the transaction abort at
* all. No one will ever care that it aborted.
XLOG (and related) changes: * Store two past checkpoint locations, not just one, in pg_control. On startup, we fall back to the older checkpoint if the newer one is unreadable. Also, a physical copy of the newest checkpoint record is kept in pg_control for possible use in disaster recovery (ie, complete loss of pg_xlog). Also add a version number for pg_control itself. Remove archdir from pg_control; it ought to be a GUC parameter, not a special case (not that it's implemented yet anyway). * Suppress successive checkpoint records when nothing has been entered in the WAL log since the last one. This is not so much to avoid I/O as to make it actually useful to keep track of the last two checkpoints. If the things are right next to each other then there's not a lot of redundancy gained... * Change CRC scheme to a true 64-bit CRC, not a pair of 32-bit CRCs on alternate bytes. Polynomial borrowed from ECMA DLT1 standard. * Fix XLOG record length handling so that it will work at BLCKSZ = 32k. * Change XID allocation to work more like OID allocation. (This is of dubious necessity, but I think it's a good idea anyway.) * Fix a number of minor bugs, such as off-by-one logic for XLOG file wraparound at the 4 gig mark. * Add documentation and clean up some coding infelicities; move file format declarations out to include files where planned contrib utilities can get at them. * Checkpoint will now occur every CHECKPOINT_SEGMENTS log segments or every CHECKPOINT_TIMEOUT seconds, whichever comes first. It is also possible to force a checkpoint by sending SIGUSR1 to the postmaster (undocumented feature...) * Defend against kill -9 postmaster by storing shmem block's key and ID in postmaster.pid lockfile, and checking at startup to ensure that no processes are still connected to old shmem block (if it still exists). * Switch backends to accept SIGQUIT rather than SIGUSR1 for emergency stop, for symmetry with postmaster and xlog utilities. Clean up signal handling in bootstrap.c so that xlog utilities launched by postmaster will react to signals better. * Standalone bootstrap now grabs lockfile in target directory, as added insurance against running it in parallel with live postmaster.
2001-03-13 02:17:06 +01:00
*/
if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate)
{
TransactionId xid = GetCurrentTransactionId();
/*
* Catch the scenario where we aborted partway through
* RecordTransactionCommit ...
*/
if (TransactionIdDidCommit(xid))
elog(PANIC, "RecordTransactionAbort: xact %u already committed",
xid);
START_CRIT_SECTION();
XLOG (and related) changes: * Store two past checkpoint locations, not just one, in pg_control. On startup, we fall back to the older checkpoint if the newer one is unreadable. Also, a physical copy of the newest checkpoint record is kept in pg_control for possible use in disaster recovery (ie, complete loss of pg_xlog). Also add a version number for pg_control itself. Remove archdir from pg_control; it ought to be a GUC parameter, not a special case (not that it's implemented yet anyway). * Suppress successive checkpoint records when nothing has been entered in the WAL log since the last one. This is not so much to avoid I/O as to make it actually useful to keep track of the last two checkpoints. If the things are right next to each other then there's not a lot of redundancy gained... * Change CRC scheme to a true 64-bit CRC, not a pair of 32-bit CRCs on alternate bytes. Polynomial borrowed from ECMA DLT1 standard. * Fix XLOG record length handling so that it will work at BLCKSZ = 32k. * Change XID allocation to work more like OID allocation. (This is of dubious necessity, but I think it's a good idea anyway.) * Fix a number of minor bugs, such as off-by-one logic for XLOG file wraparound at the 4 gig mark. * Add documentation and clean up some coding infelicities; move file format declarations out to include files where planned contrib utilities can get at them. * Checkpoint will now occur every CHECKPOINT_SEGMENTS log segments or every CHECKPOINT_TIMEOUT seconds, whichever comes first. It is also possible to force a checkpoint by sending SIGUSR1 to the postmaster (undocumented feature...) * Defend against kill -9 postmaster by storing shmem block's key and ID in postmaster.pid lockfile, and checking at startup to ensure that no processes are still connected to old shmem block (if it still exists). * Switch backends to accept SIGQUIT rather than SIGUSR1 for emergency stop, for symmetry with postmaster and xlog utilities. Clean up signal handling in bootstrap.c so that xlog utilities launched by postmaster will react to signals better. * Standalone bootstrap now grabs lockfile in target directory, as added insurance against running it in parallel with live postmaster.
2001-03-13 02:17:06 +01:00
/*
2002-09-04 22:31:48 +02:00
* We only need to log the abort in XLOG if the transaction made
* any transaction-controlled XLOG entries. (Otherwise, its XID
* appears nowhere in permanent storage, so no one else 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.
*/
if (MyLastRecPtr.xrecoff != 0)
{
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;
/*
* SHOULD SAVE ARRAY OF RELFILENODE-s TO DROP
*/
recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, &rdata);
}
/*
* Mark the transaction aborted in clog. This is not absolutely
* necessary but we may as well do it while we are here.
*/
2000-11-30 09:46:26 +01:00
TransactionIdAbort(xid);
XLOG (and related) changes: * Store two past checkpoint locations, not just one, in pg_control. On startup, we fall back to the older checkpoint if the newer one is unreadable. Also, a physical copy of the newest checkpoint record is kept in pg_control for possible use in disaster recovery (ie, complete loss of pg_xlog). Also add a version number for pg_control itself. Remove archdir from pg_control; it ought to be a GUC parameter, not a special case (not that it's implemented yet anyway). * Suppress successive checkpoint records when nothing has been entered in the WAL log since the last one. This is not so much to avoid I/O as to make it actually useful to keep track of the last two checkpoints. If the things are right next to each other then there's not a lot of redundancy gained... * Change CRC scheme to a true 64-bit CRC, not a pair of 32-bit CRCs on alternate bytes. Polynomial borrowed from ECMA DLT1 standard. * Fix XLOG record length handling so that it will work at BLCKSZ = 32k. * Change XID allocation to work more like OID allocation. (This is of dubious necessity, but I think it's a good idea anyway.) * Fix a number of minor bugs, such as off-by-one logic for XLOG file wraparound at the 4 gig mark. * Add documentation and clean up some coding infelicities; move file format declarations out to include files where planned contrib utilities can get at them. * Checkpoint will now occur every CHECKPOINT_SEGMENTS log segments or every CHECKPOINT_TIMEOUT seconds, whichever comes first. It is also possible to force a checkpoint by sending SIGUSR1 to the postmaster (undocumented feature...) * Defend against kill -9 postmaster by storing shmem block's key and ID in postmaster.pid lockfile, and checking at startup to ensure that no processes are still connected to old shmem block (if it still exists). * Switch backends to accept SIGQUIT rather than SIGUSR1 for emergency stop, for symmetry with postmaster and xlog utilities. Clean up signal handling in bootstrap.c so that xlog utilities launched by postmaster will react to signals better. * Standalone bootstrap now grabs lockfile in target directory, as added insurance against running it in parallel with live postmaster.
2001-03-13 02:17:06 +01:00
END_CRIT_SECTION();
}
XLOG (and related) changes: * Store two past checkpoint locations, not just one, in pg_control. On startup, we fall back to the older checkpoint if the newer one is unreadable. Also, a physical copy of the newest checkpoint record is kept in pg_control for possible use in disaster recovery (ie, complete loss of pg_xlog). Also add a version number for pg_control itself. Remove archdir from pg_control; it ought to be a GUC parameter, not a special case (not that it's implemented yet anyway). * Suppress successive checkpoint records when nothing has been entered in the WAL log since the last one. This is not so much to avoid I/O as to make it actually useful to keep track of the last two checkpoints. If the things are right next to each other then there's not a lot of redundancy gained... * Change CRC scheme to a true 64-bit CRC, not a pair of 32-bit CRCs on alternate bytes. Polynomial borrowed from ECMA DLT1 standard. * Fix XLOG record length handling so that it will work at BLCKSZ = 32k. * Change XID allocation to work more like OID allocation. (This is of dubious necessity, but I think it's a good idea anyway.) * Fix a number of minor bugs, such as off-by-one logic for XLOG file wraparound at the 4 gig mark. * Add documentation and clean up some coding infelicities; move file format declarations out to include files where planned contrib utilities can get at them. * Checkpoint will now occur every CHECKPOINT_SEGMENTS log segments or every CHECKPOINT_TIMEOUT seconds, whichever comes first. It is also possible to force a checkpoint by sending SIGUSR1 to the postmaster (undocumented feature...) * Defend against kill -9 postmaster by storing shmem block's key and ID in postmaster.pid lockfile, and checking at startup to ensure that no processes are still connected to old shmem block (if it still exists). * Switch backends to accept SIGQUIT rather than SIGUSR1 for emergency stop, for symmetry with postmaster and xlog utilities. Clean up signal handling in bootstrap.c so that xlog utilities launched by postmaster will react to signals better. * Standalone bootstrap now grabs lockfile in target directory, as added insurance against running it in parallel with live postmaster.
2001-03-13 02:17:06 +01:00
/* Break the chain of back-links in the XLOG records I output */
MyLastRecPtr.xrecoff = 0;
MyXactMadeXLogEntry = false;
MyXactMadeTempRelUpdate = false;
/* Show myself as out of the transaction in PGPROC array */
XLOG (and related) changes: * Store two past checkpoint locations, not just one, in pg_control. On startup, we fall back to the older checkpoint if the newer one is unreadable. Also, a physical copy of the newest checkpoint record is kept in pg_control for possible use in disaster recovery (ie, complete loss of pg_xlog). Also add a version number for pg_control itself. Remove archdir from pg_control; it ought to be a GUC parameter, not a special case (not that it's implemented yet anyway). * Suppress successive checkpoint records when nothing has been entered in the WAL log since the last one. This is not so much to avoid I/O as to make it actually useful to keep track of the last two checkpoints. If the things are right next to each other then there's not a lot of redundancy gained... * Change CRC scheme to a true 64-bit CRC, not a pair of 32-bit CRCs on alternate bytes. Polynomial borrowed from ECMA DLT1 standard. * Fix XLOG record length handling so that it will work at BLCKSZ = 32k. * Change XID allocation to work more like OID allocation. (This is of dubious necessity, but I think it's a good idea anyway.) * Fix a number of minor bugs, such as off-by-one logic for XLOG file wraparound at the 4 gig mark. * Add documentation and clean up some coding infelicities; move file format declarations out to include files where planned contrib utilities can get at them. * Checkpoint will now occur every CHECKPOINT_SEGMENTS log segments or every CHECKPOINT_TIMEOUT seconds, whichever comes first. It is also possible to force a checkpoint by sending SIGUSR1 to the postmaster (undocumented feature...) * Defend against kill -9 postmaster by storing shmem block's key and ID in postmaster.pid lockfile, and checking at startup to ensure that no processes are still connected to old shmem block (if it still exists). * Switch backends to accept SIGQUIT rather than SIGUSR1 for emergency stop, for symmetry with postmaster and xlog utilities. Clean up signal handling in bootstrap.c so that xlog utilities launched by postmaster will react to signals better. * Standalone bootstrap now grabs lockfile in target directory, as added insurance against running it in parallel with live postmaster.
2001-03-13 02:17:06 +01:00
MyProc->logRec.xrecoff = 0;
}
/* --------------------------------
* AtAbort_Cache
* --------------------------------
*/
static void
AtAbort_Cache(void)
{
AtEOXact_RelationCache(false);
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;
1998-12-16 12:53:55 +01:00
FreeXactSnapshot();
XactIsoLevel = DefaultXactIsoLevel;
1998-12-16 12:53:55 +01:00
/*
* 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;
2000-02-18 10:30:20 +01:00
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)
{
1998-09-01 05:29:17 +02:00
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 pre-commit processing (most of this stuff requires database
* access, and in fact could still cause an error...)
*/
AtEOXact_portals();
/* handle commit for large objects [ PA, 7/17/98 ] */
/* XXX probably this does not belong here */
lo_commit(true);
/* NOTIFY commit must come before lower-level cleanup */
AtCommit_Notify();
/* Update the flat password file if we changed pg_shadow or pg_group */
AtEOXact_UpdatePasswordFile(true);
/*
* 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.
*/
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_Buffers(true);
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();
2001-03-22 05:01:46 +01:00
/*
2001-03-22 05:01:46 +01:00
* 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();
AtEOXact_portals();
lo_commit(false); /* 'false' means it's abort */
AtAbort_Notify();
AtEOXact_UpdatePasswordFile(false);
/* 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);
}
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_Buffers(false);
smgrabort();
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
*
* preventChain, if true, forces autocommit behavior at the next
* CommitTransactionCommand call.
* --------------------------------
*/
void
StartTransactionCommand(bool preventChain)
{
TransactionState s = CurrentTransactionState;
/*
2002-09-04 22:31:48 +02:00
* Remember if caller wants to prevent autocommit-off chaining. This
* is only allowed if not already in a transaction block.
*/
suppressChain = preventChain;
if (preventChain && s->blockState != TBLOCK_DEFAULT)
elog(ERROR, "StartTransactionCommand: can't prevent chain");
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;
}
/*
2001-03-22 05:01:46 +01:00
* We must switch to TransactionCommandContext before returning. This
* is already done if we called StartTransaction, otherwise not.
*/
Assert(TransactionCommandContext != NULL);
MemoryContextSwitchTo(TransactionCommandContext);
}
/* --------------------------------
* CommitTransactionCommand
*
* forceCommit = true forces autocommit behavior even when autocommit is off.
* --------------------------------
*/
void
CommitTransactionCommand(bool forceCommit)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
/*
* If we aren't in a transaction block, and we are doing
2002-09-04 22:31:48 +02:00
* autocommit, just do our usual transaction commit. But if
* we aren't doing autocommit, start a transaction block
* automatically by switching to INPROGRESS state. (We handle
* this choice here, and not earlier, so that an explicit
* BEGIN issued in autocommit-off mode won't issue strange
* warnings.)
*
2002-09-04 22:31:48 +02:00
* Autocommit mode is forced by either a true forceCommit
* parameter to me, or a true preventChain parameter to the
* preceding StartTransactionCommand call, or a
* PreventTransactionChain call during the transaction.
* (The parameters could be omitted, but it turns out most
* callers of StartTransactionCommand/CommitTransactionCommand
* want to force autocommit, so making them all call
* PreventTransactionChain would just be extra notation.)
*/
case TBLOCK_DEFAULT:
if (autocommit || forceCommit || suppressChain)
CommitTransaction();
else
{
BeginTransactionBlock();
Assert(s->blockState == TBLOCK_INPROGRESS);
/* This code must match the TBLOCK_INPROGRESS case below: */
CommandCounterIncrement();
MemoryContextResetAndDeleteChildren(TransactionCommandContext);
}
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;
}
}
/* --------------------------------
* PreventTransactionChain
*
* This routine is to be called by statements that must not run inside
* a transaction block, typically because they have non-rollback-able
* side effects or do internal commits.
*
* If we have already started a transaction block, issue an error; also issue
* an error if we appear to be running inside a user-defined function (which
* could issue more commands and possibly cause a failure after the statement
* completes). In autocommit-off mode, we allow the statement if a block is
* not already started, and force the statement to be autocommitted despite
* the mode.
*
* stmtNode: pointer to parameter block for statement; this is used in
* a very klugy way to determine whether we are inside a function.
* stmtType: statement type name for error messages.
* --------------------------------
*/
void
PreventTransactionChain(void *stmtNode, const char *stmtType)
{
/*
* xact block already started?
*/
if (IsTransactionBlock())
{
/* translator: %s represents an SQL statement name */
elog(ERROR, "%s cannot run inside a transaction block", stmtType);
}
/*
* Are we inside a function call? If the statement's parameter block
* was allocated in QueryContext, assume it is an interactive command.
* Otherwise assume it is coming from a function.
*/
if (!MemoryContextContains(QueryContext, stmtNode))
{
/* translator: %s represents an SQL statement name */
elog(ERROR, "%s cannot be executed from a function", stmtType);
}
/* If we got past IsTransactionBlock test, should be in default state */
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT)
elog(ERROR, "PreventTransactionChain: can't prevent chain");
/* okay to set the flag */
suppressChain = true;
/* If we're in autocommit-off node, generate a notice */
if (!autocommit)
{
/* translator: %s represents an SQL statement name */
elog(NOTICE, "%s will be committed automatically", stmtType);
}
}
/* ----------------------------------------------------------------
* 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;
/*
2002-09-04 22:31:48 +02:00
* do begin processing. NOTE: if you put anything here, check that it
* behaves properly in both autocommit-on and autocommit-off modes. In
* the latter case we will already have done some work in the new
* transaction.
*/
/*
* 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;
}
1999-05-25 18:15:34 +02:00
/*
* 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)
1998-09-01 05:29:17 +02:00
return true;
1998-09-01 05:29:17 +02:00
return false;
}
void
xact_redo(XLogRecPtr lsn, XLogRecord *record)
{
2001-03-22 05:01:46 +01:00
uint8 info = record->xl_info & ~XLR_INFO_MASK;
if (info == XLOG_XACT_COMMIT)
{
2000-10-24 11:56:23 +02:00
TransactionIdCommit(record->xl_xid);
2000-11-30 09:46:26 +01:00
/* SHOULD REMOVE FILES OF ALL DROPPED RELATIONS */
}
else if (info == XLOG_XACT_ABORT)
{
2000-10-24 11:56:23 +02:00
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)
{
2001-03-22 05:01:46 +01:00
uint8 info = record->xl_info & ~XLR_INFO_MASK;
2001-03-22 05:01:46 +01:00
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);
}
2001-03-22 05:01:46 +01:00
2000-10-21 17:43:36 +02:00
void
2001-03-22 05:01:46 +01:00
xact_desc(char *buf, uint8 xl_info, char *rec)
2000-10-21 17:43:36 +02:00
{
2001-03-22 05:01:46 +01:00
uint8 info = xl_info & ~XLR_INFO_MASK;
2000-10-21 17:43:36 +02:00
if (info == XLOG_XACT_COMMIT)
{
2001-03-22 05:01:46 +01:00
xl_xact_commit *xlrec = (xl_xact_commit *) rec;
struct tm *tm = localtime(&xlrec->xtime);
2000-10-21 17:43:36 +02:00
sprintf(buf + strlen(buf), "commit: %04u-%02u-%02u %02u:%02u:%02u",
2001-03-22 05:01:46 +01:00
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
2000-10-21 17:43:36 +02:00
}
else if (info == XLOG_XACT_ABORT)
{
2001-03-22 05:01:46 +01:00
xl_xact_abort *xlrec = (xl_xact_abort *) rec;
struct tm *tm = localtime(&xlrec->xtime);
2000-10-21 17:43:36 +02:00
sprintf(buf + strlen(buf), "abort: %04u-%02u-%02u %02u:%02u:%02u",
2001-03-22 05:01:46 +01:00
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
2000-10-21 17:43:36 +02:00
}
else
strcat(buf, "UNKNOWN");
}
void
2001-03-22 05:01:46 +01:00
XactPushRollback(void (*func) (void *), void *data)
{
2000-10-29 19:33:41 +01:00
#ifdef XLOG_II
if (_RollbackFunc != NULL)
elog(PANIC, "XactPushRollback: already installed");
2000-10-29 19:33:41 +01:00
#endif
_RollbackFunc = func;
_RollbackData = data;
}
void
XactPopRollback(void)
{
_RollbackFunc = NULL;
}