*
* See src/backend/access/transam/README for more information.
*
- * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, 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.275 2009/09/01 02:54:51 alvherre Exp $
+ * src/backend/access/transam/xact.c
*
*-------------------------------------------------------------------------
*/
#include "commands/trigger.h"
#include "executor/spi.h"
#include "libpq/be-fsstubs.h"
+#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
-#include "storage/bufmgr.h"
+#include "replication/walsender.h"
+#include "replication/syncrep.h"
#include "storage/fd.h"
#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+#include "utils/catcache.h"
#include "utils/combocid.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/memutils.h"
-#include "utils/relcache.h"
+#include "utils/relmapper.h"
#include "utils/snapmgr.h"
+#include "utils/timeout.h"
+#include "utils/timestamp.h"
#include "pg_trace.h"
bool DefaultXactReadOnly = false;
bool XactReadOnly;
-bool XactSyncCommit = true;
+bool DefaultXactDeferrable = false;
+bool XactDeferrable;
-int CommitDelay = 0; /* precommit delay in microseconds */
-int CommitSiblings = 5; /* # concurrent xacts needed to sleep */
+int synchronous_commit = SYNCHRONOUS_COMMIT_ON;
/*
* MyXactAccessedTempRel is set when a temporary relation is accessed.
/* subtransaction states */
TBLOCK_SUBBEGIN, /* starting a subtransaction */
TBLOCK_SUBINPROGRESS, /* live subtransaction */
- TBLOCK_SUBEND, /* RELEASE received */
+ TBLOCK_SUBRELEASE, /* RELEASE received */
+ TBLOCK_SUBCOMMIT, /* COMMIT received while TBLOCK_SUBINPROGRESS */
TBLOCK_SUBABORT, /* failed subxact, awaiting ROLLBACK */
TBLOCK_SUBABORT_END, /* failed subxact, ROLLBACK received */
TBLOCK_SUBABORT_PENDING, /* live subxact, ROLLBACK received */
int nChildXids; /* # of subcommitted child XIDs */
int maxChildXids; /* allocated size of childXids[] */
Oid prevUser; /* previous CurrentUserId setting */
- bool prevSecDefCxt; /* previous SecurityDefinerContext setting */
+ int prevSecContext; /* previous SecurityRestrictionContext */
bool prevXactReadOnly; /* entry-time xact r/o state */
+ bool startedInRecovery; /* did we start in recovery? */
+ bool didLogXid; /* has xid been included in WAL record? */
struct TransactionStateData *parent; /* back link to parent */
} TransactionStateData;
0, /* # of subcommitted child Xids */
0, /* allocated size of childXids[] */
InvalidOid, /* previous CurrentUserId setting */
- false, /* previous SecurityDefinerContext setting */
+ 0, /* previous SecurityRestrictionContext */
false, /* entry-time xact r/o state */
+ false, /* startedInRecovery */
+ false, /* didLogXid */
NULL /* link to parent state block */
};
+/*
+ * unreportedXids holds XIDs of all subtransactions that have not yet been
+ * reported in a XLOG_XACT_ASSIGNMENT record.
+ */
+static int nUnreportedXids;
+static TransactionId unreportedXids[PGPROC_MAX_CACHED_SUBXIDS];
+
static TransactionState CurrentTransactionState = &TopTransactionStateData;
/*
static void AtAbort_Memory(void);
static void AtCleanup_Memory(void);
static void AtAbort_ResourceOwner(void);
-static void AtCommit_LocalCache(void);
+static void AtCCI_LocalCache(void);
static void AtCommit_Memory(void);
static void AtStart_Cache(void);
static void AtStart_Memory(void);
SubTransactionId mySubid,
SubTransactionId parentSubid);
static void CleanupTransaction(void);
+static void CheckTransactionChain(bool isTopLevel, bool throwError,
+ const char *stmtType);
static void CommitTransaction(void);
static TransactionId RecordTransactionAbort(bool isSubXact);
static void StartTransaction(void);
/*
* IsAbortedTransactionBlockState
*
- * This returns true if we are currently running a query
- * within an aborted transaction block.
+ * This returns true if we are within an aborted transaction block.
*/
bool
IsAbortedTransactionBlockState(void)
return CurrentTransactionState->transactionId;
}
+/*
+ * MarkCurrentTransactionIdLoggedIfAny
+ *
+ * Remember that the current xid - if it is assigned - now has been wal logged.
+ */
+void
+MarkCurrentTransactionIdLoggedIfAny(void)
+{
+ if (TransactionIdIsValid(CurrentTransactionState->transactionId))
+ CurrentTransactionState->didLogXid = true;
+}
+
+
+/*
+ * GetStableLatestTransactionId
+ *
+ * Get the transaction's XID if it has one, else read the next-to-be-assigned
+ * XID. Once we have a value, return that same value for the remainder of the
+ * current transaction. This is meant to provide the reference point for the
+ * age(xid) function, but might be useful for other maintenance tasks as well.
+ */
+TransactionId
+GetStableLatestTransactionId(void)
+{
+ static LocalTransactionId lxid = InvalidLocalTransactionId;
+ static TransactionId stablexid = InvalidTransactionId;
+
+ if (lxid != MyProc->lxid)
+ {
+ lxid = MyProc->lxid;
+ stablexid = GetTopTransactionIdIfAny();
+ if (!TransactionIdIsValid(stablexid))
+ stablexid = ReadNewTransactionId();
+ }
+
+ Assert(TransactionIdIsValid(stablexid));
+
+ return stablexid;
+}
/*
* AssignTransactionId
{
bool isSubXact = (s->parent != NULL);
ResourceOwner currentOwner;
+ bool log_unknown_top = false;
/* Assert that caller didn't screw up */
Assert(!TransactionIdIsValid(s->transactionId));
/*
* Ensure parent(s) have XIDs, so that a child always has an XID later
- * than its parent.
+ * than its parent. Musn't recurse here, or we might get a stack overflow
+ * if we're at the bottom of a huge stack of subtransactions none of which
+ * have XIDs yet.
*/
if (isSubXact && !TransactionIdIsValid(s->parent->transactionId))
- AssignTransactionId(s->parent);
+ {
+ TransactionState p = s->parent;
+ TransactionState *parents;
+ size_t parentOffset = 0;
+
+ parents = palloc(sizeof(TransactionState) * s->nestingLevel);
+ while (p != NULL && !TransactionIdIsValid(p->transactionId))
+ {
+ parents[parentOffset++] = p;
+ p = p->parent;
+ }
+
+ /*
+ * This is technically a recursive call, but the recursion will never
+ * be more than one layer deep.
+ */
+ while (parentOffset != 0)
+ AssignTransactionId(parents[--parentOffset]);
+
+ pfree(parents);
+ }
+
+ /*
+ * When wal_level=logical, guarantee that a subtransaction's xid can only
+ * be seen in the WAL stream if its toplevel xid has been logged before.
+ * If necessary we log a xact_assignment record with fewer than
+ * PGPROC_MAX_CACHED_SUBXIDS. Note that it is fine if didLogXid isn't set
+ * for a transaction even though it appears in a WAL record, we just might
+ * superfluously log something. That can happen when an xid is included
+ * somewhere inside a wal record, but not in XLogRecord->xl_xid, like in
+ * xl_standby_locks.
+ */
+ if (isSubXact && XLogLogicalInfoActive() &&
+ !TopTransactionStateData.didLogXid)
+ log_unknown_top = true;
/*
* Generate a new Xid and record it in PG_PROC and pg_subtrans.
s->transactionId = GetNewTransactionId(isSubXact);
if (isSubXact)
- SubTransSetParent(s->transactionId, s->parent->transactionId);
+ SubTransSetParent(s->transactionId, s->parent->transactionId, false);
+
+ /*
+ * If it's a top-level transaction, the predicate locking system needs to
+ * be told about it too.
+ */
+ if (!isSubXact)
+ RegisterPredicateLockingXid(s->transactionId);
/*
* Acquire lock on the transaction XID. (We assume this cannot block.) We
}
PG_END_TRY();
CurrentResourceOwner = currentOwner;
-}
+ /*
+ * Every PGPROC_MAX_CACHED_SUBXIDS assigned transaction ids within each
+ * top-level transaction we issue a WAL record for the assignment. We
+ * include the top-level xid and all the subxids that have not yet been
+ * reported using XLOG_XACT_ASSIGNMENT records.
+ *
+ * This is required to limit the amount of shared memory required in a hot
+ * standby server to keep track of in-progress XIDs. See notes for
+ * RecordKnownAssignedTransactionIds().
+ *
+ * We don't keep track of the immediate parent of each subxid, only the
+ * top-level transaction that each subxact belongs to. This is correct in
+ * recovery only because aborted subtransactions are separately WAL
+ * logged.
+ *
+ * This is correct even for the case where several levels above us didn't
+ * have an xid assigned as we recursed up to them beforehand.
+ */
+ if (isSubXact && XLogStandbyInfoActive())
+ {
+ unreportedXids[nUnreportedXids] = s->transactionId;
+ nUnreportedXids++;
+
+ /*
+ * ensure this test matches similar one in
+ * RecoverPreparedTransactions()
+ */
+ if (nUnreportedXids >= PGPROC_MAX_CACHED_SUBXIDS ||
+ log_unknown_top)
+ {
+ XLogRecData rdata[2];
+ xl_xact_assignment xlrec;
+
+ /*
+ * xtop is always set by now because we recurse up transaction
+ * stack to the highest unassigned xid and then come back down
+ */
+ xlrec.xtop = GetTopTransactionId();
+ Assert(TransactionIdIsValid(xlrec.xtop));
+ xlrec.nsubxacts = nUnreportedXids;
+
+ rdata[0].data = (char *) &xlrec;
+ rdata[0].len = MinSizeOfXactAssignment;
+ rdata[0].buffer = InvalidBuffer;
+ rdata[0].next = &rdata[1];
+
+ rdata[1].data = (char *) unreportedXids;
+ rdata[1].len = nUnreportedXids * sizeof(TransactionId);
+ rdata[1].buffer = InvalidBuffer;
+ rdata[1].next = NULL;
+
+ (void) XLogInsert(RM_XACT_ID, XLOG_XACT_ASSIGNMENT, rdata);
+
+ nUnreportedXids = 0;
+ /* mark top, not current xact as having been logged */
+ TopTransactionStateData.didLogXid = true;
+ }
+ }
+}
/*
* GetCurrentSubTransactionId
return s->subTransactionId;
}
+/*
+ * SubTransactionIsActive
+ *
+ * Test if the specified subxact ID is still active. Note caller is
+ * responsible for checking whether this ID is relevant to the current xact.
+ */
+bool
+SubTransactionIsActive(SubTransactionId subxid)
+{
+ TransactionState s;
+
+ for (s = CurrentTransactionState; s != NULL; s = s->parent)
+ {
+ if (s->state == TRANS_ABORT)
+ continue;
+ if (s->subTransactionId == subxid)
+ return true;
+ }
+ return false;
+}
+
/*
* GetCurrentCommandId
*
* "used" must be TRUE if the caller intends to use the command ID to mark
* inserted/updated/deleted tuples. FALSE means the ID is being fetched
- * for read-only purposes (ie, as a snapshot validity cutoff). See
+ * for read-only purposes (ie, as a snapshot validity cutoff). See
* CommandCounterIncrement() for discussion.
*/
CommandId
/*
* We always say that BootstrapTransactionId is "not my transaction ID"
- * even when it is (ie, during bootstrap). Along with the fact that
+ * even when it is (ie, during bootstrap). Along with the fact that
* transam.c always treats BootstrapTransactionId as already committed,
* this causes the tqual.c routines to see all tuples as committed, which
* is what we need during bootstrap. (Bootstrap mode only inserts tuples,
return false;
}
+/*
+ * TransactionStartedDuringRecovery
+ *
+ * Returns true if the current transaction started while recovery was still
+ * in progress. Recovery might have ended since so RecoveryInProgress() might
+ * return false already.
+ */
+bool
+TransactionStartedDuringRecovery(void)
+{
+ return CurrentTransactionState->startedInRecovery;
+}
/*
* CommandCounterIncrement
if (currentCommandIdUsed)
{
currentCommandId += 1;
- if (currentCommandId == FirstCommandId) /* check for overflow */
+ if (currentCommandId == InvalidCommandId)
{
currentCommandId -= 1;
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("cannot have more than 2^32-1 commands in a transaction")));
+ errmsg("cannot have more than 2^32-2 commands in a transaction")));
}
currentCommandIdUsed = false;
* read-only command. (But see hacks in inval.c to make real sure we
* don't think a command that queued inval messages was read-only.)
*/
- AtCommit_LocalCache();
+ AtCCI_LocalCache();
}
-
- /*
- * Make any other backends' catalog changes visible to me.
- *
- * XXX this is probably in the wrong place: CommandCounterIncrement should
- * be purely a local operation, most likely. However fooling with this
- * will affect asynchronous cross-backend interactions, which doesn't seem
- * like a wise thing to do in late beta, so save improving this for
- * another day - tgl 2007-11-30
- */
- AtStart_Cache();
}
/*
/*
* If this is the first time through, create a private context for
* AbortTransaction to work in. By reserving some space now, we can
- * insulate AbortTransaction from out-of-memory scenarios. Like
+ * insulate AbortTransaction from out-of-memory scenarios. Like
* ErrorContext, we set it up with slow growth rate and a nonzero minimum
* size, so that space will be reserved immediately.
*/
Assert(s->parent != NULL);
/*
- * Create a resource owner for the subtransaction. We make it a child of
+ * Create a resource owner for the subtransaction. We make it a child of
* the immediate parent's resource owner.
*/
s->curTransactionOwner =
* RecordTransactionCommit
*
* Returns latest XID among xact and its children, or InvalidTransactionId
- * if the xact has no XID. (We compute that here just because it's easier.)
- *
- * This is exported only to support an ugly hack in VACUUM FULL.
+ * if the xact has no XID. (We compute that here just because it's easier.)
*/
-TransactionId
+static TransactionId
RecordTransactionCommit(void)
{
TransactionId xid = GetTopTransactionIdIfAny();
TransactionId latestXid = InvalidTransactionId;
int nrels;
RelFileNode *rels;
- bool haveNonTemp;
int nchildren;
TransactionId *children;
+ int nmsgs = 0;
+ SharedInvalidationMessage *invalMessages = NULL;
+ bool RelcacheInitFileInval = false;
+ bool wrote_xlog;
/* Get data needed for commit record */
- nrels = smgrGetPendingDeletes(true, &rels, &haveNonTemp);
+ nrels = smgrGetPendingDeletes(true, &rels);
nchildren = xactGetCommittedChildren(&children);
+ if (XLogStandbyInfoActive())
+ nmsgs = xactGetCommittedInvalidationMessages(&invalMessages,
+ &RelcacheInitFileInval);
+ wrote_xlog = (XactLastRecEnd != 0);
/*
* If we haven't been assigned an XID yet, we neither can, nor do we want
/*
* If we didn't create XLOG entries, we're done here; otherwise we
- * should flush those entries the same as a commit record. (An
+ * should flush those entries the same as a commit record. (An
* example of a possible record that wouldn't cause an XID to be
* assigned is a sequence advance record due to nextval() --- we want
* to flush that to disk before reporting commit.)
*/
- if (XactLastRecEnd.xrecoff == 0)
+ if (!wrote_xlog)
goto cleanup;
}
else
/*
* Begin commit critical section and insert the commit XLOG record.
*/
- XLogRecData rdata[3];
- int lastrdata = 0;
- xl_xact_commit xlrec;
-
/* Tell bufmgr and smgr to prepare for commit */
BufmgrCommit();
/*
- * Mark ourselves as within our "commit critical section". This
+ * Mark ourselves as within our "commit critical section". This
* forces any concurrent checkpoint to wait until we've updated
* pg_clog. Without this, it is possible for the checkpoint to set
* REDO after the XLOG record but fail to flush the pg_clog update to
* crashes a little later.
*
* Note: we could, but don't bother to, set this flag in
- * RecordTransactionAbort. That's because loss of a transaction abort
+ * RecordTransactionAbort. That's because loss of a transaction abort
* is noncritical; the presumption would be that it aborted, anyway.
*
- * It's safe to change the inCommit flag of our own backend without
+ * It's safe to change the delayChkpt flag of our own backend without
* holding the ProcArrayLock, since we're the only one modifying it.
- * This makes checkpoint's determination of which xacts are inCommit a
- * bit fuzzy, but it doesn't matter.
+ * This makes checkpoint's determination of which xacts are delayChkpt
+ * a bit fuzzy, but it doesn't matter.
*/
START_CRIT_SECTION();
- MyProc->inCommit = true;
+ MyPgXact->delayChkpt = true;
SetCurrentTransactionStopTimestamp();
- xlrec.xact_time = xactStopTimestamp;
- xlrec.nrels = nrels;
- xlrec.nsubxacts = nchildren;
- rdata[0].data = (char *) (&xlrec);
- rdata[0].len = MinSizeOfXactCommit;
- rdata[0].buffer = InvalidBuffer;
- /* dump rels to delete */
- if (nrels > 0)
+
+ /*
+ * Do we need the long commit record? If not, use the compact format.
+ *
+ * For now always use the non-compact version if wal_level=logical, so
+ * we can hide commits from other databases. TODO: In the future we
+ * should merge compact and non-compact commits and use a flags
+ * variable to determine if it contains subxacts, relations or
+ * invalidation messages, that's more extensible and degrades more
+ * gracefully. Till then, it's just 20 bytes of overhead.
+ */
+ if (nrels > 0 || nmsgs > 0 || RelcacheInitFileInval || forceSyncCommit ||
+ XLogLogicalInfoActive())
{
- rdata[0].next = &(rdata[1]);
- rdata[1].data = (char *) rels;
- rdata[1].len = nrels * sizeof(RelFileNode);
- rdata[1].buffer = InvalidBuffer;
- lastrdata = 1;
+ XLogRecData rdata[4];
+ int lastrdata = 0;
+ xl_xact_commit xlrec;
+
+ /*
+ * Set flags required for recovery processing of commits.
+ */
+ xlrec.xinfo = 0;
+ if (RelcacheInitFileInval)
+ xlrec.xinfo |= XACT_COMPLETION_UPDATE_RELCACHE_FILE;
+ if (forceSyncCommit)
+ xlrec.xinfo |= XACT_COMPLETION_FORCE_SYNC_COMMIT;
+
+ xlrec.dbId = MyDatabaseId;
+ xlrec.tsId = MyDatabaseTableSpace;
+
+ xlrec.xact_time = xactStopTimestamp;
+ xlrec.nrels = nrels;
+ xlrec.nsubxacts = nchildren;
+ xlrec.nmsgs = nmsgs;
+ rdata[0].data = (char *) (&xlrec);
+ rdata[0].len = MinSizeOfXactCommit;
+ rdata[0].buffer = InvalidBuffer;
+ /* dump rels to delete */
+ if (nrels > 0)
+ {
+ rdata[0].next = &(rdata[1]);
+ rdata[1].data = (char *) rels;
+ rdata[1].len = nrels * sizeof(RelFileNode);
+ rdata[1].buffer = InvalidBuffer;
+ lastrdata = 1;
+ }
+ /* dump committed child Xids */
+ if (nchildren > 0)
+ {
+ rdata[lastrdata].next = &(rdata[2]);
+ rdata[2].data = (char *) children;
+ rdata[2].len = nchildren * sizeof(TransactionId);
+ rdata[2].buffer = InvalidBuffer;
+ lastrdata = 2;
+ }
+ /* dump shared cache invalidation messages */
+ if (nmsgs > 0)
+ {
+ rdata[lastrdata].next = &(rdata[3]);
+ rdata[3].data = (char *) invalMessages;
+ rdata[3].len = nmsgs * sizeof(SharedInvalidationMessage);
+ rdata[3].buffer = InvalidBuffer;
+ lastrdata = 3;
+ }
+ rdata[lastrdata].next = NULL;
+
+ (void) XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT, rdata);
}
- /* dump committed child Xids */
- if (nchildren > 0)
+ else
{
- rdata[lastrdata].next = &(rdata[2]);
- rdata[2].data = (char *) children;
- rdata[2].len = nchildren * sizeof(TransactionId);
- rdata[2].buffer = InvalidBuffer;
- lastrdata = 2;
- }
- rdata[lastrdata].next = NULL;
+ XLogRecData rdata[2];
+ int lastrdata = 0;
+ xl_xact_commit_compact xlrec;
+
+ xlrec.xact_time = xactStopTimestamp;
+ xlrec.nsubxacts = nchildren;
+ rdata[0].data = (char *) (&xlrec);
+ rdata[0].len = MinSizeOfXactCommitCompact;
+ rdata[0].buffer = InvalidBuffer;
+ /* dump committed child Xids */
+ if (nchildren > 0)
+ {
+ rdata[0].next = &(rdata[1]);
+ rdata[1].data = (char *) children;
+ rdata[1].len = nchildren * sizeof(TransactionId);
+ rdata[1].buffer = InvalidBuffer;
+ lastrdata = 1;
+ }
+ rdata[lastrdata].next = NULL;
- (void) XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT, rdata);
+ (void) XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT_COMPACT, rdata);
+ }
}
/*
- * Check if we want to commit asynchronously. If the user has set
- * synchronous_commit = off, and we're not doing cleanup of any non-temp
- * rels nor committing any command that wanted to force sync commit, then
- * we can defer flushing XLOG. (We must not allow asynchronous commit if
- * there are any non-temp tables to be deleted, because we might delete
- * the files before the COMMIT record is flushed to disk. We do allow
- * asynchronous commit if all to-be-deleted tables are temporary though,
- * since they are lost anyway if we crash.)
+ * Check if we want to commit asynchronously. We can allow the XLOG flush
+ * to happen asynchronously if synchronous_commit=off, or if the current
+ * transaction has not performed any WAL-logged operation. The latter
+ * case can arise if the current transaction wrote only to temporary
+ * and/or unlogged tables. In case of a crash, the loss of such a
+ * transaction will be irrelevant since temp tables will be lost anyway,
+ * and unlogged tables will be truncated. (Given the foregoing, you might
+ * think that it would be unnecessary to emit the XLOG record at all in
+ * this case, but we don't currently try to do that. It would certainly
+ * cause problems at least in Hot Standby mode, where the
+ * KnownAssignedXids machinery requires tracking every XID assignment. It
+ * might be OK to skip it only when wal_level < hot_standby, but for now
+ * we don't.)
+ *
+ * However, if we're doing cleanup of any non-temp rels or committing any
+ * command that wanted to force sync commit, then we must flush XLOG
+ * immediately. (We must not allow asynchronous commit if there are any
+ * non-temp tables to be deleted, because we might delete the files before
+ * the COMMIT record is flushed to disk. We do allow asynchronous commit
+ * if all to-be-deleted tables are temporary though, since they are lost
+ * anyway if we crash.)
*/
- if (XactSyncCommit || forceSyncCommit || haveNonTemp)
+ if ((wrote_xlog && synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
+ forceSyncCommit || nrels > 0)
{
- /*
- * Synchronous commit case.
- *
- * Sleep before flush! So we can flush more than one commit records
- * per single fsync. (The idea is some other backend may do the
- * XLogFlush while we're sleeping. This needs work still, because on
- * most Unixen, the minimum select() delay is 10msec or more, which is
- * way too long.)
- *
- * We do not sleep if enableFsync is not turned on, nor if there are
- * fewer than CommitSiblings other backends with active transactions.
- */
- if (CommitDelay > 0 && enableFsync &&
- CountActiveBackends() >= CommitSiblings)
- pg_usleep(CommitDelay);
-
XLogFlush(XactLastRecEnd);
/*
else
{
/*
- * Asynchronous commit case.
+ * Asynchronous commit case:
+ *
+ * This enables possible committed transaction loss in the case of a
+ * postmaster crash because WAL buffers are left unwritten. Ideally we
+ * could issue the WAL write without the fsync, but some
+ * wal_sync_methods do not allow separate write/fsync.
*
* Report the latest async commit LSN, so that the WAL writer knows to
* flush this commit.
*/
- XLogSetAsyncCommitLSN(XactLastRecEnd);
+ XLogSetAsyncXactLSN(XactLastRecEnd);
/*
* We must not immediately update the CLOG, since we didn't flush the
*/
if (markXidCommitted)
{
- MyProc->inCommit = false;
+ MyPgXact->delayChkpt = false;
END_CRIT_SECTION();
}
/* Compute latestXid while we have the child XIDs handy */
latestXid = TransactionIdLatest(xid, nchildren, children);
+ /*
+ * Wait for synchronous replication, if required.
+ *
+ * Note that at this stage we have marked clog, but still show as running
+ * in the procarray and continue to hold locks.
+ */
+ if (wrote_xlog)
+ SyncRepWaitForLSN(XactLastRecEnd);
+
/* Reset XactLastRecEnd until the next transaction writes something */
- XactLastRecEnd.xrecoff = 0;
+ XactLastRecEnd = 0;
cleanup:
/* Clean up local data */
/*
- * AtCommit_LocalCache
+ * AtCCI_LocalCache
*/
static void
-AtCommit_LocalCache(void)
+AtCCI_LocalCache(void)
{
+ /*
+ * Make any pending relation map changes visible. We must do this before
+ * processing local sinval messages, so that the map changes will get
+ * reflected into the relcache when relcache invals are processed.
+ */
+ AtCCI_RelationMap();
+
/*
* Make catalog changes visible to me for the next command.
*/
*
* Note: We rely on the fact that the XID of a child always follows that
* of its parent. By copying the XID of this subtransaction before the
- * XIDs of its children, we ensure that the array stays ordered.
- * Likewise, all XIDs already in the array belong to subtransactions
- * started and subcommitted before us, so their XIDs must precede ours.
+ * XIDs of its children, we ensure that the array stays ordered. Likewise,
+ * all XIDs already in the array belong to subtransactions started and
+ * subcommitted before us, so their XIDs must precede ours.
*/
s->parent->childXids[s->parent->nChildXids] = s->transactionId;
* RecordTransactionAbort
*
* Returns latest XID among xact and its children, or InvalidTransactionId
- * if the xact has no XID. (We compute that here just because it's easier.)
+ * if the xact has no XID. (We compute that here just because it's easier.)
*/
static TransactionId
RecordTransactionAbort(bool isSubXact)
/*
* If we haven't been assigned an XID, nobody will care whether we aborted
- * or not. Hence, we're done in that case. It does not matter if we have
+ * or not. Hence, we're done in that case. It does not matter if we have
* rels to delete (note that this routine is not responsible for actually
* deleting 'em). We cannot have any child XIDs, either.
*/
{
/* Reset XactLastRecEnd until the next transaction writes something */
if (!isSubXact)
- XactLastRecEnd.xrecoff = 0;
+ XactLastRecEnd = 0;
return InvalidTransactionId;
}
* We have a valid XID, so we should write an ABORT record for it.
*
* We do not flush XLOG to disk here, since the default assumption after a
- * crash would be that we aborted, anyway. For the same reason, we don't
+ * crash would be that we aborted, anyway. For the same reason, we don't
* need to worry about interlocking against checkpoint start.
*/
xid);
/* Fetch the data we need for the abort record */
- nrels = smgrGetPendingDeletes(false, &rels, NULL);
+ nrels = smgrGetPendingDeletes(false, &rels);
nchildren = xactGetCommittedChildren(&children);
/* XXX do we really need a critical section here? */
(void) XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, rdata);
+ /*
+ * Report the latest async abort LSN, so that the WAL writer knows to
+ * flush this abort. There's nothing to be gained by delaying this, since
+ * WALWriter may as well do this when it can. This is important with
+ * streaming replication because if we don't flush WAL regularly we will
+ * find that large aborts leave us with a long backlog for when commits
+ * occur after the abort, increasing our window of data loss should
+ * problems occur at that point.
+ */
+ if (!isSubXact)
+ XLogSetAsyncXactLSN(XactLastRecEnd);
+
/*
* Mark the transaction aborted in clog. This is not absolutely necessary
* but we may as well do it while we are here; also, in the subxact case
/* Reset XactLastRecEnd until the next transaction writes something */
if (!isSubXact)
- XactLastRecEnd.xrecoff = 0;
+ XactLastRecEnd = 0;
/* And clean up local data */
if (rels)
/*
* We keep the child-XID arrays in TopTransactionContext (see
- * AtSubCommit_childXids). This means we'd better free the array
+ * AtSubCommit_childXids). This means we'd better free the array
* explicitly at abort to avoid leakage.
*/
if (s->childXids != NULL)
s->childXids = NULL;
s->nChildXids = 0;
s->maxChildXids = 0;
+
+ /*
+ * We could prune the unreportedXids array here. But we don't bother. That
+ * would potentially reduce number of XLOG_XACT_ASSIGNMENT records but it
+ * would likely introduce more CPU time into the more common paths, so we
+ * choose not to do that.
+ */
}
/* ----------------------------------------------------------------
/*
* Make sure we've reset xact state variables
+ *
+ * If recovery is still in progress, mark this transaction as read-only.
+ * We have lower level defences in XLogInsert and elsewhere to stop us
+ * from modifying data during recovery, but this gives the normal
+ * indication to the user that the transaction is read-only.
*/
+ if (RecoveryInProgress())
+ {
+ s->startedInRecovery = true;
+ XactReadOnly = true;
+ }
+ else
+ {
+ s->startedInRecovery = false;
+ XactReadOnly = DefaultXactReadOnly;
+ }
+ XactDeferrable = DefaultXactDeferrable;
XactIsoLevel = DefaultXactIsoLevel;
- XactReadOnly = DefaultXactReadOnly;
forceSyncCommit = false;
MyXactAccessedTempRel = false;
currentCommandId = FirstCommandId;
currentCommandIdUsed = false;
+ /*
+ * initialize reported xid accounting
+ */
+ nUnreportedXids = 0;
+ s->didLogXid = false;
+
/*
* must initialize resource-management stuff first
*/
VirtualXactLockTableInsert(vxid);
/*
- * Advertise it in the proc array. We assume assignment of
+ * Advertise it in the proc array. We assume assignment of
* LocalTransactionID is atomic, and the backendId should be set already.
*/
Assert(MyProc->backendId == vxid.backendId);
s->childXids = NULL;
s->nChildXids = 0;
s->maxChildXids = 0;
- GetUserIdAndContext(&s->prevUser, &s->prevSecDefCxt);
- /* SecurityDefinerContext should never be set outside a transaction */
- Assert(!s->prevSecDefCxt);
+ GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
+ /* SecurityRestrictionContext should never be set outside a transaction */
+ Assert(s->prevSecContext == 0);
/*
* initialize other subsystems for new transaction
Assert(s->parent == NULL);
/*
- * Do pre-commit processing (most of this stuff requires database access,
- * and in fact could still cause an error...)
- *
- * It is possible for CommitHoldablePortals to invoke functions that queue
- * deferred triggers, and it's also possible that triggers create holdable
- * cursors. So we have to loop until there's nothing left to do.
+ * Do pre-commit processing that involves calling user-defined code, such
+ * as triggers. Since closing cursors could queue trigger actions,
+ * triggers could open cursors, etc, we have to keep looping until there's
+ * nothing left to do.
*/
for (;;)
{
AfterTriggerFireDeferred();
/*
- * Convert any open holdable cursors into static portals. If there
- * weren't any, we are done ... otherwise loop back to check if they
- * queued deferred triggers. Lather, rinse, repeat.
+ * Close open portals (converting holdable ones into static portals).
+ * If there weren't any, we are done ... otherwise loop back to check
+ * if they queued deferred triggers. Lather, rinse, repeat.
*/
- if (!CommitHoldablePortals())
+ if (!PreCommit_Portals(false))
break;
}
- /* Now we can shut down the deferred-trigger manager */
- AfterTriggerEndXact(true);
+ CallXactCallbacks(XACT_EVENT_PRE_COMMIT);
- /* Close any open regular cursors */
- AtCommit_Portals();
+ /*
+ * The remaining actions cannot call any user-defined code, so it's safe
+ * to start shutting down within-transaction services. But note that most
+ * of this stuff could still throw an error, which would switch us into
+ * the transaction-abort path.
+ */
+
+ /* Shut down the deferred-trigger manager */
+ AfterTriggerEndXact(true);
/*
* Let ON COMMIT management do its thing (must happen after closing
/* close large objects before lower-level cleanup */
AtEOXact_LargeObject(true);
- /* NOTIFY commit must come before lower-level cleanup */
- AtCommit_Notify();
+ /*
+ * Mark serializable transaction as complete for predicate locking
+ * purposes. This should be done as late as we can put it and still allow
+ * errors to be raised for failure patterns found at commit.
+ */
+ PreCommit_CheckForSerializationFailure();
+
+ /*
+ * Insert notifications sent by NOTIFY commands into the queue. This
+ * should be late in the pre-commit sequence to minimize time spent
+ * holding the notify-insertion lock.
+ */
+ PreCommit_Notify();
/* Prevent cancel/die interrupt while cleaning up */
HOLD_INTERRUPTS();
+ /* Commit updates to the relation map --- do this as late as possible */
+ AtEOXact_RelationMap(true);
+
/*
* set the current transaction state information appropriately during
* commit processing
/* Clean up the relation cache */
AtEOXact_RelationCache(true);
- /* Clean up the snapshot manager */
- AtEarlyCommit_Snapshot();
-
/*
* Make catalog changes visible to all backends. This has to happen after
* relcache references are dropped (see comments for
*/
AtEOXact_Inval(true);
- /*
- * Likewise, dropping of files deleted during the transaction is best done
- * after releasing relcache and buffer pins. (This is not strictly
- * necessary during commit, since such pins should have been released
- * already, but this ordering is definitely critical during abort.)
- */
- smgrDoPendingDeletes(true);
-
AtEOXact_MultiXact();
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, true);
+ /*
+ * Likewise, dropping of files deleted during the transaction is best done
+ * after releasing relcache and buffer pins. (This is not strictly
+ * necessary during commit, since such pins should have been released
+ * already, but this ordering is definitely critical during abort.) Since
+ * this may take many seconds, also delay until after releasing locks.
+ * Other backends will observe the attendant catalog changes and not
+ * attempt to access affected files.
+ */
+ smgrDoPendingDeletes(true);
+
/* Check we've released all catcache entries */
AtEOXact_CatCache(true);
+ AtCommit_Notify();
AtEOXact_GUC(true, 1);
AtEOXact_SPI(true);
AtEOXact_on_commit_actions(true);
AtEOXact_Namespace(true);
- /* smgrcommit already done */
+ AtEOXact_SMgr();
AtEOXact_Files();
AtEOXact_ComboCid();
AtEOXact_HashTables(true);
Assert(s->parent == NULL);
/*
- * Do pre-commit processing (most of this stuff requires database access,
- * and in fact could still cause an error...)
- *
- * It is possible for PrepareHoldablePortals to invoke functions that
- * queue deferred triggers, and it's also possible that triggers create
- * holdable cursors. So we have to loop until there's nothing left to do.
+ * Do pre-commit processing that involves calling user-defined code, such
+ * as triggers. Since closing cursors could queue trigger actions,
+ * triggers could open cursors, etc, we have to keep looping until there's
+ * nothing left to do.
*/
for (;;)
{
AfterTriggerFireDeferred();
/*
- * Convert any open holdable cursors into static portals. If there
- * weren't any, we are done ... otherwise loop back to check if they
- * queued deferred triggers. Lather, rinse, repeat.
+ * Close open portals (converting holdable ones into static portals).
+ * If there weren't any, we are done ... otherwise loop back to check
+ * if they queued deferred triggers. Lather, rinse, repeat.
*/
- if (!PrepareHoldablePortals())
+ if (!PreCommit_Portals(true))
break;
}
- /* Now we can shut down the deferred-trigger manager */
- AfterTriggerEndXact(true);
+ CallXactCallbacks(XACT_EVENT_PRE_PREPARE);
+
+ /*
+ * The remaining actions cannot call any user-defined code, so it's safe
+ * to start shutting down within-transaction services. But note that most
+ * of this stuff could still throw an error, which would switch us into
+ * the transaction-abort path.
+ */
- /* Close any open regular cursors */
- AtCommit_Portals();
+ /* Shut down the deferred-trigger manager */
+ AfterTriggerEndXact(true);
/*
* Let ON COMMIT management do its thing (must happen after closing
/* close large objects before lower-level cleanup */
AtEOXact_LargeObject(true);
+ /*
+ * Mark serializable transaction as complete for predicate locking
+ * purposes. This should be done as late as we can put it and still allow
+ * errors to be raised for failure patterns found at commit.
+ */
+ PreCommit_CheckForSerializationFailure();
+
/* NOTIFY will be handled below */
/*
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot PREPARE a transaction that has operated on temporary tables")));
+ /*
+ * Likewise, don't allow PREPARE after pg_export_snapshot. This could be
+ * supported if we added cleanup logic to twophase.c, but for now it
+ * doesn't seem worth the trouble.
+ */
+ if (XactHasExportedSnapshots())
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot PREPARE a transaction that has exported snapshots")));
+
/* Prevent cancel/die interrupt while cleaning up */
HOLD_INTERRUPTS();
StartPrepare(gxact);
AtPrepare_Notify();
- AtPrepare_Inval();
AtPrepare_Locks();
+ AtPrepare_PredicateLocks();
AtPrepare_PgStat();
+ AtPrepare_MultiXact();
+ AtPrepare_RelationMap();
/*
* Here is where we really truly prepare.
*/
/* Reset XactLastRecEnd until the next transaction writes something */
- XactLastRecEnd.xrecoff = 0;
+ XactLastRecEnd = 0;
/*
- * Let others know about no transaction in progress by me. This has to be
+ * Let others know about no transaction in progress by me. This has to be
* done *after* the prepared transaction has been marked valid, else
* someone may think it is unlocked and recyclable.
*/
/*
* This is all post-transaction cleanup. Note that if an error is raised
* here, it's too late to abort the transaction. This should be just
- * noncritical resource releasing. See notes in CommitTransaction.
+ * noncritical resource releasing. See notes in CommitTransaction.
*/
CallXactCallbacks(XACT_EVENT_PREPARE);
/* Clean up the relation cache */
AtEOXact_RelationCache(true);
- /* Clean up the snapshot manager */
- AtEarlyCommit_Snapshot();
-
/* notify doesn't need a postprepare call */
PostPrepare_PgStat();
PostPrepare_smgr();
- AtEOXact_MultiXact();
+ PostPrepare_MultiXact(xid);
PostPrepare_Locks(xid);
+ PostPrepare_PredicateLocks(xid);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
AtEOXact_SPI(true);
AtEOXact_on_commit_actions(true);
AtEOXact_Namespace(true);
- /* smgrcommit already done */
+ AtEOXact_SMgr();
AtEOXact_Files();
AtEOXact_ComboCid();
AtEOXact_HashTables(true);
- /* don't call AtEOXact_PgStat here */
+ /* don't call AtEOXact_PgStat here; we fixed pgstat state above */
AtEOXact_Snapshot(true);
+ pgstat_report_xact_timestamp(0);
CurrentResourceOwner = NULL;
ResourceOwnerDelete(TopTransactionResourceOwner);
* 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();
+ LockErrorCleanup();
+
+ /*
+ * If any timeout events are still active, make sure the timeout interrupt
+ * is scheduled. This covers possible loss of a timeout interrupt due to
+ * longjmp'ing out of the SIGINT handler (see notes in handle_sig_alarm).
+ * We delay this till after LockErrorCleanup so that we don't uselessly
+ * reschedule lock or deadlock check timeouts.
+ */
+ reschedule_timeouts();
+
+ /*
+ * Re-enable signals, in case we got here by longjmp'ing out of a signal
+ * handler. We do this fairly early in the sequence so that the timeout
+ * infrastructure will be functional if needed while aborting.
+ */
+ PG_SETMASK(&UnBlockSig);
/*
* check the current transaction state
* Reset user ID which might have been changed transiently. We need this
* to clean up in case control escaped out of a SECURITY DEFINER function
* or other local change of CurrentUserId; therefore, the prior value of
- * SecurityDefinerContext also needs to be restored.
+ * SecurityRestrictionContext also needs to be restored.
*
* (Note: it is not necessary to restore session authorization or role
* settings here because those can only be changed via GUC, and GUC will
* take care of rolling them back if need be.)
*/
- SetUserIdAndContext(s->prevUser, s->prevSecDefCxt);
+ SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
/*
* do abort processing
*/
- AfterTriggerEndXact(false);
+ AfterTriggerEndXact(false); /* 'false' means it's abort */
AtAbort_Portals();
- AtEOXact_LargeObject(false); /* 'false' means it's abort */
+ AtEOXact_LargeObject(false);
AtAbort_Notify();
+ AtEOXact_RelationMap(false);
/*
* Advertise the fact that we aborted in pg_clog (assuming that we got as
ProcArrayEndTransaction(MyProc, latestXid);
/*
- * Post-abort cleanup. See notes in CommitTransaction() concerning
- * ordering.
+ * Post-abort cleanup. See notes in CommitTransaction() concerning
+ * ordering. We can skip all of it if the transaction failed before
+ * creating a resource owner.
*/
+ if (TopTransactionResourceOwner != NULL)
+ {
+ CallXactCallbacks(XACT_EVENT_ABORT);
- CallXactCallbacks(XACT_EVENT_ABORT);
-
- ResourceOwnerRelease(TopTransactionResourceOwner,
- RESOURCE_RELEASE_BEFORE_LOCKS,
- false, true);
- AtEOXact_Buffers(false);
- AtEOXact_RelationCache(false);
- AtEOXact_Inval(false);
- smgrDoPendingDeletes(false);
- AtEOXact_MultiXact();
- ResourceOwnerRelease(TopTransactionResourceOwner,
- RESOURCE_RELEASE_LOCKS,
- false, true);
- ResourceOwnerRelease(TopTransactionResourceOwner,
- RESOURCE_RELEASE_AFTER_LOCKS,
- false, true);
- AtEOXact_CatCache(false);
-
- AtEOXact_GUC(false, 1);
- AtEOXact_SPI(false);
- AtEOXact_on_commit_actions(false);
- AtEOXact_Namespace(false);
- AtEOXact_Files();
- AtEOXact_ComboCid();
- AtEOXact_HashTables(false);
- AtEOXact_PgStat(false);
- AtEOXact_Snapshot(false);
- pgstat_report_xact_timestamp(0);
+ ResourceOwnerRelease(TopTransactionResourceOwner,
+ RESOURCE_RELEASE_BEFORE_LOCKS,
+ false, true);
+ AtEOXact_Buffers(false);
+ AtEOXact_RelationCache(false);
+ AtEOXact_Inval(false);
+ AtEOXact_MultiXact();
+ ResourceOwnerRelease(TopTransactionResourceOwner,
+ RESOURCE_RELEASE_LOCKS,
+ false, true);
+ ResourceOwnerRelease(TopTransactionResourceOwner,
+ RESOURCE_RELEASE_AFTER_LOCKS,
+ false, true);
+ smgrDoPendingDeletes(false);
+ AtEOXact_CatCache(false);
+
+ AtEOXact_GUC(false, 1);
+ AtEOXact_SPI(false);
+ AtEOXact_on_commit_actions(false);
+ AtEOXact_Namespace(false);
+ AtEOXact_SMgr();
+ AtEOXact_Files();
+ AtEOXact_ComboCid();
+ AtEOXact_HashTables(false);
+ AtEOXact_PgStat(false);
+ pgstat_report_xact_timestamp(0);
+ }
/*
* State remains TRANS_ABORT until CleanupTransaction().
* do abort cleanup processing
*/
AtCleanup_Portals(); /* now safe to release portal memory */
+ AtEOXact_Snapshot(false); /* and release the transaction's snapshots */
CurrentResourceOwner = NULL; /* and resource owner */
if (TopTransactionResourceOwner)
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_END:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT_END:
case TBLOCK_SUBABORT_END:
case TBLOCK_ABORT_PENDING:
/*
* Here we were in a perfectly good transaction block but the user
- * told us to ROLLBACK anyway. We have to abort the transaction
+ * told us to ROLLBACK anyway. We have to abort the transaction
* and then clean up.
*/
case TBLOCK_ABORT_PENDING:
/*
* We were just issued a SAVEPOINT inside a transaction block.
- * Start a subtransaction. (DefineSavepoint already did
+ * Start a subtransaction. (DefineSavepoint already did
* PushTransaction, so as to have someplace to put the SUBBEGIN
* state.)
*/
break;
/*
- * We were issued a COMMIT or RELEASE command, so we end the
- * current subtransaction and return to the parent transaction.
- * The parent might be ended too, so repeat till we are all the
- * way out or find an INPROGRESS transaction.
+ * We were issued a RELEASE command, so we end the current
+ * subtransaction and return to the parent transaction. The parent
+ * might be ended too, so repeat till we find an INPROGRESS
+ * transaction or subtransaction.
*/
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
do
{
CommitSubTransaction();
s = CurrentTransactionState; /* changed by pop */
- } while (s->blockState == TBLOCK_SUBEND);
+ } while (s->blockState == TBLOCK_SUBRELEASE);
+
+ Assert(s->blockState == TBLOCK_INPROGRESS ||
+ s->blockState == TBLOCK_SUBINPROGRESS);
+ break;
+
+ /*
+ * We were issued a COMMIT, so we end the current subtransaction
+ * hierarchy and perform final commit. We do this by rolling up
+ * any subtransactions into their parent, which leads to O(N^2)
+ * operations with respect to resource owners - this isn't that
+ * bad until we approach a thousands of savepoints but is
+ * necessary for correctness should after triggers create new
+ * resource owners.
+ */
+ case TBLOCK_SUBCOMMIT:
+ do
+ {
+ CommitSubTransaction();
+ s = CurrentTransactionState; /* changed by pop */
+ } while (s->blockState == TBLOCK_SUBCOMMIT);
/* If we had a COMMIT command, finish off the main xact too */
if (s->blockState == TBLOCK_END)
{
s->blockState = TBLOCK_DEFAULT;
}
else
- {
- Assert(s->blockState == TBLOCK_INPROGRESS ||
- s->blockState == TBLOCK_SUBINPROGRESS);
- }
+ elog(ERROR, "CommitTransactionCommand: unexpected state %s",
+ BlockStateAsString(s->blockState));
break;
/*
break;
/*
- * Here, we failed while trying to COMMIT. Clean up the
+ * Here, we failed while trying to COMMIT. Clean up the
* transaction and return to idle state (we do not want to stay in
* the transaction).
*/
/*
* If we failed while trying to create a subtransaction, clean up
- * the broken subtransaction and abort the parent. The same
+ * the broken subtransaction and abort the parent. The same
* applies if we get a failure while ending a subtransaction.
*/
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBRESTART:
AbortSubTransaction();
/* all okay */
}
+/*
+ * These two functions allow for warnings or errors if a command is
+ * executed outside of a transaction block.
+ *
+ * While top-level transaction control commands (BEGIN/COMMIT/ABORT) and
+ * SET that have no effect issue warnings, all other no-effect commands
+ * generate errors.
+ */
+void
+WarnNoTransactionChain(bool isTopLevel, const char *stmtType)
+{
+ CheckTransactionChain(isTopLevel, false, stmtType);
+}
+
+void
+RequireTransactionChain(bool isTopLevel, const char *stmtType)
+{
+ CheckTransactionChain(isTopLevel, true, stmtType);
+}
+
/*
* RequireTransactionChain
*
* 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
+ * issue anything, 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.
*
* isTopLevel: passed down from ProcessUtility to determine whether we are
* inside a function.
- * stmtType: statement type name, for error messages.
+ * stmtType: statement type name, for warning or error messages.
*/
-void
-RequireTransactionChain(bool isTopLevel, const char *stmtType)
+static void
+CheckTransactionChain(bool isTopLevel, bool throwError, const char *stmtType)
{
/*
* xact block already started?
if (!isTopLevel)
return;
- ereport(ERROR,
+ ereport(throwError ? ERROR : WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
/* translator: %s represents an SQL statement name */
errmsg("%s can only be used in transaction blocks",
stmtType)));
+ return;
}
/*
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_END:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT_END:
case TBLOCK_SUBABORT_END:
case TBLOCK_ABORT_PENDING:
while (s->parent != NULL)
{
if (s->blockState == TBLOCK_SUBINPROGRESS)
- s->blockState = TBLOCK_SUBEND;
+ s->blockState = TBLOCK_SUBCOMMIT;
else
elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_END:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT_END:
case TBLOCK_SUBABORT_END:
case TBLOCK_ABORT_PENDING:
break;
/*
- * We are inside a subtransaction. Mark everything up to top
+ * We are inside a subtransaction. Mark everything up to top
* level as exitable.
*/
case TBLOCK_SUBINPROGRESS:
* default state.
*/
case TBLOCK_STARTED:
- ereport(NOTICE,
+ ereport(WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
errmsg("there is no transaction in progress")));
s->blockState = TBLOCK_ABORT_PENDING;
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_END:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT_END:
case TBLOCK_SUBABORT_END:
case TBLOCK_ABORT_PENDING:
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_END:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT:
case TBLOCK_SUBABORT:
case TBLOCK_ABORT_END:
break;
/*
- * We are in a non-aborted subtransaction. This is the only valid
+ * We are in a non-aborted subtransaction. This is the only valid
* case.
*/
case TBLOCK_SUBINPROGRESS:
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_END:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT:
case TBLOCK_SUBABORT:
case TBLOCK_ABORT_END:
/*
* Mark "commit pending" all subtransactions up to the target
- * subtransaction. The actual commits will happen when control gets to
+ * subtransaction. The actual commits will happen when control gets to
* CommitTransactionCommand.
*/
xact = CurrentTransactionState;
for (;;)
{
Assert(xact->blockState == TBLOCK_SUBINPROGRESS);
- xact->blockState = TBLOCK_SUBEND;
+ xact->blockState = TBLOCK_SUBRELEASE;
if (xact == target)
break;
xact = xact->parent;
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_END:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT_END:
case TBLOCK_SUBABORT_END:
case TBLOCK_ABORT_PENDING:
/*
* Mark "abort pending" all subtransactions up to the target
- * subtransaction. The actual aborts will happen when control gets to
+ * subtransaction. The actual aborts will happen when control gets to
* CommitTransactionCommand.
*/
xact = CurrentTransactionState;
case TBLOCK_DEFAULT:
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT:
case TBLOCK_SUBABORT:
case TBLOCK_ABORT_END:
case TBLOCK_SUBBEGIN:
case TBLOCK_INPROGRESS:
case TBLOCK_END:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT:
case TBLOCK_ABORT_END:
case TBLOCK_SUBABORT_END:
switch (s->blockState)
{
case TBLOCK_DEFAULT:
- /* Not in a transaction, do nothing */
+ if (s->state == TRANS_DEFAULT)
+ {
+ /* Not in a transaction, do nothing */
+ }
+ else
+ {
+ /*
+ * We can get here after an error during transaction start
+ * (state will be TRANS_START). Need to clean up the
+ * incompletely started transaction. First, adjust the
+ * low-level state to suppress warning message from
+ * AbortTransaction.
+ */
+ if (s->state == TRANS_START)
+ s->state = TRANS_INPROGRESS;
+ AbortTransaction();
+ CleanupTransaction();
+ }
break;
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
*/
case TBLOCK_SUBBEGIN:
case TBLOCK_SUBINPROGRESS:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBRESTART:
AbortSubTransaction();
case TBLOCK_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
case TBLOCK_END:
- case TBLOCK_SUBEND:
+ case TBLOCK_SUBRELEASE:
+ case TBLOCK_SUBCOMMIT:
case TBLOCK_PREPARE:
return 'T'; /* in transaction */
case TBLOCK_ABORT:
elog(WARNING, "CommitSubTransaction while in %s state",
TransStateAsString(s->state));
- /* Pre-commit processing goes here -- nothing to do at the moment */
+ /* Pre-commit processing goes here */
+ CallSubXactCallbacks(SUBXACT_EVENT_PRE_COMMIT_SUB, s->subTransactionId,
+ s->parent->subTransactionId);
+
+ /* Do the actual "commit", such as it is */
s->state = TRANS_COMMIT;
/* Must CCI to ensure commands of subtransaction are seen as done */
CommandCounterIncrement();
/*
- * Prior to 8.4 we marked subcommit in clog at this point. We now only
+ * Prior to 8.4 we marked subcommit in clog at this point. We now only
* perform that step, if required, as part of the atomic update of the
* whole transaction tree at top level commit or abort.
*/
/*
* The only lock we actually release here is the subtransaction XID lock.
- * The rest just get transferred to the parent resource owner.
*/
CurrentResourceOwner = s->curTransactionOwner;
if (TransactionIdIsValid(s->transactionId))
XactLockTableDelete(s->transactionId);
+ /*
+ * Other locks should get transferred to their parent resource owner.
+ */
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_LOCKS,
true, false);
AbortBufferIO();
UnlockBuffers();
- LockWaitCancel();
+ /*
+ * 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.
+ */
+ LockErrorCleanup();
+
+ /*
+ * If any timeout events are still active, make sure the timeout interrupt
+ * is scheduled. This covers possible loss of a timeout interrupt due to
+ * longjmp'ing out of the SIGINT handler (see notes in handle_sig_alarm).
+ * We delay this till after LockErrorCleanup so that we don't uselessly
+ * reschedule lock or deadlock check timeouts.
+ */
+ reschedule_timeouts();
+
+ /*
+ * Re-enable signals, in case we got here by longjmp'ing out of a signal
+ * handler. We do this fairly early in the sequence so that the timeout
+ * infrastructure will be functional if needed while aborting.
+ */
+ PG_SETMASK(&UnBlockSig);
/*
* check the current transaction state
* Reset user ID which might have been changed transiently. (See notes in
* AbortTransaction.)
*/
- SetUserIdAndContext(s->prevUser, s->prevSecDefCxt);
+ SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
/*
* We can skip all this stuff if the subxact failed before creating a
AtEOSubXact_RelationCache(false, s->subTransactionId,
s->parent->subTransactionId);
AtEOSubXact_Inval(false);
- AtSubAbort_smgr();
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_LOCKS,
false, false);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
false, false);
+ AtSubAbort_smgr();
AtEOXact_GUC(false, s->gucNestLevel);
AtEOSubXact_SPI(false, s->subTransactionId);
s->savepointLevel = p->savepointLevel;
s->state = TRANS_DEFAULT;
s->blockState = TBLOCK_SUBBEGIN;
- GetUserIdAndContext(&s->prevUser, &s->prevSecDefCxt);
+ GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
s->prevXactReadOnly = XactReadOnly;
CurrentTransactionState = s;
return "SUB BEGIN";
case TBLOCK_SUBINPROGRESS:
return "SUB INPROGRS";
- case TBLOCK_SUBEND:
- return "SUB END";
+ case TBLOCK_SUBRELEASE:
+ return "SUB RELEASE";
+ case TBLOCK_SUBCOMMIT:
+ return "SUB COMMIT";
case TBLOCK_SUBABORT:
return "SUB ABORT";
case TBLOCK_SUBABORT_END:
/*
* xactGetCommittedChildren
*
- * Gets the list of committed children of the current transaction. The return
+ * Gets the list of committed children of the current transaction. The return
* value is the number of child transactions. *ptr is set to point to an
* array of TransactionIds. The array is allocated in TopTransactionContext;
* the caller should *not* pfree() it (this is a change from pre-8.4 code!).
* XLOG support routines
*/
+/*
+ * Before 9.0 this was a fairly short function, but now it performs many
+ * actions for which the order of execution is critical.
+ */
static void
-xact_redo_commit(xl_xact_commit *xlrec, TransactionId xid)
+xact_redo_commit_internal(TransactionId xid, XLogRecPtr lsn,
+ TransactionId *sub_xids, int nsubxacts,
+ SharedInvalidationMessage *inval_msgs, int nmsgs,
+ RelFileNode *xnodes, int nrels,
+ Oid dbId, Oid tsId,
+ uint32 xinfo)
{
- TransactionId *sub_xids;
TransactionId max_xid;
int i;
- /* Mark the transaction committed in pg_clog */
- sub_xids = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
- TransactionIdCommitTree(xid, xlrec->nsubxacts, sub_xids);
+ max_xid = TransactionIdLatest(xid, nsubxacts, sub_xids);
- /* Make sure nextXid is beyond any XID mentioned in the record */
- max_xid = xid;
- for (i = 0; i < xlrec->nsubxacts; i++)
- {
- if (TransactionIdPrecedes(max_xid, sub_xids[i]))
- max_xid = sub_xids[i];
- }
+ /*
+ * Make sure nextXid is beyond any XID mentioned in the record.
+ *
+ * We don't expect anyone else to modify nextXid, hence we don't need to
+ * hold a lock while checking this. We still acquire the lock to modify
+ * it, though.
+ */
if (TransactionIdFollowsOrEquals(max_xid,
ShmemVariableCache->nextXid))
{
+ LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
ShmemVariableCache->nextXid = max_xid;
TransactionIdAdvance(ShmemVariableCache->nextXid);
+ LWLockRelease(XidGenLock);
+ }
+
+ if (standbyState == STANDBY_DISABLED)
+ {
+ /*
+ * Mark the transaction committed in pg_clog.
+ */
+ TransactionIdCommitTree(xid, nsubxacts, sub_xids);
+ }
+ else
+ {
+ /*
+ * If a transaction completion record arrives that has as-yet
+ * unobserved subtransactions then this will not have been fully
+ * handled by the call to RecordKnownAssignedTransactionIds() in the
+ * main recovery loop in xlog.c. So we need to do bookkeeping again to
+ * cover that case. This is confusing and it is easy to think this
+ * call is irrelevant, which has happened three times in development
+ * already. Leave it in.
+ */
+ RecordKnownAssignedTransactionIds(max_xid);
+
+ /*
+ * Mark the transaction committed in pg_clog. We use async commit
+ * protocol during recovery to provide information on database
+ * consistency for when users try to set hint bits. It is important
+ * that we do not set hint bits until the minRecoveryPoint is past
+ * this commit record. This ensures that if we crash we don't see hint
+ * bits set on changes made by transactions that haven't yet
+ * recovered. It's unlikely but it's good to be safe.
+ */
+ TransactionIdAsyncCommitTree(xid, nsubxacts, sub_xids, lsn);
+
+ /*
+ * We must mark clog before we update the ProcArray.
+ */
+ ExpireTreeKnownAssignedTransactionIds(xid, nsubxacts, sub_xids, max_xid);
+
+ /*
+ * Send any cache invalidations attached to the commit. We must
+ * maintain the same order of invalidation then release locks as
+ * occurs in CommitTransaction().
+ */
+ ProcessCommittedInvalidationMessages(inval_msgs, nmsgs,
+ XactCompletionRelcacheInitFileInval(xinfo),
+ dbId, tsId);
+
+ /*
+ * Release locks, if any. We do this for both two phase and normal one
+ * phase transactions. In effect we are ignoring the prepare phase and
+ * just going straight to lock release. At commit we release all locks
+ * via their top-level xid only, so no need to provide subxact list,
+ * which will save time when replaying commits.
+ */
+ StandbyReleaseLockTree(xid, 0, NULL);
}
/* Make sure files supposed to be dropped are dropped */
- for (i = 0; i < xlrec->nrels; i++)
+ if (nrels > 0)
{
- SMgrRelation srel = smgropen(xlrec->xnodes[i]);
- ForkNumber fork;
+ /*
+ * First update minimum recovery point to cover this WAL record. Once
+ * a relation is deleted, there's no going back. The buffer manager
+ * enforces the WAL-first rule for normal updates to relation files,
+ * so that the minimum recovery point is always updated before the
+ * corresponding change in the data file is flushed to disk, but we
+ * have to do the same here since we're bypassing the buffer manager.
+ *
+ * Doing this before deleting the files means that if a deletion fails
+ * for some reason, you cannot start up the system even after restart,
+ * until you fix the underlying situation so that the deletion will
+ * succeed. Alternatively, we could update the minimum recovery point
+ * after deletion, but that would leave a small window where the
+ * WAL-first rule would be violated.
+ */
+ XLogFlush(lsn);
- for (fork = 0; fork <= MAX_FORKNUM; fork++)
+ for (i = 0; i < nrels; i++)
{
- if (smgrexists(srel, fork))
- {
- XLogDropRelation(xlrec->xnodes[i], fork);
- smgrdounlink(srel, fork, false, true);
- }
+ SMgrRelation srel = smgropen(xnodes[i], InvalidBackendId);
+ ForkNumber fork;
+
+ for (fork = 0; fork <= MAX_FORKNUM; fork++)
+ XLogDropRelation(xnodes[i], fork);
+ smgrdounlink(srel, true);
+ smgrclose(srel);
}
- smgrclose(srel);
}
+
+ /*
+ * We issue an XLogFlush() for the same reason we emit ForceSyncCommit()
+ * in normal operation. For example, in CREATE DATABASE, we copy all files
+ * from the template database, and then commit the transaction. If we
+ * crash after all the files have been copied but before the commit, you
+ * have files in the data directory without an entry in pg_database. To
+ * minimize the window for that, we use ForceSyncCommit() to rush the
+ * commit record to disk as quick as possible. We have the same window
+ * during recovery, and forcing an XLogFlush() (which updates
+ * minRecoveryPoint during recovery) helps to reduce that problem window,
+ * for any user that requested ForceSyncCommit().
+ */
+ if (XactCompletionForceSyncCommit(xinfo))
+ XLogFlush(lsn);
+
}
+/*
+ * Utility function to call xact_redo_commit_internal after breaking down xlrec
+ */
+static void
+xact_redo_commit(xl_xact_commit *xlrec,
+ TransactionId xid, XLogRecPtr lsn)
+{
+ TransactionId *subxacts;
+ SharedInvalidationMessage *inval_msgs;
+
+ /* subxid array follows relfilenodes */
+ subxacts = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
+ /* invalidation messages array follows subxids */
+ inval_msgs = (SharedInvalidationMessage *) &(subxacts[xlrec->nsubxacts]);
+
+ xact_redo_commit_internal(xid, lsn, subxacts, xlrec->nsubxacts,
+ inval_msgs, xlrec->nmsgs,
+ xlrec->xnodes, xlrec->nrels,
+ xlrec->dbId,
+ xlrec->tsId,
+ xlrec->xinfo);
+}
+
+/*
+ * Utility function to call xact_redo_commit_internal for compact form of message.
+ */
+static void
+xact_redo_commit_compact(xl_xact_commit_compact *xlrec,
+ TransactionId xid, XLogRecPtr lsn)
+{
+ xact_redo_commit_internal(xid, lsn, xlrec->subxacts, xlrec->nsubxacts,
+ NULL, 0, /* inval msgs */
+ NULL, 0, /* relfilenodes */
+ InvalidOid, /* dbId */
+ InvalidOid, /* tsId */
+ 0); /* xinfo */
+}
+
+/*
+ * Be careful with the order of execution, as with xact_redo_commit().
+ * The two functions are similar but differ in key places.
+ *
+ * Note also that an abort can be for a subtransaction and its children,
+ * not just for a top level abort. That means we have to consider
+ * topxid != xid, whereas in commit we would find topxid == xid always
+ * because subtransaction commit is never WAL logged.
+ */
static void
xact_redo_abort(xl_xact_abort *xlrec, TransactionId xid)
{
TransactionId max_xid;
int i;
- /* Mark the transaction aborted in pg_clog */
sub_xids = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
- TransactionIdAbortTree(xid, xlrec->nsubxacts, sub_xids);
+ max_xid = TransactionIdLatest(xid, xlrec->nsubxacts, sub_xids);
- /* Make sure nextXid is beyond any XID mentioned in the record */
- max_xid = xid;
- for (i = 0; i < xlrec->nsubxacts; i++)
- {
- if (TransactionIdPrecedes(max_xid, sub_xids[i]))
- max_xid = sub_xids[i];
- }
+ /*
+ * Make sure nextXid is beyond any XID mentioned in the record.
+ *
+ * We don't expect anyone else to modify nextXid, hence we don't need to
+ * hold a lock while checking this. We still acquire the lock to modify
+ * it, though.
+ */
if (TransactionIdFollowsOrEquals(max_xid,
ShmemVariableCache->nextXid))
{
+ LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
ShmemVariableCache->nextXid = max_xid;
TransactionIdAdvance(ShmemVariableCache->nextXid);
+ LWLockRelease(XidGenLock);
+ }
+
+ if (standbyState == STANDBY_DISABLED)
+ {
+ /* Mark the transaction aborted in pg_clog, no need for async stuff */
+ TransactionIdAbortTree(xid, xlrec->nsubxacts, sub_xids);
+ }
+ else
+ {
+ /*
+ * If a transaction completion record arrives that has as-yet
+ * unobserved subtransactions then this will not have been fully
+ * handled by the call to RecordKnownAssignedTransactionIds() in the
+ * main recovery loop in xlog.c. So we need to do bookkeeping again to
+ * cover that case. This is confusing and it is easy to think this
+ * call is irrelevant, which has happened three times in development
+ * already. Leave it in.
+ */
+ RecordKnownAssignedTransactionIds(max_xid);
+
+ /* Mark the transaction aborted in pg_clog, no need for async stuff */
+ TransactionIdAbortTree(xid, xlrec->nsubxacts, sub_xids);
+
+ /*
+ * We must update the ProcArray after we have marked clog.
+ */
+ ExpireTreeKnownAssignedTransactionIds(xid, xlrec->nsubxacts, sub_xids, max_xid);
+
+ /*
+ * There are no flat files that need updating, nor invalidation
+ * messages to send or undo.
+ */
+
+ /*
+ * Release locks, if any. There are no invalidations to send.
+ */
+ StandbyReleaseLockTree(xid, xlrec->nsubxacts, sub_xids);
}
/* Make sure files supposed to be dropped are dropped */
for (i = 0; i < xlrec->nrels; i++)
{
- SMgrRelation srel = smgropen(xlrec->xnodes[i]);
+ SMgrRelation srel = smgropen(xlrec->xnodes[i], InvalidBackendId);
ForkNumber fork;
for (fork = 0; fork <= MAX_FORKNUM; fork++)
- {
- if (smgrexists(srel, fork))
- {
- XLogDropRelation(xlrec->xnodes[i], fork);
- smgrdounlink(srel, fork, false, true);
- }
- }
+ XLogDropRelation(xlrec->xnodes[i], fork);
+ smgrdounlink(srel, true);
smgrclose(srel);
}
}
/* Backup blocks are not used in xact records */
Assert(!(record->xl_info & XLR_BKP_BLOCK_MASK));
- if (info == XLOG_XACT_COMMIT)
+ if (info == XLOG_XACT_COMMIT_COMPACT)
+ {
+ xl_xact_commit_compact *xlrec = (xl_xact_commit_compact *) XLogRecGetData(record);
+
+ xact_redo_commit_compact(xlrec, record->xl_xid, lsn);
+ }
+ else if (info == XLOG_XACT_COMMIT)
{
xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record);
- xact_redo_commit(xlrec, record->xl_xid);
+ xact_redo_commit(xlrec, record->xl_xid, lsn);
}
else if (info == XLOG_XACT_ABORT)
{
{
xl_xact_commit_prepared *xlrec = (xl_xact_commit_prepared *) XLogRecGetData(record);
- xact_redo_commit(&xlrec->crec, xlrec->xid);
+ xact_redo_commit(&xlrec->crec, xlrec->xid, lsn);
RemoveTwoPhaseFile(xlrec->xid, false);
}
else if (info == XLOG_XACT_ABORT_PREPARED)
xact_redo_abort(&xlrec->arec, xlrec->xid);
RemoveTwoPhaseFile(xlrec->xid, false);
}
- else
- elog(PANIC, "xact_redo: unknown op code %u", info);
-}
-
-static void
-xact_desc_commit(StringInfo buf, xl_xact_commit *xlrec)
-{
- int i;
-
- appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
- if (xlrec->nrels > 0)
- {
- appendStringInfo(buf, "; rels:");
- for (i = 0; i < xlrec->nrels; i++)
- {
- char *path = relpath(xlrec->xnodes[i], MAIN_FORKNUM);
-
- appendStringInfo(buf, " %s", path);
- pfree(path);
- }
- }
- if (xlrec->nsubxacts > 0)
- {
- TransactionId *xacts = (TransactionId *)
- &xlrec->xnodes[xlrec->nrels];
-
- appendStringInfo(buf, "; subxacts:");
- for (i = 0; i < xlrec->nsubxacts; i++)
- appendStringInfo(buf, " %u", xacts[i]);
- }
-}
-
-static void
-xact_desc_abort(StringInfo buf, xl_xact_abort *xlrec)
-{
- int i;
-
- appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
- if (xlrec->nrels > 0)
- {
- appendStringInfo(buf, "; rels:");
- for (i = 0; i < xlrec->nrels; i++)
- {
- char *path = relpath(xlrec->xnodes[i], MAIN_FORKNUM);
-
- appendStringInfo(buf, " %s", path);
- pfree(path);
- }
- }
- if (xlrec->nsubxacts > 0)
+ else if (info == XLOG_XACT_ASSIGNMENT)
{
- TransactionId *xacts = (TransactionId *)
- &xlrec->xnodes[xlrec->nrels];
+ xl_xact_assignment *xlrec = (xl_xact_assignment *) XLogRecGetData(record);
- appendStringInfo(buf, "; subxacts:");
- for (i = 0; i < xlrec->nsubxacts; i++)
- appendStringInfo(buf, " %u", xacts[i]);
- }
-}
-
-void
-xact_desc(StringInfo buf, uint8 xl_info, char *rec)
-{
- uint8 info = xl_info & ~XLR_INFO_MASK;
-
- if (info == XLOG_XACT_COMMIT)
- {
- xl_xact_commit *xlrec = (xl_xact_commit *) rec;
-
- appendStringInfo(buf, "commit: ");
- xact_desc_commit(buf, xlrec);
- }
- else if (info == XLOG_XACT_ABORT)
- {
- xl_xact_abort *xlrec = (xl_xact_abort *) rec;
-
- appendStringInfo(buf, "abort: ");
- xact_desc_abort(buf, xlrec);
- }
- else if (info == XLOG_XACT_PREPARE)
- {
- appendStringInfo(buf, "prepare");
- }
- else if (info == XLOG_XACT_COMMIT_PREPARED)
- {
- xl_xact_commit_prepared *xlrec = (xl_xact_commit_prepared *) rec;
-
- appendStringInfo(buf, "commit %u: ", xlrec->xid);
- xact_desc_commit(buf, &xlrec->crec);
- }
- else if (info == XLOG_XACT_ABORT_PREPARED)
- {
- xl_xact_abort_prepared *xlrec = (xl_xact_abort_prepared *) rec;
-
- appendStringInfo(buf, "abort %u: ", xlrec->xid);
- xact_desc_abort(buf, &xlrec->arec);
+ if (standbyState >= STANDBY_INITIALIZED)
+ ProcArrayApplyXidAssignment(xlrec->xtop,
+ xlrec->nsubxacts, xlrec->xsub);
}
else
- appendStringInfo(buf, "UNKNOWN");
+ elog(PANIC, "xact_redo: unknown op code %u", info);
}