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

3473 lines
88 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* xact.c
* top level transaction system support routines
*
2003-08-04 04:40:20 +02:00
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.172 2004/07/27 05:10:49 tgl Exp $
*
* NOTES
* Transaction aborts can now occur two ways:
*
2002-11-13 04:12:05 +01:00
* 1) system dies from some internal cause (syntax error, 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
2002-11-13 04:12:05 +01:00
* to ignore these commands until we see a COMMIT transaction or
* ROLLBACK.
*
* 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
* TopTransactionContext until this point.
*
* NOTES
* 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
2002-11-13 04:12:05 +01:00
* based on which 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:
*
* BeginTransactionBlock
* CommitTransactionBlock
* AbortTransactionBlock
*
* These are invoked only in response to a user "BEGIN WORK", "COMMIT",
2003-08-04 02:43:34 +02:00
* or "ROLLBACK" 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
2002-11-13 04:12:05 +01:00
* 2) select * from foo
* 3) insert into foo (bar = baz)
* 4) commit
*
* in the main processing loop, this results in the following
* transaction sequence:
*
* / StartTransactionCommand();
* 1) / ProcessUtility(); << begin
* \ BeginTransactionBlock();
* \ CommitTransactionCommand();
*
* / StartTransactionCommand();
2002-11-13 04:12:05 +01:00
* 2) < ProcessQuery(); << select * from foo
* \ CommitTransactionCommand();
*
* / StartTransactionCommand();
2002-11-13 04:12:05 +01:00
* 3) < ProcessQuery(); << insert into foo (bar = baz)
* \ CommitTransactionCommand();
*
* / StartTransactionCommand();
2002-11-13 04:12:05 +01:00
* 4) / ProcessUtility(); << commit
* \ 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 BeginTransactionBlock() and EndTransactionBlock() and
* outside these calls they need to do normal start/commit
* processing.
*
2002-11-13 04:12:05 +01:00
* Furthermore, suppose the "select * from foo" caused an abort
* condition. We would then want to abort the transaction and
2002-11-13 04:12:05 +01:00
* ignore all subsequent commands up to the "commit".
* -cim 3/23/90
*
*-------------------------------------------------------------------------
*/
1996-10-21 09:15:18 +02:00
#include "postgres.h"
1996-10-21 09:15:18 +02:00
#include <time.h>
2001-11-01 07:17:01 +01:00
#include <unistd.h>
#include "access/subtrans.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/tablecmds.h"
#include "commands/trigger.h"
#include "commands/user.h"
#include "executor/spi.h"
#include "libpq/be-fsstubs.h"
#include "miscadmin.h"
#include "storage/fd.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/resowner.h"
#include "pgstat.h"
/*
* transaction states - transaction state from server perspective
*/
typedef enum TransState
{
TRANS_DEFAULT,
TRANS_START,
TRANS_INPROGRESS,
TRANS_COMMIT,
TRANS_ABORT
} TransState;
/*
* transaction block states - transaction state of client queries
*/
typedef enum TBlockState
{
/* not-in-transaction-block states */
TBLOCK_DEFAULT,
TBLOCK_STARTED,
/* transaction block states */
TBLOCK_BEGIN,
TBLOCK_INPROGRESS,
TBLOCK_END,
TBLOCK_ABORT,
TBLOCK_ENDABORT,
/* subtransaction states */
TBLOCK_SUBBEGIN,
TBLOCK_SUBINPROGRESS,
TBLOCK_SUBEND,
TBLOCK_SUBABORT,
TBLOCK_SUBABORT_PENDING,
TBLOCK_SUBENDABORT_ALL,
TBLOCK_SUBENDABORT_RELEASE,
TBLOCK_SUBENDABORT
} TBlockState;
/*
* transaction state structure
*/
typedef struct TransactionStateData
{
TransactionId transactionIdData; /* my XID */
char *name; /* savepoint name, if any */
int savepointLevel; /* savepoint level */
CommandId commandId; /* current CID */
TransState state; /* low-level state */
TBlockState blockState; /* high-level state */
int nestingLevel; /* nest depth */
MemoryContext curTransactionContext; /* my xact-lifetime context */
ResourceOwner curTransactionOwner; /* my query resources */
List *childXids; /* subcommitted child XIDs */
AclId currentUser; /* subxact start current_user */
struct TransactionStateData *parent; /* back link to parent */
} TransactionStateData;
typedef TransactionStateData *TransactionState;
static void AbortTransaction(void);
static void AtAbort_Memory(void);
static void AtCleanup_Memory(void);
static void AtCommit_LocalCache(void);
static void AtCommit_Memory(void);
static void AtStart_Cache(void);
static void AtStart_Memory(void);
static void AtStart_ResourceOwner(void);
static void CallEOXactCallbacks(bool isCommit);
static void CleanupTransaction(void);
static void CommitTransaction(void);
static void RecordTransactionAbort(void);
static void StartTransaction(void);
static void RecordSubTransactionCommit(void);
static void StartSubTransaction(void);
static void CommitSubTransaction(void);
static void AbortSubTransaction(void);
static void CleanupSubTransaction(void);
static void StartAbortedSubTransaction(void);
static void PushTransaction(void);
static void PopTransaction(void);
static void CommitTransactionToLevel(int level);
static char *CleanupAbortedSubTransactions(bool returnName);
static void AtSubAbort_Memory(void);
static void AtSubCleanup_Memory(void);
static void AtSubCommit_Memory(void);
static void AtSubStart_Memory(void);
static void AtSubStart_ResourceOwner(void);
static void ShowTransactionState(const char *str);
static void ShowTransactionStateRec(TransactionState state);
static const char *BlockStateAsString(TBlockState blockState);
static const char *TransStateAsString(TransState state);
2002-11-13 04:12:05 +01:00
/*
* CurrentTransactionState always points to the current transaction state
* block. It will point to TopTransactionStateData when not in a
* transaction at all, or when in a top-level transaction.
*/
static TransactionStateData TopTransactionStateData = {
0, /* transaction id */
NULL, /* savepoint name */
0, /* savepoint level */
FirstCommandId, /* command id */
TRANS_DEFAULT, /* transaction state */
TBLOCK_DEFAULT, /* transaction block state from the client
2003-08-04 02:43:34 +02:00
* perspective */
0, /* nesting level */
NULL, /* cur transaction context */
NULL, /* cur transaction resource owner */
NIL, /* subcommitted child Xids */
0, /* entry-time current userid */
NULL /* link to parent state block */
};
static TransactionState CurrentTransactionState = &TopTransactionStateData;
/*
* These vars hold the value of now(), ie, the transaction start time.
* This does not change as we enter and exit subtransactions, so we don't
* keep it inside the TransactionState stack.
*/
static AbsoluteTime xactStartTime; /* integer part */
static int xactStartTimeUsec; /* microsecond part */
/*
2002-11-13 04:12:05 +01:00
* User-tweakable parameters
*/
1999-05-25 18:15:34 +02:00
int DefaultXactIsoLevel = XACT_READ_COMMITTED;
int XactIsoLevel;
bool DefaultXactReadOnly = false;
bool XactReadOnly;
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 */
/*
* List of add-on end-of-xact callbacks
*/
typedef struct EOXactCallbackItem
{
struct EOXactCallbackItem *next;
EOXactCallback callback;
void *arg;
} EOXactCallbackItem;
static EOXactCallbackItem *EOXact_callbacks = NULL;
2001-03-22 05:01:46 +01:00
static void (*_RollbackFunc) (void *) = NULL;
static void *_RollbackData = NULL;
/* ----------------------------------------------------------------
2002-11-13 04:12:05 +01:00
* transaction state accessors
* ----------------------------------------------------------------
*/
/*
2002-11-13 04:12:05 +01:00
* IsTransactionState
*
2002-11-13 04:12:05 +01:00
* 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;
}
/*
2002-11-13 04:12:05 +01:00
* IsAbortedTransactionBlockState
*
2002-11-13 04:12:05 +01:00
* 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 ||
s->blockState == TBLOCK_SUBABORT)
return true;
return false;
}
/*
* GetTopTransactionId
*
* Get the ID of the main transaction, even if we are currently inside
* a subtransaction.
*/
TransactionId
GetTopTransactionId(void)
{
return TopTransactionStateData.transactionIdData;
}
/*
2002-11-13 04:12:05 +01:00
* GetCurrentTransactionId
*/
TransactionId
GetCurrentTransactionId(void)
{
TransactionState s = CurrentTransactionState;
return s->transactionIdData;
}
/*
2002-11-13 04:12:05 +01:00
* GetCurrentCommandId
*/
CommandId
GetCurrentCommandId(void)
{
TransactionState s = CurrentTransactionState;
return s->commandId;
}
/*
2002-11-13 04:12:05 +01:00
* GetCurrentTransactionStartTime
*/
AbsoluteTime
GetCurrentTransactionStartTime(void)
{
return xactStartTime;
}
/*
2002-11-13 04:12:05 +01:00
* GetCurrentTransactionStartTimeUsec
*/
AbsoluteTime
GetCurrentTransactionStartTimeUsec(int *msec)
{
*msec = xactStartTimeUsec;
return xactStartTime;
}
/*
* GetCurrentTransactionNestLevel
*
* Note: this will return zero when not inside any transaction, one when
* inside a top-level transaction, etc.
*/
int
GetCurrentTransactionNestLevel(void)
{
TransactionState s = CurrentTransactionState;
return s->nestingLevel;
}
/*
2002-11-13 04:12:05 +01:00
* TransactionIdIsCurrentTransactionId
*
2002-11-13 04:12:05 +01:00
* During bootstrap, we cheat and say "it's not my transaction ID" even though
2003-08-04 02:43:34 +02:00
* it is. Along with transam.c's cheat to say that the bootstrap XID is
2002-11-13 04:12:05 +01:00
* 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;
}
/*
* We will return true for the Xid of the current subtransaction,
* any of its subcommitted children, any of its parents, or any of
* their previously subcommitted children.
*/
while (s != NULL)
{
ListCell *cell;
if (TransactionIdEquals(xid, s->transactionIdData))
return true;
foreach(cell, s->childXids)
{
if (TransactionIdEquals(xid, lfirst_int(cell)))
return true;
}
s = s->parent;
}
return false;
}
/*
2002-11-13 04:12:05 +01:00
* 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 */
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("cannot have more than 2^32-1 commands in a 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();
AtStart_Cache();
}
/* ----------------------------------------------------------------
* StartTransaction stuff
* ----------------------------------------------------------------
*/
/*
2002-11-13 04:12:05 +01:00
* AtStart_Cache
*/
static void
AtStart_Cache(void)
{
AcceptInvalidationMessages();
}
/*
2002-11-13 04:12:05 +01:00
* AtStart_Memory
*/
static void
AtStart_Memory(void)
{
TransactionState s = CurrentTransactionState;
/*
* We shouldn't have a transaction context already.
*/
Assert(TopTransactionContext == NULL);
/*
* Create a toplevel context for the transaction.
*/
TopTransactionContext =
AllocSetContextCreate(TopMemoryContext,
"TopTransactionContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/*
* In a top-level transaction, CurTransactionContext is the same as
* TopTransactionContext.
*/
CurTransactionContext = TopTransactionContext;
s->curTransactionContext = CurTransactionContext;
/* Make the CurTransactionContext active. */
MemoryContextSwitchTo(CurTransactionContext);
}
/*
* AtStart_ResourceOwner
*/
static void
AtStart_ResourceOwner(void)
{
TransactionState s = CurrentTransactionState;
/*
* We shouldn't have a transaction resource owner already.
*/
Assert(TopTransactionResourceOwner == NULL);
/*
* Create a toplevel resource owner for the transaction.
*/
s->curTransactionOwner = ResourceOwnerCreate(NULL, "TopTransaction");
TopTransactionResourceOwner = s->curTransactionOwner;
CurTransactionResourceOwner = s->curTransactionOwner;
CurrentResourceOwner = s->curTransactionOwner;
}
/* ----------------------------------------------------------------
* StartSubTransaction stuff
* ----------------------------------------------------------------
*/
/*
* AtSubStart_Memory
*/
static void
AtSubStart_Memory(void)
{
TransactionState s = CurrentTransactionState;
Assert(CurTransactionContext != NULL);
/*
* Create a CurTransactionContext, which will be used to hold data that
* survives subtransaction commit but disappears on subtransaction abort.
* We make it a child of the immediate parent's CurTransactionContext.
*/
CurTransactionContext = AllocSetContextCreate(CurTransactionContext,
"CurTransactionContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
s->curTransactionContext = CurTransactionContext;
/* Make the CurTransactionContext active. */
MemoryContextSwitchTo(CurTransactionContext);
}
/*
* AtSubStart_ResourceOwner
*/
static void
AtSubStart_ResourceOwner(void)
{
TransactionState s = CurrentTransactionState;
Assert(s->parent != NULL);
/*
* Create a resource owner for the subtransaction. We make it a
* child of the immediate parent's resource owner.
*/
s->curTransactionOwner =
ResourceOwnerCreate(s->parent->curTransactionOwner,
"SubTransaction");
CurTransactionResourceOwner = s->curTransactionOwner;
CurrentResourceOwner = s->curTransactionOwner;
}
/* ----------------------------------------------------------------
* CommitTransaction stuff
* ----------------------------------------------------------------
*/
/*
2002-11-13 04:12:05 +01:00
* RecordTransactionCommit
*/
2000-10-28 18:21:00 +02:00
void
RecordTransactionCommit(void)
{
int nrels;
RelFileNode *rptr;
int nchildren;
TransactionId *children;
/* Get data needed for commit record */
nrels = smgrGetPendingDeletes(true, &rptr);
nchildren = xactGetCommittedChildren(&children);
/*
* If we made neither any XLOG entries nor any temp-rel updates,
* and have no files to be deleted, we can omit recording the transaction
* commit at all. (This test includes the effects of subtransactions,
* so the presence of committed subxacts need not alone force a write.)
*/
if (MyXactMadeXLogEntry || MyXactMadeTempRelUpdate || nrels > 0)
2000-10-24 11:56:23 +02:00
{
TransactionId xid = GetCurrentTransactionId();
bool madeTCentries;
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
/*
* We only need to log the commit in XLOG if the transaction made
* any transaction-controlled XLOG entries or will delete files.
* (If it made no transaction-controlled XLOG entries, its XID
* appears nowhere in permanent storage, so no one else will ever care
* if it committed.)
*/
madeTCentries = (MyLastRecPtr.xrecoff != 0);
if (madeTCentries || nrels > 0)
{
XLogRecData rdata[3];
int lastrdata = 0;
xl_xact_commit xlrec;
xlrec.xtime = time(NULL);
xlrec.nrels = nrels;
xlrec.nsubxacts = nchildren;
rdata[0].buffer = InvalidBuffer;
rdata[0].data = (char *) (&xlrec);
rdata[0].len = MinSizeOfXactCommit;
/* dump rels to delete */
if (nrels > 0)
{
rdata[0].next = &(rdata[1]);
rdata[1].buffer = InvalidBuffer;
rdata[1].data = (char *) rptr;
rdata[1].len = nrels * sizeof(RelFileNode);
lastrdata = 1;
}
/* dump committed child Xids */
if (nchildren > 0)
{
rdata[lastrdata].next = &(rdata[2]);
rdata[2].buffer = InvalidBuffer;
rdata[2].data = (char *) children;
rdata[2].len = nchildren * sizeof(TransactionId);
lastrdata = 2;
}
rdata[lastrdata].next = NULL;
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.
*
* Note: if we generated a commit record above, MyXactMadeXLogEntry
* will certainly be set now.
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)
pg_usleep(CommitDelay);
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
2003-08-04 02:43:34 +02:00
* 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 (madeTCentries || MyXactMadeTempRelUpdate)
{
TransactionIdCommit(xid);
/* to avoid race conditions, the parent must commit first */
TransactionIdCommitTree(nchildren, children);
}
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;
/* And clean up local data */
if (rptr)
pfree(rptr);
if (children)
pfree(children);
}
/*
2002-11-13 04:12:05 +01:00
* AtCommit_LocalCache
*/
static void
AtCommit_LocalCache(void)
{
/*
* Make catalog changes visible to me for the next command.
*/
CommandEndInvalidationMessages();
}
/*
2002-11-13 04:12:05 +01:00
* 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;
CurTransactionContext = NULL;
CurrentTransactionState->curTransactionContext = NULL;
}
/* ----------------------------------------------------------------
* CommitSubTransaction stuff
* ----------------------------------------------------------------
*/
/*
* AtSubCommit_Memory
*
* We do not throw away the child's CurTransactionContext, since the data
* it contains will be needed at upper commit.
*/
static void
AtSubCommit_Memory(void)
{
TransactionState s = CurrentTransactionState;
Assert(s->parent != NULL);
/* Return to parent transaction level's memory context. */
CurTransactionContext = s->parent->curTransactionContext;
MemoryContextSwitchTo(CurTransactionContext);
}
/*
* AtSubCommit_childXids
*
* Pass my own XID and my child XIDs up to my parent as committed children.
*/
static void
AtSubCommit_childXids(void)
{
TransactionState s = CurrentTransactionState;
MemoryContext old_cxt;
Assert(s->parent != NULL);
old_cxt = MemoryContextSwitchTo(s->parent->curTransactionContext);
s->parent->childXids = list_concat(s->parent->childXids, s->childXids);
s->childXids = NIL; /* ensure list not doubly referenced */
s->parent->childXids = lappend_int(s->parent->childXids,
s->transactionIdData);
MemoryContextSwitchTo(old_cxt);
}
/*
* RecordSubTransactionCommit
*/
static void
RecordSubTransactionCommit(void)
{
/*
* We do not log the subcommit in XLOG; it doesn't matter until
* the top-level transaction commits.
*
* We must mark the subtransaction subcommitted 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. (The test here actually covers the
* entire transaction tree so far, so it may mark subtransactions that
* don't really need it, but it's probably not worth being tenser.
* Note that if a prior subtransaction dirtied these variables, then
* RecordTransactionCommit will have to do the full pushup anyway...)
*/
if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate)
{
TransactionId xid = GetCurrentTransactionId();
/* XXX does this really need to be a critical section? */
START_CRIT_SECTION();
/* Record subtransaction subcommit */
TransactionIdSubCommit(xid);
END_CRIT_SECTION();
}
}
/* ----------------------------------------------------------------
* AbortTransaction stuff
* ----------------------------------------------------------------
*/
/*
2002-11-13 04:12:05 +01:00
* RecordTransactionAbort
*/
static void
RecordTransactionAbort(void)
{
int nrels;
RelFileNode *rptr;
int nchildren;
TransactionId *children;
/* Get data needed for abort record */
nrels = smgrGetPendingDeletes(false, &rptr);
nchildren = xactGetCommittedChildren(&children);
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
* temp-rel updates, and are not going to delete any files, we can omit
* recording the transaction abort at all. No one will ever care that
* it aborted. (These tests cover our whole transaction tree.)
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 || nrels > 0)
{
TransactionId xid = GetCurrentTransactionId();
/*
* Catch the scenario where we aborted partway through
* RecordTransactionCommit ...
*/
if (TransactionIdDidCommit(xid))
elog(PANIC, "cannot abort transaction %u, it was 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 or will delete files.
* (If it made no transaction-controlled XLOG entries, its XID
* appears nowhere in permanent storage, so no one else will ever care
* if it committed.)
*
* We do not flush XLOG to disk unless deleting files, since the
* default assumption after a crash would be that we aborted, anyway.
*/
if (MyLastRecPtr.xrecoff != 0 || nrels > 0)
{
XLogRecData rdata[3];
int lastrdata = 0;
xl_xact_abort xlrec;
XLogRecPtr recptr;
xlrec.xtime = time(NULL);
xlrec.nrels = nrels;
xlrec.nsubxacts = nchildren;
rdata[0].buffer = InvalidBuffer;
rdata[0].data = (char *) (&xlrec);
rdata[0].len = MinSizeOfXactAbort;
/* dump rels to delete */
if (nrels > 0)
{
rdata[0].next = &(rdata[1]);
rdata[1].buffer = InvalidBuffer;
rdata[1].data = (char *) rptr;
rdata[1].len = nrels * sizeof(RelFileNode);
lastrdata = 1;
}
/* dump committed child Xids */
if (nchildren > 0)
{
rdata[lastrdata].next = &(rdata[2]);
rdata[2].buffer = InvalidBuffer;
rdata[2].data = (char *) children;
rdata[2].len = nchildren * sizeof(TransactionId);
lastrdata = 2;
}
rdata[lastrdata].next = NULL;
recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, rdata);
/* Must flush if we are deleting files... */
if (nrels > 0)
XLogFlush(recptr);
}
/*
* Mark the transaction aborted in clog. This is not absolutely
* necessary but we may as well do it while we are here.
*
* The ordering here isn't critical but it seems best to mark the
* parent last. That reduces the chance that concurrent
* TransactionIdDidAbort calls will decide they need to do redundant
* work.
*/
TransactionIdAbortTree(nchildren, children);
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;
/* And clean up local data */
if (rptr)
pfree(rptr);
if (children)
pfree(children);
}
/*
2002-11-13 04:12:05 +01:00
* AtAbort_Memory
*/
static void
AtAbort_Memory(void)
{
/*
* Make sure we are in a valid context (not a child of
2003-08-04 02:43:34 +02:00
* TopTransactionContext...). 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 (TopTransactionContext != NULL)
{
MemoryContextSwitchTo(TopTransactionContext);
/*
* We do not want to destroy the transaction's global state yet,
* so we can't free any memory here.
*/
}
else
MemoryContextSwitchTo(TopMemoryContext);
}
/*
* AtSubAbort_Memory
*/
static void
AtSubAbort_Memory(void)
{
Assert(TopTransactionContext != NULL);
MemoryContextSwitchTo(TopTransactionContext);
}
/*
* RecordSubTransactionAbort
*/
static void
RecordSubTransactionAbort(void)
{
int nrels;
RelFileNode *rptr;
int nchildren;
TransactionId *children;
/* Get data needed for abort record */
nrels = smgrGetPendingDeletes(false, &rptr);
nchildren = xactGetCommittedChildren(&children);
/*
* If we made neither any transaction-controlled XLOG entries nor any
* temp-rel updates, and are not going to delete any files, we can omit
* recording the transaction abort at all. No one will ever care that
* it aborted. (These tests cover our whole transaction tree, and
* therefore may mark subxacts that don't really need it, but it's
* probably not worth being tenser.)
*
* In this case we needn't worry about marking subcommitted children as
* aborted, because they didn't mark themselves as subcommitted in the
* first place; see the optimization in RecordSubTransactionCommit.
*/
if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate || nrels > 0)
{
TransactionId xid = GetCurrentTransactionId();
START_CRIT_SECTION();
/*
* We only need to log the abort in XLOG if the transaction made
* any transaction-controlled XLOG entries or will delete files.
*/
if (MyLastRecPtr.xrecoff != 0 || nrels > 0)
{
XLogRecData rdata[3];
int lastrdata = 0;
xl_xact_abort xlrec;
XLogRecPtr recptr;
xlrec.xtime = time(NULL);
xlrec.nrels = nrels;
xlrec.nsubxacts = nchildren;
rdata[0].buffer = InvalidBuffer;
rdata[0].data = (char *) (&xlrec);
rdata[0].len = MinSizeOfXactAbort;
/* dump rels to delete */
if (nrels > 0)
{
rdata[0].next = &(rdata[1]);
rdata[1].buffer = InvalidBuffer;
rdata[1].data = (char *) rptr;
rdata[1].len = nrels * sizeof(RelFileNode);
lastrdata = 1;
}
/* dump committed child Xids */
if (nchildren > 0)
{
rdata[lastrdata].next = &(rdata[2]);
rdata[2].buffer = InvalidBuffer;
rdata[2].data = (char *) children;
rdata[2].len = nchildren * sizeof(TransactionId);
lastrdata = 2;
}
rdata[lastrdata].next = NULL;
recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, rdata);
/* Must flush if we are deleting files... */
if (nrels > 0)
XLogFlush(recptr);
}
/*
* Mark the transaction aborted in clog. This is not absolutely
* necessary but we may as well do it while we are here.
*/
TransactionIdAbortTree(nchildren, children);
TransactionIdAbort(xid);
END_CRIT_SECTION();
}
/* And clean up local data */
if (rptr)
pfree(rptr);
if (children)
pfree(children);
}
/* ----------------------------------------------------------------
* CleanupTransaction stuff
* ----------------------------------------------------------------
*/
/*
2002-11-13 04:12:05 +01:00
* 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);
Assert(CurrentTransactionState->parent == NULL);
/*
* Release all transaction-local memory.
*/
if (TopTransactionContext != NULL)
MemoryContextDelete(TopTransactionContext);
TopTransactionContext = NULL;
CurTransactionContext = NULL;
CurrentTransactionState->curTransactionContext = NULL;
}
/* ----------------------------------------------------------------
* CleanupSubTransaction stuff
* ----------------------------------------------------------------
*/
/*
* AtSubCleanup_Memory
*/
static void
AtSubCleanup_Memory(void)
{
TransactionState s = CurrentTransactionState;
Assert(s->parent != NULL);
/* Make sure we're not in an about-to-be-deleted context */
MemoryContextSwitchTo(s->parent->curTransactionContext);
CurTransactionContext = s->parent->curTransactionContext;
/*
* Delete the subxact local memory contexts. Its CurTransactionContext
* can go too (note this also kills CurTransactionContexts from any
* children of the subxact).
*/
MemoryContextDelete(s->curTransactionContext);
}
/* ----------------------------------------------------------------
* interface routines
* ----------------------------------------------------------------
*/
/*
2002-11-13 04:12:05 +01:00
* StartTransaction
*/
static void
StartTransaction(void)
{
TransactionState s = CurrentTransactionState;
/*
* check the current transaction state
*/
if (s->state != TRANS_DEFAULT)
elog(WARNING, "StartTransaction and not in default state");
/*
* set the current transaction state information appropriately during
* start processing
*/
s->state = TRANS_START;
/*
* Make sure we've freed any old snapshot, and reset xact state variables
*/
FreeXactSnapshot();
XactIsoLevel = DefaultXactIsoLevel;
XactReadOnly = DefaultXactReadOnly;
/*
* must initialize resource-management stuff first
*/
AtStart_Memory();
AtStart_ResourceOwner();
/*
* generate a new transaction id
*/
s->transactionIdData = GetNewTransactionId(false);
XactLockTableInsert(s->transactionIdData);
/*
* set now()
*/
xactStartTime = GetCurrentAbsoluteTimeUsec(&(xactStartTimeUsec));
/*
* initialize current transaction state fields
*/
s->commandId = FirstCommandId;
s->nestingLevel = 1;
s->childXids = NIL;
/*
* You might expect to see "s->currentUser = GetUserId();" here, but
* you won't because it doesn't work during startup; the userid isn't
* set yet during a backend's first transaction start. We only use
* the currentUser field in sub-transaction state structs.
*/
/*
* initialize other subsystems for new transaction
*/
AtStart_Inval();
AtStart_Cache();
DeferredTriggerBeginXact();
/*
* done with start processing, set current transaction state to "in
* progress"
*/
s->state = TRANS_INPROGRESS;
ShowTransactionState("StartTransaction");
}
/*
2002-11-13 04:12:05 +01:00
* CommitTransaction
*/
static void
CommitTransaction(void)
{
TransactionState s = CurrentTransactionState;
ShowTransactionState("CommitTransaction");
/*
* check the current transaction state
*/
if (s->state != TRANS_INPROGRESS)
elog(WARNING, "CommitTransaction and not in in-progress state");
Assert(s->parent == NULL);
/*
* 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();
/*
2003-08-04 02:43:34 +02:00
* Similarly, let ON COMMIT management do its thing before we start to
* commit.
*/
PreCommit_on_commit_actions();
/* 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...)
*/
AtCommit_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 != 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.
*
* The ordering of operations is not entirely random. The idea is:
2003-08-04 02:43:34 +02:00
* release resources visible to other backends (eg, files, buffer
* pins); then release locks; then release backend-local resources. We
* want to release locks at the point where any backend waiting for us
* will see our transaction as being fully cleaned up.
*
* Resources that can be associated with individual queries are
* handled by the ResourceOwner mechanism. The other calls here
* are for backend-wide state.
*/
smgrDoPendingDeletes(true);
/* smgrcommit already done */
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, true);
/*
* Make catalog changes visible to all backends. This has to happen
* after relcache references are dropped (see comments for
* AtEOXact_RelationCache), but before locks are released (if anyone
* is waiting for lock on a relation we've modified, we want them to
* know about the catalog change before they start using the relation).
*/
AtEOXact_Inval(true);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
true, true);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, true);
CallEOXactCallbacks(true);
AtEOXact_GUC(true, false);
AtEOXact_SPI(true);
AtEOXact_on_commit_actions(true, s->transactionIdData);
AtEOXact_Namespace(true);
AtEOXact_Files();
pgstat_count_xact_commit();
CurrentResourceOwner = NULL;
ResourceOwnerDelete(TopTransactionResourceOwner);
s->curTransactionOwner = NULL;
CurTransactionResourceOwner = NULL;
TopTransactionResourceOwner = NULL;
AtCommit_Memory();
s->nestingLevel = 0;
s->childXids = NIL;
/*
* done with commit processing, set current transaction state back to
* default
*/
s->state = TRANS_DEFAULT;
RESUME_INTERRUPTS();
}
/*
2002-11-13 04:12:05 +01:00
* 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");
Assert(s->parent == NULL);
/*
* set the current transaction state information appropriately during
* the abort processing
*/
s->state = TRANS_ABORT;
/* Make sure we are in a valid memory context */
AtAbort_Memory();
/*
* Reset user id which might have been changed transiently. We cannot
* use s->currentUser, but must get the session userid from miscinit.c.
*
* (Note: it is not necessary to restore session authorization here
* because that can only be changed via GUC, and GUC will take care of
* rolling it back if need be. However, an error within a SECURITY
* DEFINER function could send control here with the wrong current
* userid.)
*/
SetUserId(GetSessionUserId());
/*
* do abort processing
*/
DeferredTriggerAbortXact();
AtAbort_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 != NULL)
{
/* Lock SInvalLock because that's what GetSnapshotData uses. */
LWLockAcquire(SInvalLock, LW_EXCLUSIVE);
MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
LWLockRelease(SInvalLock);
}
/*
2003-08-04 02:43:34 +02:00
* Post-abort cleanup. See notes in CommitTransaction() concerning
* ordering.
*/
smgrDoPendingDeletes(false);
smgrabort();
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
false, true);
AtEOXact_Inval(false);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
false, true);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
false, true);
CallEOXactCallbacks(false);
AtEOXact_GUC(false, false);
AtEOXact_SPI(false);
AtEOXact_on_commit_actions(false, s->transactionIdData);
AtEOXact_Namespace(false);
AtEOXact_Files();
SetReindexProcessing(InvalidOid, InvalidOid);
pgstat_count_xact_rollback();
/*
* State remains TRANS_ABORT until CleanupTransaction().
*/
RESUME_INTERRUPTS();
}
/*
2002-11-13 04:12:05 +01:00
* 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_Portals(); /* now safe to release portal memory */
CurrentResourceOwner = NULL; /* and resource owner */
ResourceOwnerDelete(TopTransactionResourceOwner);
s->curTransactionOwner = NULL;
CurTransactionResourceOwner = NULL;
TopTransactionResourceOwner = NULL;
AtCleanup_Memory(); /* and transaction memory */
s->nestingLevel = 0;
s->childXids = NIL;
/*
* done with abort processing, set current transaction state back to
* default
*/
s->state = TRANS_DEFAULT;
}
/*
2002-11-13 04:12:05 +01:00
* 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();
s->blockState = TBLOCK_STARTED;
break;
/*
* This is the case when we 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:
case TBLOCK_SUBINPROGRESS:
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:
case TBLOCK_SUBABORT:
break;
/* These cases are invalid. */
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_END:
case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_ENDABORT:
elog(FATAL, "StartTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
/*
* We must switch to CurTransactionContext before returning. This is
2003-08-04 02:43:34 +02:00
* already done if we called StartTransaction, otherwise not.
*/
Assert(CurTransactionContext != NULL);
MemoryContextSwitchTo(CurTransactionContext);
}
/*
2002-11-13 04:12:05 +01:00
* CommitTransactionCommand
*/
void
CommitTransactionCommand(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
/*
* This shouldn't happen, because it means the previous
* StartTransactionCommand didn't set the STARTED state
* appropriately, or we didn't manage previous pending
* abort states.
*/
case TBLOCK_DEFAULT:
case TBLOCK_SUBABORT_PENDING:
elog(FATAL, "CommitTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState));
break;
/*
* If we aren't in a transaction block, just do our usual
* transaction commit.
*/
case TBLOCK_STARTED:
CommitTransaction();
s->blockState = TBLOCK_DEFAULT;
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.
*/
case TBLOCK_INPROGRESS:
CommandCounterIncrement();
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:
/* commit all open subtransactions */
if (s->nestingLevel > 1)
CommitTransactionToLevel(2);
s = CurrentTransactionState;
Assert(s->parent == NULL);
/* and now the outer transaction */
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;
/*
* Ditto, but in a subtransaction. AbortOutOfAnyTransaction
* will do the dirty work.
*/
case TBLOCK_SUBENDABORT_ALL:
AbortOutOfAnyTransaction();
s = CurrentTransactionState; /* changed by AbortOutOfAnyTransaction */
/* AbortOutOfAnyTransaction sets the blockState */
break;
/*
* We were just issued a SAVEPOINT inside a transaction block.
* Start a subtransaction. (BeginTransactionBlock already
* did PushTransaction, so as to have someplace to put the
* SUBBEGIN state.)
*/
case TBLOCK_SUBBEGIN:
StartSubTransaction();
s->blockState = TBLOCK_SUBINPROGRESS;
break;
/*
* Inside a subtransaction, increment the command counter.
*/
case TBLOCK_SUBINPROGRESS:
CommandCounterIncrement();
break;
/*
* We were issued a RELEASE command, so we end the current
* subtransaction and return to the parent transaction.
*/
case TBLOCK_SUBEND:
CommitSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
break;
/*
* If we are in an aborted subtransaction, do nothing.
*/
case TBLOCK_SUBABORT:
break;
/*
* The current subtransaction is ending. Do the equivalent
* of a ROLLBACK TO followed by a RELEASE command.
*/
case TBLOCK_SUBENDABORT_RELEASE:
CleanupAbortedSubTransactions(false);
break;
/*
* The current subtransaction is ending due to a ROLLBACK
* TO command, so close all savepoints up to the target
* level. When finished, recreate the savepoint.
*/
case TBLOCK_SUBENDABORT:
{
char *name = CleanupAbortedSubTransactions(true);
Assert(PointerIsValid(name));
DefineSavepoint(name);
s = CurrentTransactionState; /* changed by DefineSavepoint */
pfree(name);
/* This is the same as TBLOCK_SUBBEGIN case */
AssertState(s->blockState == TBLOCK_SUBBEGIN);
StartSubTransaction();
s->blockState = TBLOCK_SUBINPROGRESS;
}
break;
}
}
/*
* CleanupAbortedSubTransactions
*
* Helper function for CommitTransactionCommand. Aborts and cleans up
* dead subtransactions after a ROLLBACK TO command. Optionally returns
* the name of the last dead subtransaction so it can be reused to redefine
* the savepoint. (Caller is responsible for pfree'ing the result.)
*/
static char *
CleanupAbortedSubTransactions(bool returnName)
{
TransactionState s = CurrentTransactionState;
char *name = NULL;
AssertState(PointerIsValid(s->parent));
Assert(s->parent->blockState == TBLOCK_SUBINPROGRESS ||
s->parent->blockState == TBLOCK_INPROGRESS ||
s->parent->blockState == TBLOCK_SUBABORT_PENDING);
/*
* Abort everything up to the target level. The current
* subtransaction only needs cleanup. If we need to save the name,
* look for the last subtransaction in TBLOCK_SUBABORT_PENDING state.
*/
if (returnName && s->parent->blockState != TBLOCK_SUBABORT_PENDING)
name = MemoryContextStrdup(TopMemoryContext, s->name);
CleanupSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
while (s->blockState == TBLOCK_SUBABORT_PENDING)
{
AbortSubTransaction();
if (returnName && s->parent->blockState != TBLOCK_SUBABORT_PENDING)
name = MemoryContextStrdup(TopMemoryContext, s->name);
CleanupSubTransaction();
PopTransaction();
s = CurrentTransactionState;
}
return name;
}
/*
* AbortCurrentTransaction
*/
void
AbortCurrentTransaction(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
/*
* we aren't in a transaction, so we do nothing.
*/
case TBLOCK_DEFAULT:
break;
/*
* if we aren't in a transaction block, we just do the basic
* abort & cleanup transaction.
*/
case TBLOCK_STARTED:
AbortTransaction();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
break;
/*
* If we are in 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:
AbortTransaction();
s->blockState = TBLOCK_ABORT;
/* CleanupTransaction happens when we exit TBLOCK_ENDABORT */
break;
/*
* This is the case when we are somewhere in a transaction block
* and we've gotten a failure, so we abort the transaction and
* set up the persistent ABORT state. We will stay in ABORT
* until we get an "END TRANSACTION".
*/
case TBLOCK_INPROGRESS:
AbortTransaction();
s->blockState = TBLOCK_ABORT;
/* CleanupTransaction happens when we exit TBLOCK_ENDABORT */
break;
/*
* Here, the system was fouled up just after the user wanted
* to end the transaction block so we abort the transaction
* and return to the default state.
*/
case TBLOCK_END:
AbortTransaction();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
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:
case TBLOCK_SUBABORT:
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;
/*
* If we are just starting a subtransaction, put it
* in aborted state.
*/
case TBLOCK_SUBBEGIN:
StartAbortedSubTransaction();
s->blockState = TBLOCK_SUBABORT;
break;
case TBLOCK_SUBINPROGRESS:
AbortSubTransaction();
s->blockState = TBLOCK_SUBABORT;
break;
/*
* If we are aborting an ending transaction,
* we have to abort the parent transaction too.
*/
case TBLOCK_SUBEND:
case TBLOCK_SUBABORT_PENDING:
AbortSubTransaction();
CleanupSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
Assert(s->blockState != TBLOCK_SUBEND &&
s->blockState != TBLOCK_SUBENDABORT);
AbortCurrentTransaction();
break;
/*
* Same as above, except the Abort() was already done.
*/
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBENDABORT_RELEASE:
CleanupSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
Assert(s->blockState != TBLOCK_SUBEND &&
s->blockState != TBLOCK_SUBENDABORT);
AbortCurrentTransaction();
break;
/*
* We are already aborting the whole transaction tree.
* Do nothing, CommitTransactionCommand will call
* AbortOutOfAnyTransaction and set things straight.
*/
case TBLOCK_SUBENDABORT_ALL:
break;
}
}
/*
2002-11-13 04:12:05 +01:00
* PreventTransactionChain
*
2002-11-13 04:12:05 +01:00
* 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.
*
2002-11-13 04:12:05 +01:00
* 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). Subtransactions are verboten too.
*
2002-11-13 04:12:05 +01:00
* 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())
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
2003-08-04 02:43:34 +02:00
/* translator: %s represents an SQL statement name */
errmsg("%s cannot run inside a transaction block",
stmtType)));
2003-08-04 02:43:34 +02:00
/*
* subtransaction?
*/
if (IsSubTransaction())
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
/* translator: %s represents an SQL statement name */
errmsg("%s cannot run inside a subtransaction",
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))
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
2003-08-04 02:43:34 +02:00
/* translator: %s represents an SQL statement name */
errmsg("%s cannot be executed from a function", stmtType)));
/* If we got past IsTransactionBlock test, should be in default state */
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
CurrentTransactionState->blockState != TBLOCK_STARTED)
elog(FATAL, "cannot prevent transaction chain");
/* all okay */
}
/*
* RequireTransactionChain
*
* This routine is to be called by statements that must run inside
* a transaction block, because they have no effects that persist past
* transaction end (and so calling them outside a transaction block
* is presumably an error). DECLARE CURSOR is an example.
*
* If we appear to be running inside a user-defined function, we do not
* issue an error, since the function could issue more commands that make
* use of the current statement's results. Likewise subtransactions.
* Thus this is an inverse for PreventTransactionChain.
*
* 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
RequireTransactionChain(void *stmtNode, const char *stmtType)
{
/*
* xact block already started?
*/
if (IsTransactionBlock())
return;
2003-08-04 02:43:34 +02:00
/*
* subtransaction?
*/
if (IsSubTransaction())
return;
/*
* 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))
return;
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
2003-08-04 02:43:34 +02:00
/* translator: %s represents an SQL statement name */
errmsg("%s may only be used in transaction blocks",
stmtType)));
}
/*
* IsInTransactionChain
*
* This routine is for statements that need to behave differently inside
* a transaction block than when running as single commands. ANALYZE is
* currently the only example.
*
* stmtNode: pointer to parameter block for statement; this is used in
* a very klugy way to determine whether we are inside a function.
*/
bool
IsInTransactionChain(void *stmtNode)
{
/*
* Return true on same conditions that would make PreventTransactionChain
* error out
*/
if (IsTransactionBlock())
return true;
if (IsSubTransaction())
return true;
if (!MemoryContextContains(QueryContext, stmtNode))
return true;
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
CurrentTransactionState->blockState != TBLOCK_STARTED)
return true;
return false;
}
/*
* Register or deregister callback functions for end-of-xact cleanup
*
* These functions are intended for use by dynamically loaded modules.
* For built-in modules we generally just hardwire the appropriate calls
* (mainly because it's easier to control the order that way, where needed).
*
* Note that the callback occurs post-commit or post-abort, so the callback
* functions can only do noncritical cleanup.
*/
void
RegisterEOXactCallback(EOXactCallback callback, void *arg)
{
EOXactCallbackItem *item;
item = (EOXactCallbackItem *)
MemoryContextAlloc(TopMemoryContext, sizeof(EOXactCallbackItem));
item->callback = callback;
item->arg = arg;
item->next = EOXact_callbacks;
EOXact_callbacks = item;
}
void
UnregisterEOXactCallback(EOXactCallback callback, void *arg)
{
EOXactCallbackItem *item;
EOXactCallbackItem *prev;
prev = NULL;
for (item = EOXact_callbacks; item; prev = item, item = item->next)
{
if (item->callback == callback && item->arg == arg)
{
if (prev)
prev->next = item->next;
else
EOXact_callbacks = item->next;
pfree(item);
break;
}
}
}
static void
CallEOXactCallbacks(bool isCommit)
{
EOXactCallbackItem *item;
for (item = EOXact_callbacks; item; item = item->next)
{
(*item->callback) (isCommit, item->arg);
}
}
/* ----------------------------------------------------------------
* transaction block support
* ----------------------------------------------------------------
*/
/*
2002-11-13 04:12:05 +01:00
* BeginTransactionBlock
* This executes a BEGIN command.
*/
void
BeginTransactionBlock(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
/*
* We are not inside a transaction block, so allow one
* to begin.
*/
case TBLOCK_STARTED:
s->blockState = TBLOCK_BEGIN;
break;
/*
* Already a transaction block in progress.
*/
case TBLOCK_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
case TBLOCK_ABORT:
case TBLOCK_SUBABORT:
ereport(WARNING,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("there is already a transaction in progress")));
break;
/* These cases are invalid. Reject them altogether. */
case TBLOCK_DEFAULT:
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_ENDABORT:
case TBLOCK_END:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_SUBEND:
elog(FATAL, "BeginTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
}
/*
2002-11-13 04:12:05 +01:00
* EndTransactionBlock
* This executes a COMMIT command.
*
* Since COMMIT may actually do a ROLLBACK, the result indicates what
* happened: TRUE for COMMIT, FALSE for ROLLBACK.
*/
bool
EndTransactionBlock(void)
{
TransactionState s = CurrentTransactionState;
bool result = false;
switch (s->blockState)
{
/*
* 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.
*/
case TBLOCK_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
s->blockState = TBLOCK_END;
result = true;
break;
/*
* We are in a transaction block which aborted. Since the
* AbortTransaction() was already done, we need only
* change to the special "END ABORT" state. The upcoming
* CommitTransactionCommand() will recognise this and then put us
* back in the default state.
*/
case TBLOCK_ABORT:
s->blockState = TBLOCK_ENDABORT;
break;
/*
* Here we are inside an aborted subtransaction. Go to the "abort
* the whole tree" state so that CommitTransactionCommand() calls
* AbortOutOfAnyTransaction.
*/
case TBLOCK_SUBABORT:
s->blockState = TBLOCK_SUBENDABORT_ALL;
break;
case TBLOCK_STARTED:
/*
* 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.
*/
ereport(WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
errmsg("there is no transaction in progress")));
AbortTransaction();
s->blockState = TBLOCK_ENDABORT;
break;
/* these cases are invalid. */
case TBLOCK_DEFAULT:
case TBLOCK_BEGIN:
case TBLOCK_ENDABORT:
case TBLOCK_END:
case TBLOCK_SUBBEGIN:
case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
return result;
}
/*
2002-11-13 04:12:05 +01:00
* UserAbortTransactionBlock
* This executes a ROLLBACK command.
*/
void
UserAbortTransactionBlock(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
/*
* We are inside a failed transaction block and we got an
* abort command from the user. Abort processing is already
* done, we just need to move to the ENDABORT state so we will
* end up in the default state after the upcoming
* CommitTransactionCommand().
*/
case TBLOCK_ABORT:
s->blockState = TBLOCK_ENDABORT;
break;
/*
* We are inside a failed subtransaction and we got an
* abort command from the user. Abort processing is already
* done, so go to the "abort all" state and
* CommitTransactionCommand will call AbortOutOfAnyTransaction
* to set things straight.
*/
case TBLOCK_SUBABORT:
s->blockState = TBLOCK_SUBENDABORT_ALL;
break;
/*
* We are inside a transaction block and we got an abort
* command from the user, so we move to the ENDABORT state and
* do abort processing so we will end up in the default state
* after the upcoming CommitTransactionCommand().
*/
case TBLOCK_INPROGRESS:
AbortTransaction();
s->blockState = TBLOCK_ENDABORT;
break;
/*
* We are inside a subtransaction. Abort the current
* subtransaction and go to the "abort all" state, so
* CommitTransactionCommand will call AbortOutOfAnyTransaction
* to set things straight.
*/
case TBLOCK_SUBINPROGRESS:
AbortSubTransaction();
s->blockState = TBLOCK_SUBENDABORT_ALL;
break;
/*
* 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.
*/
case TBLOCK_STARTED:
ereport(WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
errmsg("there is no transaction in progress")));
AbortTransaction();
s->blockState = TBLOCK_ENDABORT;
break;
/* These cases are invalid. */
case TBLOCK_DEFAULT:
case TBLOCK_BEGIN:
case TBLOCK_END:
case TBLOCK_ENDABORT:
case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_SUBBEGIN:
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
}
/*
* DefineSavepoint
* This executes a SAVEPOINT command.
*/
void
DefineSavepoint(char *name)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
case TBLOCK_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
/* Normal subtransaction start */
PushTransaction();
s = CurrentTransactionState; /* changed by push */
/*
* Note that we are allocating the savepoint name in the
* parent transaction's CurTransactionContext, since we
* don't yet have a transaction context for the new guy.
*/
s->name = MemoryContextStrdup(CurTransactionContext, name);
s->blockState = TBLOCK_SUBBEGIN;
break;
/* These cases are invalid. Reject them altogether. */
case TBLOCK_DEFAULT:
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_ABORT:
case TBLOCK_SUBABORT:
case TBLOCK_ENDABORT:
case TBLOCK_END:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_SUBEND:
elog(FATAL, "BeginTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
}
/*
* ReleaseSavepoint
* This executes a RELEASE command.
*/
void
ReleaseSavepoint(List *options)
{
TransactionState s = CurrentTransactionState;
TransactionState target = s;
char *name = NULL;
ListCell *cell;
/*
* Check valid block state transaction status.
*/
switch (s->blockState)
{
case TBLOCK_INPROGRESS:
case TBLOCK_ABORT:
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("no such savepoint")));
break;
/*
* We are in a non-aborted subtransaction. This is
* the only valid case.
*/
case TBLOCK_SUBINPROGRESS:
break;
/* these cases are invalid. */
case TBLOCK_DEFAULT:
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
case TBLOCK_ENDABORT:
case TBLOCK_END:
case TBLOCK_SUBABORT:
case TBLOCK_SUBBEGIN:
case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
elog(FATAL, "ReleaseSavepoint: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
foreach (cell, options)
{
DefElem *elem = lfirst(cell);
if (strcmp(elem->defname, "savepoint_name") == 0)
name = strVal(elem->arg);
}
Assert(PointerIsValid(name));
while (target != NULL)
{
if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
break;
target = target->parent;
}
if (!PointerIsValid(target))
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("no such savepoint")));
CommitTransactionToLevel(target->nestingLevel);
}
/*
* RollbackToSavepoint
* This executes a ROLLBACK TO <savepoint> command.
*/
void
RollbackToSavepoint(List *options)
{
TransactionState s = CurrentTransactionState;
TransactionState target,
xact;
ListCell *cell;
char *name = NULL;
switch (s->blockState)
{
/*
* We can't rollback to a savepoint if there is no saveopint
* defined.
*/
case TBLOCK_ABORT:
case TBLOCK_INPROGRESS:
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("no such savepoint")));
break;
/*
* There is at least one savepoint, so proceed.
*/
case TBLOCK_SUBABORT:
case TBLOCK_SUBINPROGRESS:
/*
* Have to do AbortSubTransaction, but first check
* if this is the right subtransaction
*/
break;
/* these cases are invalid. */
case TBLOCK_DEFAULT:
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
case TBLOCK_END:
case TBLOCK_ENDABORT:
case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_SUBBEGIN:
elog(FATAL, "RollbackToSavepoint: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
foreach (cell, options)
{
DefElem *elem = lfirst(cell);
if (strcmp(elem->defname, "savepoint_name") == 0)
name = strVal(elem->arg);
}
Assert(PointerIsValid(name));
target = CurrentTransactionState;
while (target != NULL)
{
if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
break;
target = target->parent;
/* we don't cross savepoint level boundaries */
if (target->savepointLevel != s->savepointLevel)
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("no such savepoint")));
}
if (!PointerIsValid(target))
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("no such savepoint")));
/*
* Abort the current subtransaction, if needed. We can't Cleanup the
* savepoint yet, so signal CommitTransactionCommand to do it and
* close all savepoints up to the target level.
*/
if (s->blockState == TBLOCK_SUBINPROGRESS)
AbortSubTransaction();
s->blockState = TBLOCK_SUBENDABORT;
/*
* Mark "abort pending" all subtransactions up to the target
* subtransaction. (Except the current subtransaction!)
*/
xact = CurrentTransactionState;
while (xact != target)
{
xact = xact->parent;
Assert(PointerIsValid(xact));
Assert(xact->blockState == TBLOCK_SUBINPROGRESS);
xact->blockState = TBLOCK_SUBABORT_PENDING;
}
}
/*
* RollbackAndReleaseSavepoint
*
* Executes a ROLLBACK TO command, immediately followed by a RELEASE
* of the same savepoint.
*/
void
RollbackAndReleaseSavepoint(List *options)
{
TransactionState s;
RollbackToSavepoint(options);
s = CurrentTransactionState;
Assert(s->blockState == TBLOCK_SUBENDABORT);
s->blockState = TBLOCK_SUBENDABORT_RELEASE;
}
/*
2002-11-13 04:12:05 +01:00
* AbortOutOfAnyTransaction
*
2002-11-13 04:12:05 +01:00
* 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 transaction or nested transaction
*/
do {
switch (s->blockState)
{
case TBLOCK_DEFAULT:
/* Not in a transaction, do nothing */
break;
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
case TBLOCK_INPROGRESS:
case TBLOCK_END:
/* In a transaction, so clean up */
AbortTransaction();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
break;
case TBLOCK_ABORT:
case TBLOCK_ENDABORT:
/* AbortTransaction already done, still need Cleanup */
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
break;
case TBLOCK_SUBBEGIN:
/*
* We didn't get as far as starting the subxact, so there's
* nothing to abort. Just pop back to parent.
*/
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
break;
case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBEND:
case TBLOCK_SUBABORT_PENDING:
/* In a subtransaction, so clean it up and abort parent too */
AbortSubTransaction();
CleanupSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
break;
case TBLOCK_SUBABORT:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBENDABORT_RELEASE:
/* As above, but AbortSubTransaction already done */
CleanupSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
break;
}
} while (s->blockState != TBLOCK_DEFAULT);
1999-05-25 18:15:34 +02:00
/* Should be out of all subxacts now */
Assert(s->parent == NULL);
}
/*
* CommitTransactionToLevel
*
* Commit everything from the current transaction level
* up to the specified level (inclusive).
*/
void
CommitTransactionToLevel(int level)
{
TransactionState s = CurrentTransactionState;
Assert(s->state == TRANS_INPROGRESS);
while (s->nestingLevel >= level)
{
CommitSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
Assert(s->state == TRANS_INPROGRESS);
}
}
/*
* IsTransactionBlock --- are we within a transaction block?
*/
bool
IsTransactionBlock(void)
{
TransactionState s = CurrentTransactionState;
if (s->blockState == TBLOCK_DEFAULT || s->blockState == TBLOCK_STARTED)
return false;
return true;
}
/*
* IsTransactionOrTransactionBlock --- are we within either a transaction
* or a transaction block? (The backend is only really "idle" when this
* returns false.)
*
* This should match up with IsTransactionBlock and IsTransactionState.
*/
bool
IsTransactionOrTransactionBlock(void)
{
TransactionState s = CurrentTransactionState;
if (s->blockState == TBLOCK_DEFAULT)
return false;
return true;
}
/*
* TransactionBlockStatusCode - return status code to send in ReadyForQuery
*/
char
TransactionBlockStatusCode(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
case TBLOCK_DEFAULT:
case TBLOCK_STARTED:
return 'I'; /* idle --- not in transaction */
case TBLOCK_BEGIN:
case TBLOCK_INPROGRESS:
case TBLOCK_END:
case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBBEGIN:
case TBLOCK_SUBEND:
return 'T'; /* in transaction */
case TBLOCK_ABORT:
case TBLOCK_ENDABORT:
case TBLOCK_SUBABORT:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
return 'E'; /* in failed transaction */
}
/* should never get here */
elog(FATAL, "invalid transaction block state: %s",
BlockStateAsString(s->blockState));
return 0; /* keep compiler quiet */
}
/*
* IsSubTransaction
*/
bool
IsSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
case TBLOCK_DEFAULT:
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
case TBLOCK_INPROGRESS:
case TBLOCK_END:
case TBLOCK_ABORT:
case TBLOCK_ENDABORT:
return false;
case TBLOCK_SUBBEGIN:
case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBABORT:
case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
return true;
}
/* should never get here */
elog(FATAL, "invalid transaction block state: %s",
BlockStateAsString(s->blockState));
return false; /* keep compiler quiet */
}
/*
* StartSubTransaction
*/
static void
StartSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
if (s->state != TRANS_DEFAULT)
elog(WARNING, "StartSubTransaction and not in default state");
s->state = TRANS_START;
/*
* must initialize resource-management stuff first
*/
AtSubStart_Memory();
AtSubStart_ResourceOwner();
/*
* Generate a new Xid and record it in pg_subtrans.
*/
s->transactionIdData = GetNewTransactionId(true);
SubTransSetParent(s->transactionIdData, s->parent->transactionIdData);
XactLockTableInsert(s->transactionIdData);
/*
* Finish setup of other transaction state fields.
*/
s->currentUser = GetUserId();
/*
* Initialize other subsystems for new subtransaction
*/
AtSubStart_Inval();
AtSubStart_Notify();
DeferredTriggerBeginSubXact();
s->state = TRANS_INPROGRESS;
ShowTransactionState("StartSubTransaction");
}
/*
* CommitSubTransaction
*/
static void
CommitSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
ShowTransactionState("CommitSubTransaction");
if (s->state != TRANS_INPROGRESS)
elog(WARNING, "CommitSubTransaction and not in in-progress state");
/* Pre-commit processing */
AtSubCommit_Portals(s->parent->transactionIdData,
s->parent->curTransactionOwner);
DeferredTriggerEndSubXact(true);
s->state = TRANS_COMMIT;
/* Mark subtransaction as subcommitted */
CommandCounterIncrement();
RecordSubTransactionCommit();
AtSubCommit_childXids();
/* Post-commit cleanup */
AtSubCommit_smgr();
AtSubEOXact_Inval(true);
AtEOSubXact_SPI(true, s->transactionIdData);
/*
* Note that we just release the resource owner's resources and don't
* delete it. This is because locks are not actually released here.
* The owner object continues to exist as a child of its parent owner
* (namely my parent transaction's resource owner), and the locks
* effectively become that owner object's responsibility.
*/
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, false);
/* we can skip the LOCKS phase */
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, false);
AtSubCommit_Notify();
AtEOXact_GUC(true, true);
AtEOSubXact_on_commit_actions(true, s->transactionIdData,
s->parent->transactionIdData);
CurrentResourceOwner = s->parent->curTransactionOwner;
CurTransactionResourceOwner = s->parent->curTransactionOwner;
s->curTransactionOwner = NULL;
AtSubCommit_Memory();
s->state = TRANS_DEFAULT;
}
/*
* AbortSubTransaction
*/
static void
AbortSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
ShowTransactionState("AbortSubTransaction");
if (s->state != TRANS_INPROGRESS)
elog(WARNING, "AbortSubTransaction and not in in-progress state");
HOLD_INTERRUPTS();
s->state = TRANS_ABORT;
/*
* 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!
*
* FIXME This may be incorrect --- Are there some locks we should keep?
* Buffer locks, for example? I don't think so but I'm not sure.
*/
LWLockReleaseAll();
AbortBufferIO();
UnlockBuffers();
LockWaitCancel();
AtSubAbort_Memory();
/*
* do abort processing
*/
RecordSubTransactionAbort();
/* Post-abort cleanup */
AtSubAbort_smgr();
DeferredTriggerEndSubXact(false);
AtEOSubXact_SPI(false, s->transactionIdData);
AtSubAbort_Portals(s->parent->transactionIdData,
s->parent->curTransactionOwner);
AtSubEOXact_Inval(false);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
false, false);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_LOCKS,
false, false);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
false, false);
AtSubAbort_Notify();
AtEOXact_GUC(false, true);
AtEOSubXact_on_commit_actions(false, s->transactionIdData,
s->parent->transactionIdData);
/*
* Reset user id which might have been changed transiently. Here we
* want to restore to the userid that was current at subxact entry.
* (As in AbortTransaction, we need not worry about the session userid.)
*
* Must do this after AtEOXact_GUC to handle the case where we entered
* the subxact inside a SECURITY DEFINER function (hence current and
* session userids were different) and then session auth was changed
* inside the subxact. GUC will reset both current and session userids
* to the entry-time session userid. This is right in every other
* scenario so it seems simplest to let GUC do that and fix it here.
*/
SetUserId(s->currentUser);
CommandCounterIncrement();
RESUME_INTERRUPTS();
}
/*
* CleanupSubTransaction
*/
static void
CleanupSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
ShowTransactionState("CleanupSubTransaction");
if (s->state != TRANS_ABORT)
elog(WARNING, "CleanupSubTransaction and not in aborted state");
AtSubCleanup_Portals();
CurrentResourceOwner = s->parent->curTransactionOwner;
CurTransactionResourceOwner = s->parent->curTransactionOwner;
ResourceOwnerDelete(s->curTransactionOwner);
s->curTransactionOwner = NULL;
AtSubCleanup_Memory();
s->state = TRANS_DEFAULT;
}
/*
* StartAbortedSubTransaction
*
* This function is used to start a subtransaction and put it immediately
* into aborted state. The end result should be equivalent to
* StartSubTransaction immediately followed by AbortSubTransaction.
* The reason we don't implement it just that way is that many of the backend
* modules aren't designed to handle starting a subtransaction when not
* inside a valid transaction. Rather than making them all capable of
* doing that, we just omit the paired start and abort calls in this path.
*/
static void
StartAbortedSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
if (s->state != TRANS_DEFAULT)
elog(WARNING, "StartAbortedSubTransaction and not in default state");
s->state = TRANS_START;
/*
* We don't bother to generate a new Xid, so the end state is not
* *exactly* like we had done a full Start/AbortSubTransaction...
*/
s->transactionIdData = InvalidTransactionId;
/* Make sure currentUser is reasonably valid */
Assert(s->parent != NULL);
s->currentUser = s->parent->currentUser;
/*
* Initialize only what has to be there for CleanupSubTransaction to work.
*/
AtSubStart_Memory();
AtSubStart_ResourceOwner();
s->state = TRANS_ABORT;
AtSubAbort_Memory();
ShowTransactionState("StartAbortedSubTransaction");
}
/*
* PushTransaction
* Set up transaction state for a subtransaction
*
* The caller has to make sure to always reassign CurrentTransactionState
* if it has a local pointer to it after calling this function.
*/
static void
PushTransaction(void)
{
TransactionState p = CurrentTransactionState;
TransactionState s;
/*
* We keep subtransaction state nodes in TopTransactionContext.
*/
s = (TransactionState)
MemoryContextAllocZero(TopTransactionContext,
sizeof(TransactionStateData));
s->parent = p;
s->nestingLevel = p->nestingLevel + 1;
s->savepointLevel = p->savepointLevel;
s->state = TRANS_DEFAULT;
s->blockState = TBLOCK_SUBBEGIN;
/* Command IDs count in a continuous sequence through subtransactions */
s->commandId = p->commandId;
/*
* Copy down some other data so that we will have valid state until
* StartSubTransaction runs.
*/
s->transactionIdData = p->transactionIdData;
s->curTransactionContext = p->curTransactionContext;
s->curTransactionOwner = p->curTransactionOwner;
s->currentUser = p->currentUser;
CurrentTransactionState = s;
}
/*
* PopTransaction
* Pop back to parent transaction state
*
* The caller has to make sure to always reassign CurrentTransactionState
* if it has a local pointer to it after calling this function.
*/
static void
PopTransaction(void)
{
TransactionState s = CurrentTransactionState;
if (s->state != TRANS_DEFAULT)
elog(WARNING, "PopTransaction and not in default state");
if (s->parent == NULL)
elog(FATAL, "PopTransaction with no parent");
/* Command IDs count in a continuous sequence through subtransactions */
s->parent->commandId = s->commandId;
CurrentTransactionState = s->parent;
/* Let's just make sure CurTransactionContext is good */
CurTransactionContext = s->parent->curTransactionContext;
MemoryContextSwitchTo(CurTransactionContext);
/* Ditto for ResourceOwner links */
CurTransactionResourceOwner = s->parent->curTransactionOwner;
CurrentResourceOwner = s->parent->curTransactionOwner;
/* Free the old child structure */
if (s->name)
pfree(s->name);
pfree(s);
}
/*
* ShowTransactionState
* Debug support
*/
static void
ShowTransactionState(const char *str)
{
/* skip work if message will definitely not be printed */
if (log_min_messages <= DEBUG2 || client_min_messages <= DEBUG2)
{
elog(DEBUG2, "%s", str);
ShowTransactionStateRec(CurrentTransactionState);
}
}
/*
* ShowTransactionStateRec
* Recursive subroutine for ShowTransactionState
*/
static void
ShowTransactionStateRec(TransactionState s)
{
if (s->parent)
ShowTransactionStateRec(s->parent);
/* use ereport to suppress computation if msg will not be printed */
ereport(DEBUG2,
(errmsg_internal("name: %s; blockState: %13s; state: %7s, xid/cid: %u/%02u, nestlvl: %d, children: %s",
PointerIsValid(s->name) ? s->name : "unnamed",
BlockStateAsString(s->blockState),
TransStateAsString(s->state),
(unsigned int) s->transactionIdData,
(unsigned int) s->commandId,
s->nestingLevel,
nodeToString(s->childXids))));
}
/*
* BlockStateAsString
* Debug support
*/
static const char *
BlockStateAsString(TBlockState blockState)
{
switch (blockState)
{
case TBLOCK_DEFAULT:
return "DEFAULT";
case TBLOCK_STARTED:
return "STARTED";
case TBLOCK_BEGIN:
return "BEGIN";
case TBLOCK_INPROGRESS:
return "INPROGRESS";
case TBLOCK_END:
return "END";
case TBLOCK_ABORT:
return "ABORT";
case TBLOCK_ENDABORT:
return "ENDABORT";
case TBLOCK_SUBBEGIN:
return "SUB BEGIN";
case TBLOCK_SUBINPROGRESS:
return "SUB INPROGRS";
case TBLOCK_SUBEND:
return "SUB END";
case TBLOCK_SUBABORT:
return "SUB ABORT";
case TBLOCK_SUBENDABORT_ALL:
return "SUB ENDAB ALL";
case TBLOCK_SUBENDABORT:
return "SUB ENDAB";
case TBLOCK_SUBABORT_PENDING:
return "SUB ABRT PEND";
case TBLOCK_SUBENDABORT_RELEASE:
return "SUB ENDAB REL";
}
return "UNRECOGNIZED";
}
/*
* TransStateAsString
* Debug support
*/
static const char *
TransStateAsString(TransState state)
{
switch (state)
{
case TRANS_DEFAULT:
return "DEFAULT";
case TRANS_START:
return "START";
case TRANS_COMMIT:
return "COMMIT";
case TRANS_ABORT:
return "ABORT";
case TRANS_INPROGRESS:
return "INPROGR";
}
return "UNRECOGNIZED";
}
/*
* xactGetCommittedChildren
*
* Gets the list of committed children of the current transaction. The return
* value is the number of child transactions. *children is set to point to a
* palloc'd array of TransactionIds. If there are no subxacts, *children is
* set to NULL.
*/
int
xactGetCommittedChildren(TransactionId **ptr)
{
TransactionState s = CurrentTransactionState;
int nchildren;
TransactionId *children;
ListCell *p;
nchildren = list_length(s->childXids);
if (nchildren == 0)
{
*ptr = NULL;
return 0;
}
children = (TransactionId *) palloc(nchildren * sizeof(TransactionId));
*ptr = children;
foreach(p, s->childXids)
{
TransactionId child = lfirst_int(p);
*children++ = child;
}
return nchildren;
}
2002-11-13 04:12:05 +01:00
/*
* XLOG support routines
*/
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)
{
xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record);
int i;
2000-10-24 11:56:23 +02:00
TransactionIdCommit(record->xl_xid);
/* Mark committed subtransactions as committed */
TransactionIdCommitTree(xlrec->nsubxacts,
(TransactionId *) &(xlrec->xnodes[xlrec->nrels]));
/* Make sure files supposed to be dropped are dropped */
for (i = 0; i < xlrec->nrels; i++)
{
XLogCloseRelation(xlrec->xnodes[i]);
smgrdounlink(smgropen(xlrec->xnodes[i]), false, true);
}
}
else if (info == XLOG_XACT_ABORT)
{
xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record);
int i;
2000-10-24 11:56:23 +02:00
TransactionIdAbort(record->xl_xid);
/* mark subtransactions as aborted */
TransactionIdAbortTree(xlrec->nsubxacts,
(TransactionId *) &(xlrec->xnodes[xlrec->nrels]));
/* Make sure files supposed to be dropped are dropped */
for (i = 0; i < xlrec->nrels; i++)
{
XLogCloseRelation(xlrec->xnodes[i]);
smgrdounlink(smgropen(xlrec->xnodes[i]), false, true);
}
}
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;
int i;
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);
if (xlrec->nrels > 0)
{
sprintf(buf + strlen(buf), "; rels:");
for (i = 0; i < xlrec->nrels; i++)
{
RelFileNode rnode = xlrec->xnodes[i];
sprintf(buf + strlen(buf), " %u/%u/%u",
rnode.spcNode, rnode.dbNode, rnode.relNode);
}
}
if (xlrec->nsubxacts > 0)
{
TransactionId *xacts = (TransactionId *)
&xlrec->xnodes[xlrec->nrels];
sprintf(buf + strlen(buf), "; subxacts:");
for (i = 0; i < xlrec->nsubxacts; i++)
sprintf(buf + strlen(buf), " %u", xacts[i]);
}
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);
if (xlrec->nrels > 0)
{
sprintf(buf + strlen(buf), "; rels:");
for (i = 0; i < xlrec->nrels; i++)
{
RelFileNode rnode = xlrec->xnodes[i];
sprintf(buf + strlen(buf), " %u/%u/%u",
rnode.spcNode, rnode.dbNode, rnode.relNode);
}
}
if (xlrec->nsubxacts > 0)
{
TransactionId *xacts = (TransactionId *)
&xlrec->xnodes[xlrec->nrels];
sprintf(buf + strlen(buf), "; subxacts:");
for (i = 0; i < xlrec->nsubxacts; i++)
sprintf(buf + strlen(buf), " %u", xacts[i]);
}
2000-10-21 17:43:36 +02:00
}
else
strcat(buf, "UNKNOWN");
}
void
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;
}