mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
The sole useful effect of this function, to check that no catcache entries have positive refcounts at transaction end, has really been obsolete since we introduced ResourceOwners in PG 8.1. We reduced the checks to assertions years ago, so that the function was a complete no-op in production builds. There have been previous discussions about removing it entirely, but consensus up to now was that it had some small value as a cross-check for bugs in the ResourceOwner logic. However, it now emerges that it's possible to trigger these assertions if you hit an assert-enabled backend with SIGTERM during a call to SearchCatCacheList, because that function temporarily increases the refcounts of entries it's intending to add to a catcache list construct. In a normal ERROR scenario, the extra refcounts are cleaned up by SearchCatCacheList's PG_CATCH block; but in a FATAL exit we do a transaction abort and exit without ever executing PG_CATCH handlers. There's a case to be made that this is a generic hazard and we should consider restructuring elog(FATAL) handling so that pending PG_CATCH handlers do get run. That's pretty scary though: it could easily create more problems than it solves. Preliminary stress testing by Andreas Seltenreich suggests that there are not many live problems of this ilk, so we rejected that idea. There are more-localized ways to fix the problem; the most principled one would be to use PG_ENSURE_ERROR_CLEANUP instead of plain PG_TRY. But adding cycles to SearchCatCacheList isn't very appealing. We could also weaken the assertions in AtEOXact_CatCache in some more or less ad-hoc way, but that just makes its raison d'etre even less compelling. In the end, the most reasonable solution seems to be to just remove AtEOXact_CatCache altogether, on the grounds that it's not worth trying to fix it. It hasn't found any bugs for us in many years. Per report from Jeevan Chalke. Back-patch to all supported branches. Discussion: https://postgr.es/m/CAM2+6=VEE30YtRQCZX7_sCFsEpoUkFBV1gZazL70fqLn8rcvBA@mail.gmail.com
5673 lines
158 KiB
C
5673 lines
158 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* xact.c
|
|
* top level transaction system support routines
|
|
*
|
|
* See src/backend/access/transam/README for more information.
|
|
*
|
|
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/access/transam/xact.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "access/commit_ts.h"
|
|
#include "access/multixact.h"
|
|
#include "access/parallel.h"
|
|
#include "access/subtrans.h"
|
|
#include "access/transam.h"
|
|
#include "access/twophase.h"
|
|
#include "access/xact.h"
|
|
#include "access/xlog.h"
|
|
#include "access/xloginsert.h"
|
|
#include "access/xlogutils.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/storage.h"
|
|
#include "commands/async.h"
|
|
#include "commands/tablecmds.h"
|
|
#include "commands/trigger.h"
|
|
#include "executor/spi.h"
|
|
#include "libpq/be-fsstubs.h"
|
|
#include "libpq/pqsignal.h"
|
|
#include "miscadmin.h"
|
|
#include "pgstat.h"
|
|
#include "replication/logical.h"
|
|
#include "replication/logicallauncher.h"
|
|
#include "replication/origin.h"
|
|
#include "replication/syncrep.h"
|
|
#include "replication/walsender.h"
|
|
#include "storage/condition_variable.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/builtins.h"
|
|
#include "utils/catcache.h"
|
|
#include "utils/combocid.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/inval.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/relmapper.h"
|
|
#include "utils/snapmgr.h"
|
|
#include "utils/timeout.h"
|
|
#include "utils/timestamp.h"
|
|
#include "pg_trace.h"
|
|
|
|
|
|
/*
|
|
* User-tweakable parameters
|
|
*/
|
|
int DefaultXactIsoLevel = XACT_READ_COMMITTED;
|
|
int XactIsoLevel;
|
|
|
|
bool DefaultXactReadOnly = false;
|
|
bool XactReadOnly;
|
|
|
|
bool DefaultXactDeferrable = false;
|
|
bool XactDeferrable;
|
|
|
|
int synchronous_commit = SYNCHRONOUS_COMMIT_ON;
|
|
|
|
/*
|
|
* When running as a parallel worker, we place only a single
|
|
* TransactionStateData on the parallel worker's state stack, and the XID
|
|
* reflected there will be that of the *innermost* currently-active
|
|
* subtransaction in the backend that initiated parallelism. However,
|
|
* GetTopTransactionId() and TransactionIdIsCurrentTransactionId()
|
|
* need to return the same answers in the parallel worker as they would have
|
|
* in the user backend, so we need some additional bookkeeping.
|
|
*
|
|
* XactTopTransactionId stores the XID of our toplevel transaction, which
|
|
* will be the same as TopTransactionState.transactionId in an ordinary
|
|
* backend; but in a parallel backend, which does not have the entire
|
|
* transaction state, it will instead be copied from the backend that started
|
|
* the parallel operation.
|
|
*
|
|
* nParallelCurrentXids will be 0 and ParallelCurrentXids NULL in an ordinary
|
|
* backend, but in a parallel backend, nParallelCurrentXids will contain the
|
|
* number of XIDs that need to be considered current, and ParallelCurrentXids
|
|
* will contain the XIDs themselves. This includes all XIDs that were current
|
|
* or sub-committed in the parent at the time the parallel operation began.
|
|
* The XIDs are stored sorted in numerical order (not logical order) to make
|
|
* lookups as fast as possible.
|
|
*/
|
|
TransactionId XactTopTransactionId = InvalidTransactionId;
|
|
int nParallelCurrentXids = 0;
|
|
TransactionId *ParallelCurrentXids;
|
|
|
|
/*
|
|
* Miscellaneous flag bits to record events which occur on the top level
|
|
* transaction. These flags are only persisted in MyXactFlags and are intended
|
|
* so we remember to do certain things later on in the transaction. This is
|
|
* globally accessible, so can be set from anywhere in the code that requires
|
|
* recording flags.
|
|
*/
|
|
int MyXactFlags;
|
|
|
|
/*
|
|
* transaction states - transaction state from server perspective
|
|
*/
|
|
typedef enum TransState
|
|
{
|
|
TRANS_DEFAULT, /* idle */
|
|
TRANS_START, /* transaction starting */
|
|
TRANS_INPROGRESS, /* inside a valid transaction */
|
|
TRANS_COMMIT, /* commit in progress */
|
|
TRANS_ABORT, /* abort in progress */
|
|
TRANS_PREPARE /* prepare in progress */
|
|
} TransState;
|
|
|
|
/*
|
|
* transaction block states - transaction state of client queries
|
|
*
|
|
* Note: the subtransaction states are used only for non-topmost
|
|
* transactions; the others appear only in the topmost transaction.
|
|
*/
|
|
typedef enum TBlockState
|
|
{
|
|
/* not-in-transaction-block states */
|
|
TBLOCK_DEFAULT, /* idle */
|
|
TBLOCK_STARTED, /* running single-query transaction */
|
|
|
|
/* transaction block states */
|
|
TBLOCK_BEGIN, /* starting transaction block */
|
|
TBLOCK_INPROGRESS, /* live transaction */
|
|
TBLOCK_PARALLEL_INPROGRESS, /* live transaction inside parallel worker */
|
|
TBLOCK_END, /* COMMIT received */
|
|
TBLOCK_ABORT, /* failed xact, awaiting ROLLBACK */
|
|
TBLOCK_ABORT_END, /* failed xact, ROLLBACK received */
|
|
TBLOCK_ABORT_PENDING, /* live xact, ROLLBACK received */
|
|
TBLOCK_PREPARE, /* live xact, PREPARE received */
|
|
|
|
/* subtransaction states */
|
|
TBLOCK_SUBBEGIN, /* starting a subtransaction */
|
|
TBLOCK_SUBINPROGRESS, /* live subtransaction */
|
|
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 */
|
|
TBLOCK_SUBRESTART, /* live subxact, ROLLBACK TO received */
|
|
TBLOCK_SUBABORT_RESTART /* failed subxact, ROLLBACK TO received */
|
|
} TBlockState;
|
|
|
|
/*
|
|
* transaction state structure
|
|
*/
|
|
typedef struct TransactionStateData
|
|
{
|
|
TransactionId transactionId; /* my XID, or Invalid if none */
|
|
SubTransactionId subTransactionId; /* my subxact ID */
|
|
char *name; /* savepoint name, if any */
|
|
int savepointLevel; /* savepoint level */
|
|
TransState state; /* low-level state */
|
|
TBlockState blockState; /* high-level state */
|
|
int nestingLevel; /* transaction nesting depth */
|
|
int gucNestLevel; /* GUC context nesting depth */
|
|
MemoryContext curTransactionContext; /* my xact-lifetime context */
|
|
ResourceOwner curTransactionOwner; /* my query resources */
|
|
TransactionId *childXids; /* subcommitted child XIDs, in XID order */
|
|
int nChildXids; /* # of subcommitted child XIDs */
|
|
int maxChildXids; /* allocated size of childXids[] */
|
|
Oid prevUser; /* previous CurrentUserId 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? */
|
|
int parallelModeLevel; /* Enter/ExitParallelMode counter */
|
|
struct TransactionStateData *parent; /* back link to parent */
|
|
} TransactionStateData;
|
|
|
|
typedef TransactionStateData *TransactionState;
|
|
|
|
/*
|
|
* 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 */
|
|
0, /* subtransaction id */
|
|
NULL, /* savepoint name */
|
|
0, /* savepoint level */
|
|
TRANS_DEFAULT, /* transaction state */
|
|
TBLOCK_DEFAULT, /* transaction block state from the client
|
|
* perspective */
|
|
0, /* transaction nesting depth */
|
|
0, /* GUC context nesting depth */
|
|
NULL, /* cur transaction context */
|
|
NULL, /* cur transaction resource owner */
|
|
NULL, /* subcommitted child Xids */
|
|
0, /* # of subcommitted child Xids */
|
|
0, /* allocated size of childXids[] */
|
|
InvalidOid, /* previous CurrentUserId setting */
|
|
0, /* previous SecurityRestrictionContext */
|
|
false, /* entry-time xact r/o state */
|
|
false, /* startedInRecovery */
|
|
false, /* didLogXid */
|
|
0, /* parallelMode */
|
|
NULL /* link to parent state block */
|
|
};
|
|
|
|
/*
|
|
* unreportedXids holds XIDs of all subtransactions that have not yet been
|
|
* reported in an XLOG_XACT_ASSIGNMENT record.
|
|
*/
|
|
static int nUnreportedXids;
|
|
static TransactionId unreportedXids[PGPROC_MAX_CACHED_SUBXIDS];
|
|
|
|
static TransactionState CurrentTransactionState = &TopTransactionStateData;
|
|
|
|
/*
|
|
* The subtransaction ID and command ID assignment counters are global
|
|
* to a whole transaction, so we do not keep them in the state stack.
|
|
*/
|
|
static SubTransactionId currentSubTransactionId;
|
|
static CommandId currentCommandId;
|
|
static bool currentCommandIdUsed;
|
|
|
|
/*
|
|
* xactStartTimestamp is the value of transaction_timestamp().
|
|
* stmtStartTimestamp is the value of statement_timestamp().
|
|
* xactStopTimestamp is the time at which we log a commit or abort WAL record.
|
|
* These do not change as we enter and exit subtransactions, so we don't
|
|
* keep them inside the TransactionState stack.
|
|
*/
|
|
static TimestampTz xactStartTimestamp;
|
|
static TimestampTz stmtStartTimestamp;
|
|
static TimestampTz xactStopTimestamp;
|
|
|
|
/*
|
|
* GID to be used for preparing the current transaction. This is also
|
|
* global to a whole transaction, so we don't keep it in the state stack.
|
|
*/
|
|
static char *prepareGID;
|
|
|
|
/*
|
|
* Some commands want to force synchronous commit.
|
|
*/
|
|
static bool forceSyncCommit = false;
|
|
|
|
/*
|
|
* Private context for transaction-abort work --- we reserve space for this
|
|
* at startup to ensure that AbortTransaction and AbortSubTransaction can work
|
|
* when we've run out of memory.
|
|
*/
|
|
static MemoryContext TransactionAbortContext = NULL;
|
|
|
|
/*
|
|
* List of add-on start- and end-of-xact callbacks
|
|
*/
|
|
typedef struct XactCallbackItem
|
|
{
|
|
struct XactCallbackItem *next;
|
|
XactCallback callback;
|
|
void *arg;
|
|
} XactCallbackItem;
|
|
|
|
static XactCallbackItem *Xact_callbacks = NULL;
|
|
|
|
/*
|
|
* List of add-on start- and end-of-subxact callbacks
|
|
*/
|
|
typedef struct SubXactCallbackItem
|
|
{
|
|
struct SubXactCallbackItem *next;
|
|
SubXactCallback callback;
|
|
void *arg;
|
|
} SubXactCallbackItem;
|
|
|
|
static SubXactCallbackItem *SubXact_callbacks = NULL;
|
|
|
|
|
|
/* local function prototypes */
|
|
static void AssignTransactionId(TransactionState s);
|
|
static void AbortTransaction(void);
|
|
static void AtAbort_Memory(void);
|
|
static void AtCleanup_Memory(void);
|
|
static void AtAbort_ResourceOwner(void);
|
|
static void AtCCI_LocalCache(void);
|
|
static void AtCommit_Memory(void);
|
|
static void AtStart_Cache(void);
|
|
static void AtStart_Memory(void);
|
|
static void AtStart_ResourceOwner(void);
|
|
static void CallXactCallbacks(XactEvent event);
|
|
static void CallSubXactCallbacks(SubXactEvent event,
|
|
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);
|
|
|
|
static void StartSubTransaction(void);
|
|
static void CommitSubTransaction(void);
|
|
static void AbortSubTransaction(void);
|
|
static void CleanupSubTransaction(void);
|
|
static void PushTransaction(void);
|
|
static void PopTransaction(void);
|
|
|
|
static void AtSubAbort_Memory(void);
|
|
static void AtSubCleanup_Memory(void);
|
|
static void AtSubAbort_ResourceOwner(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(const char *str, TransactionState state);
|
|
static const char *BlockStateAsString(TBlockState blockState);
|
|
static const char *TransStateAsString(TransState state);
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* transaction state accessors
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* IsTransactionState
|
|
*
|
|
* This returns true if we are inside a valid transaction; that is,
|
|
* it is safe to initiate database access, take heavyweight locks, etc.
|
|
*/
|
|
bool
|
|
IsTransactionState(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* TRANS_DEFAULT and TRANS_ABORT are obviously unsafe states. However, we
|
|
* also reject the startup/shutdown states TRANS_START, TRANS_COMMIT,
|
|
* TRANS_PREPARE since it might be too soon or too late within those
|
|
* transition states to do anything interesting. Hence, the only "valid"
|
|
* state is TRANS_INPROGRESS.
|
|
*/
|
|
return (s->state == TRANS_INPROGRESS);
|
|
}
|
|
|
|
/*
|
|
* IsAbortedTransactionBlockState
|
|
*
|
|
* This returns true if we are 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
|
|
*
|
|
* This will return the XID of the main transaction, assigning one if
|
|
* it's not yet set. Be careful to call this only inside a valid xact.
|
|
*/
|
|
TransactionId
|
|
GetTopTransactionId(void)
|
|
{
|
|
if (!TransactionIdIsValid(XactTopTransactionId))
|
|
AssignTransactionId(&TopTransactionStateData);
|
|
return XactTopTransactionId;
|
|
}
|
|
|
|
/*
|
|
* GetTopTransactionIdIfAny
|
|
*
|
|
* This will return the XID of the main transaction, if one is assigned.
|
|
* It will return InvalidTransactionId if we are not currently inside a
|
|
* transaction, or inside a transaction that hasn't yet been assigned an XID.
|
|
*/
|
|
TransactionId
|
|
GetTopTransactionIdIfAny(void)
|
|
{
|
|
return XactTopTransactionId;
|
|
}
|
|
|
|
/*
|
|
* GetCurrentTransactionId
|
|
*
|
|
* This will return the XID of the current transaction (main or sub
|
|
* transaction), assigning one if it's not yet set. Be careful to call this
|
|
* only inside a valid xact.
|
|
*/
|
|
TransactionId
|
|
GetCurrentTransactionId(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (!TransactionIdIsValid(s->transactionId))
|
|
AssignTransactionId(s);
|
|
return s->transactionId;
|
|
}
|
|
|
|
/*
|
|
* GetCurrentTransactionIdIfAny
|
|
*
|
|
* This will return the XID of the current sub xact, if one is assigned.
|
|
* It will return InvalidTransactionId if we are not currently inside a
|
|
* transaction, or inside a transaction that hasn't been assigned an XID yet.
|
|
*/
|
|
TransactionId
|
|
GetCurrentTransactionIdIfAny(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
|
|
*
|
|
* Assigns a new permanent XID to the given TransactionState.
|
|
* We do not assign XIDs to transactions until/unless this is called.
|
|
* Also, any parent TransactionStates that don't yet have XIDs are assigned
|
|
* one; this maintains the invariant that a child transaction has an XID
|
|
* following its parent's.
|
|
*/
|
|
static void
|
|
AssignTransactionId(TransactionState s)
|
|
{
|
|
bool isSubXact = (s->parent != NULL);
|
|
ResourceOwner currentOwner;
|
|
bool log_unknown_top = false;
|
|
|
|
/* Assert that caller didn't screw up */
|
|
Assert(!TransactionIdIsValid(s->transactionId));
|
|
Assert(s->state == TRANS_INPROGRESS);
|
|
|
|
/*
|
|
* Workers synchronize transaction state at the beginning of each parallel
|
|
* operation, so we can't account for new XIDs at this point.
|
|
*/
|
|
if (IsInParallelMode() || IsParallelWorker())
|
|
elog(ERROR, "cannot assign XIDs during a parallel operation");
|
|
|
|
/*
|
|
* Ensure parent(s) have XIDs, so that a child always has an XID later
|
|
* 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))
|
|
{
|
|
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 an 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.
|
|
*
|
|
* NB: we must make the subtrans entry BEFORE the Xid appears anywhere in
|
|
* shared storage other than PG_PROC; because if there's no room for it in
|
|
* PG_PROC, the subtrans entry is needed to ensure that other backends see
|
|
* the Xid as "running". See GetNewTransactionId.
|
|
*/
|
|
s->transactionId = GetNewTransactionId(isSubXact);
|
|
if (!isSubXact)
|
|
XactTopTransactionId = s->transactionId;
|
|
|
|
if (isSubXact)
|
|
SubTransSetParent(s->transactionId, s->parent->transactionId);
|
|
|
|
/*
|
|
* 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
|
|
* have to ensure that the lock is assigned to the transaction's own
|
|
* ResourceOwner.
|
|
*/
|
|
currentOwner = CurrentResourceOwner;
|
|
PG_TRY();
|
|
{
|
|
CurrentResourceOwner = s->curTransactionOwner;
|
|
XactLockTableInsert(s->transactionId);
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
/* Ensure CurrentResourceOwner is restored on error */
|
|
CurrentResourceOwner = currentOwner;
|
|
PG_RE_THROW();
|
|
}
|
|
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)
|
|
{
|
|
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;
|
|
|
|
XLogBeginInsert();
|
|
XLogRegisterData((char *) &xlrec, MinSizeOfXactAssignment);
|
|
XLogRegisterData((char *) unreportedXids,
|
|
nUnreportedXids * sizeof(TransactionId));
|
|
|
|
(void) XLogInsert(RM_XACT_ID, XLOG_XACT_ASSIGNMENT);
|
|
|
|
nUnreportedXids = 0;
|
|
/* mark top, not current xact as having been logged */
|
|
TopTransactionStateData.didLogXid = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* GetCurrentSubTransactionId
|
|
*/
|
|
SubTransactionId
|
|
GetCurrentSubTransactionId(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
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
|
|
* CommandCounterIncrement() for discussion.
|
|
*/
|
|
CommandId
|
|
GetCurrentCommandId(bool used)
|
|
{
|
|
/* this is global to a transaction, not subtransaction-local */
|
|
if (used)
|
|
{
|
|
/*
|
|
* Forbid setting currentCommandIdUsed in parallel mode, because we
|
|
* have no provision for communicating this back to the master. We
|
|
* could relax this restriction when currentCommandIdUsed was already
|
|
* true at the start of the parallel operation.
|
|
*/
|
|
Assert(CurrentTransactionState->parallelModeLevel == 0);
|
|
currentCommandIdUsed = true;
|
|
}
|
|
return currentCommandId;
|
|
}
|
|
|
|
/*
|
|
* GetCurrentTransactionStartTimestamp
|
|
*/
|
|
TimestampTz
|
|
GetCurrentTransactionStartTimestamp(void)
|
|
{
|
|
return xactStartTimestamp;
|
|
}
|
|
|
|
/*
|
|
* GetCurrentStatementStartTimestamp
|
|
*/
|
|
TimestampTz
|
|
GetCurrentStatementStartTimestamp(void)
|
|
{
|
|
return stmtStartTimestamp;
|
|
}
|
|
|
|
/*
|
|
* GetCurrentTransactionStopTimestamp
|
|
*
|
|
* We return current time if the transaction stop time hasn't been set
|
|
* (which can happen if we decide we don't need to log an XLOG record).
|
|
*/
|
|
TimestampTz
|
|
GetCurrentTransactionStopTimestamp(void)
|
|
{
|
|
if (xactStopTimestamp != 0)
|
|
return xactStopTimestamp;
|
|
return GetCurrentTimestamp();
|
|
}
|
|
|
|
/*
|
|
* SetCurrentStatementStartTimestamp
|
|
*/
|
|
void
|
|
SetCurrentStatementStartTimestamp(void)
|
|
{
|
|
stmtStartTimestamp = GetCurrentTimestamp();
|
|
}
|
|
|
|
/*
|
|
* SetCurrentTransactionStopTimestamp
|
|
*/
|
|
static inline void
|
|
SetCurrentTransactionStopTimestamp(void)
|
|
{
|
|
xactStopTimestamp = GetCurrentTimestamp();
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
|
|
/*
|
|
* TransactionIdIsCurrentTransactionId
|
|
*/
|
|
bool
|
|
TransactionIdIsCurrentTransactionId(TransactionId xid)
|
|
{
|
|
TransactionState s;
|
|
|
|
/*
|
|
* We always say that BootstrapTransactionId is "not my transaction ID"
|
|
* 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,
|
|
* it never updates or deletes them, so all tuples can be presumed good
|
|
* immediately.)
|
|
*
|
|
* Likewise, InvalidTransactionId and FrozenTransactionId are certainly
|
|
* not my transaction ID, so we can just return "false" immediately for
|
|
* any non-normal XID.
|
|
*/
|
|
if (!TransactionIdIsNormal(xid))
|
|
return false;
|
|
|
|
/*
|
|
* In parallel workers, the XIDs we must consider as current are stored in
|
|
* ParallelCurrentXids rather than the transaction-state stack. Note that
|
|
* the XIDs in this array are sorted numerically rather than according to
|
|
* transactionIdPrecedes order.
|
|
*/
|
|
if (nParallelCurrentXids > 0)
|
|
{
|
|
int low,
|
|
high;
|
|
|
|
low = 0;
|
|
high = nParallelCurrentXids - 1;
|
|
while (low <= high)
|
|
{
|
|
int middle;
|
|
TransactionId probe;
|
|
|
|
middle = low + (high - low) / 2;
|
|
probe = ParallelCurrentXids[middle];
|
|
if (probe == xid)
|
|
return true;
|
|
else if (probe < xid)
|
|
low = middle + 1;
|
|
else
|
|
high = middle - 1;
|
|
}
|
|
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. However, a transaction being aborted
|
|
* is no longer "current", even though it may still have an entry on the
|
|
* state stack.
|
|
*/
|
|
for (s = CurrentTransactionState; s != NULL; s = s->parent)
|
|
{
|
|
int low,
|
|
high;
|
|
|
|
if (s->state == TRANS_ABORT)
|
|
continue;
|
|
if (!TransactionIdIsValid(s->transactionId))
|
|
continue; /* it can't have any child XIDs either */
|
|
if (TransactionIdEquals(xid, s->transactionId))
|
|
return true;
|
|
/* As the childXids array is ordered, we can use binary search */
|
|
low = 0;
|
|
high = s->nChildXids - 1;
|
|
while (low <= high)
|
|
{
|
|
int middle;
|
|
TransactionId probe;
|
|
|
|
middle = low + (high - low) / 2;
|
|
probe = s->childXids[middle];
|
|
if (TransactionIdEquals(probe, xid))
|
|
return true;
|
|
else if (TransactionIdPrecedes(probe, xid))
|
|
low = middle + 1;
|
|
else
|
|
high = middle - 1;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* EnterParallelMode
|
|
*/
|
|
void
|
|
EnterParallelMode(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
Assert(s->parallelModeLevel >= 0);
|
|
|
|
++s->parallelModeLevel;
|
|
}
|
|
|
|
/*
|
|
* ExitParallelMode
|
|
*/
|
|
void
|
|
ExitParallelMode(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
Assert(s->parallelModeLevel > 0);
|
|
Assert(s->parallelModeLevel > 1 || !ParallelContextActive());
|
|
|
|
--s->parallelModeLevel;
|
|
}
|
|
|
|
/*
|
|
* IsInParallelMode
|
|
*
|
|
* Are we in a parallel operation, as either the master or a worker? Check
|
|
* this to prohibit operations that change backend-local state expected to
|
|
* match across all workers. Mere caches usually don't require such a
|
|
* restriction. State modified in a strict push/pop fashion, such as the
|
|
* active snapshot stack, is often fine.
|
|
*/
|
|
bool
|
|
IsInParallelMode(void)
|
|
{
|
|
return CurrentTransactionState->parallelModeLevel != 0;
|
|
}
|
|
|
|
/*
|
|
* CommandCounterIncrement
|
|
*/
|
|
void
|
|
CommandCounterIncrement(void)
|
|
{
|
|
/*
|
|
* If the current value of the command counter hasn't been "used" to mark
|
|
* tuples, we need not increment it, since there's no need to distinguish
|
|
* a read-only command from others. This helps postpone command counter
|
|
* overflow, and keeps no-op CommandCounterIncrement operations cheap.
|
|
*/
|
|
if (currentCommandIdUsed)
|
|
{
|
|
/*
|
|
* Workers synchronize transaction state at the beginning of each
|
|
* parallel operation, so we can't account for new commands after that
|
|
* point.
|
|
*/
|
|
if (IsInParallelMode() || IsParallelWorker())
|
|
elog(ERROR, "cannot start commands during a parallel operation");
|
|
|
|
currentCommandId += 1;
|
|
if (currentCommandId == InvalidCommandId)
|
|
{
|
|
currentCommandId -= 1;
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
errmsg("cannot have more than 2^32-2 commands in a transaction")));
|
|
}
|
|
currentCommandIdUsed = false;
|
|
|
|
/* Propagate new command ID into static snapshots */
|
|
SnapshotSetCommandId(currentCommandId);
|
|
|
|
/*
|
|
* Make any catalog changes done by the just-completed command visible
|
|
* in the local syscache. We obviously don't need to do this after a
|
|
* 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.)
|
|
*/
|
|
AtCCI_LocalCache();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ForceSyncCommit
|
|
*
|
|
* Interface routine to allow commands to force a synchronous commit of the
|
|
* current top-level transaction
|
|
*/
|
|
void
|
|
ForceSyncCommit(void)
|
|
{
|
|
forceSyncCommit = true;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* StartTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* AtStart_Cache
|
|
*/
|
|
static void
|
|
AtStart_Cache(void)
|
|
{
|
|
AcceptInvalidationMessages();
|
|
}
|
|
|
|
/*
|
|
* AtStart_Memory
|
|
*/
|
|
static void
|
|
AtStart_Memory(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* 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
|
|
* ErrorContext, we set it up with slow growth rate and a nonzero minimum
|
|
* size, so that space will be reserved immediately.
|
|
*/
|
|
if (TransactionAbortContext == NULL)
|
|
TransactionAbortContext =
|
|
AllocSetContextCreate(TopMemoryContext,
|
|
"TransactionAbortContext",
|
|
32 * 1024,
|
|
32 * 1024,
|
|
32 * 1024);
|
|
|
|
/*
|
|
* We shouldn't have a transaction context already.
|
|
*/
|
|
Assert(TopTransactionContext == NULL);
|
|
|
|
/*
|
|
* Create a toplevel context for the transaction.
|
|
*/
|
|
TopTransactionContext =
|
|
AllocSetContextCreate(TopMemoryContext,
|
|
"TopTransactionContext",
|
|
ALLOCSET_DEFAULT_SIZES);
|
|
|
|
/*
|
|
* 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_SIZES);
|
|
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
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* 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.)
|
|
*
|
|
* If you change this function, see RecordTransactionCommitPrepared also.
|
|
*/
|
|
static TransactionId
|
|
RecordTransactionCommit(void)
|
|
{
|
|
TransactionId xid = GetTopTransactionIdIfAny();
|
|
bool markXidCommitted = TransactionIdIsValid(xid);
|
|
TransactionId latestXid = InvalidTransactionId;
|
|
int nrels;
|
|
RelFileNode *rels;
|
|
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);
|
|
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
|
|
* to write a COMMIT record.
|
|
*/
|
|
if (!markXidCommitted)
|
|
{
|
|
/*
|
|
* We expect that every smgrscheduleunlink is followed by a catalog
|
|
* update, and hence XID assignment, so we shouldn't get here with any
|
|
* pending deletes. Use a real test not just an Assert to check this,
|
|
* since it's a bit fragile.
|
|
*/
|
|
if (nrels != 0)
|
|
elog(ERROR, "cannot commit a transaction that deleted files but has no xid");
|
|
|
|
/* Can't have child XIDs either; AssignTransactionId enforces this */
|
|
Assert(nchildren == 0);
|
|
|
|
/*
|
|
* Transactions without an assigned xid can contain invalidation
|
|
* messages (e.g. explicit relcache invalidations or catcache
|
|
* invalidations for inplace updates); standbys need to process those.
|
|
* We can't emit a commit record without an xid, and we don't want to
|
|
* force assigning an xid, because that'd be problematic for e.g.
|
|
* vacuum. Hence we emit a bespoke record for the invalidations. We
|
|
* don't want to use that in case a commit record is emitted, so they
|
|
* happen synchronously with commits (besides not wanting to emit more
|
|
* WAL recoreds).
|
|
*/
|
|
if (nmsgs != 0)
|
|
{
|
|
LogStandbyInvalidations(nmsgs, invalMessages,
|
|
RelcacheInitFileInval);
|
|
wrote_xlog = true; /* not strictly necessary */
|
|
}
|
|
|
|
/*
|
|
* If we didn't create XLOG entries, we're done here; otherwise we
|
|
* should trigger flushing those entries the same as a commit record
|
|
* would. This will primarily happen for HOT pruning and the like; we
|
|
* want these to be flushed to disk in due time.
|
|
*/
|
|
if (!wrote_xlog)
|
|
goto cleanup;
|
|
}
|
|
else
|
|
{
|
|
bool replorigin;
|
|
|
|
/*
|
|
* Are we using the replication origins feature? Or, in other words,
|
|
* are we replaying remote actions?
|
|
*/
|
|
replorigin = (replorigin_session_origin != InvalidRepOriginId &&
|
|
replorigin_session_origin != DoNotReplicateId);
|
|
|
|
/*
|
|
* Begin commit critical section and insert the commit XLOG record.
|
|
*/
|
|
/* Tell bufmgr and smgr to prepare for commit */
|
|
BufmgrCommit();
|
|
|
|
/*
|
|
* Mark ourselves as within our "commit critical section". This
|
|
* forces any concurrent checkpoint to wait until we've updated
|
|
* pg_xact. Without this, it is possible for the checkpoint to set
|
|
* REDO after the XLOG record but fail to flush the pg_xact update to
|
|
* disk, leading to loss of the transaction commit if the system
|
|
* 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
|
|
* is noncritical; the presumption would be that it aborted, anyway.
|
|
*
|
|
* 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 delayChkpt
|
|
* a bit fuzzy, but it doesn't matter.
|
|
*/
|
|
START_CRIT_SECTION();
|
|
MyPgXact->delayChkpt = true;
|
|
|
|
SetCurrentTransactionStopTimestamp();
|
|
|
|
XactLogCommitRecord(xactStopTimestamp,
|
|
nchildren, children, nrels, rels,
|
|
nmsgs, invalMessages,
|
|
RelcacheInitFileInval, forceSyncCommit,
|
|
MyXactFlags,
|
|
InvalidTransactionId /* plain commit */ );
|
|
|
|
if (replorigin)
|
|
/* Move LSNs forward for this replication origin */
|
|
replorigin_session_advance(replorigin_session_origin_lsn,
|
|
XactLastRecEnd);
|
|
|
|
/*
|
|
* Record commit timestamp. The value comes from plain commit
|
|
* timestamp if there's no replication origin; otherwise, the
|
|
* timestamp was already set in replorigin_session_origin_timestamp by
|
|
* replication.
|
|
*
|
|
* We don't need to WAL-log anything here, as the commit record
|
|
* written above already contains the data.
|
|
*/
|
|
|
|
if (!replorigin || replorigin_session_origin_timestamp == 0)
|
|
replorigin_session_origin_timestamp = xactStopTimestamp;
|
|
|
|
TransactionTreeSetCommitTsData(xid, nchildren, children,
|
|
replorigin_session_origin_timestamp,
|
|
replorigin_session_origin, false);
|
|
}
|
|
|
|
/*
|
|
* 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 or didn't assign
|
|
* an xid. The transaction can end up not writing any WAL, even if it has
|
|
* an xid, if it only wrote to temporary and/or unlogged tables. It can
|
|
* end up having written WAL without an xid if it did HOT pruning. In
|
|
* case of a crash, the loss of such a transaction will be irrelevant;
|
|
* temp tables will be lost anyway, unlogged tables will be truncated and
|
|
* HOT pruning will be done again later. (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 < replica, 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 ((wrote_xlog && markXidCommitted &&
|
|
synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
|
|
forceSyncCommit || nrels > 0)
|
|
{
|
|
XLogFlush(XactLastRecEnd);
|
|
|
|
/*
|
|
* Now we may update the CLOG, if we wrote a COMMIT record above
|
|
*/
|
|
if (markXidCommitted)
|
|
TransactionIdCommitTree(xid, nchildren, children);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* 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.
|
|
*/
|
|
XLogSetAsyncXactLSN(XactLastRecEnd);
|
|
|
|
/*
|
|
* We must not immediately update the CLOG, since we didn't flush the
|
|
* XLOG. Instead, we store the LSN up to which the XLOG must be
|
|
* flushed before the CLOG may be updated.
|
|
*/
|
|
if (markXidCommitted)
|
|
TransactionIdAsyncCommitTree(xid, nchildren, children, XactLastRecEnd);
|
|
}
|
|
|
|
/*
|
|
* If we entered a commit critical section, leave it now, and let
|
|
* checkpoints proceed.
|
|
*/
|
|
if (markXidCommitted)
|
|
{
|
|
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. Similar to the decision
|
|
* above about using committing asynchronously we only want to wait if
|
|
* this backend assigned an xid and wrote WAL. No need to wait if an xid
|
|
* was assigned due to temporary/unlogged tables or due to HOT pruning.
|
|
*
|
|
* 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 && markXidCommitted)
|
|
SyncRepWaitForLSN(XactLastRecEnd, true);
|
|
|
|
/* remember end of last commit record */
|
|
XactLastCommitEnd = XactLastRecEnd;
|
|
|
|
/* Reset XactLastRecEnd until the next transaction writes something */
|
|
XactLastRecEnd = 0;
|
|
cleanup:
|
|
/* Clean up local data */
|
|
if (rels)
|
|
pfree(rels);
|
|
|
|
return latestXid;
|
|
}
|
|
|
|
|
|
/*
|
|
* AtCCI_LocalCache
|
|
*/
|
|
static 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.
|
|
*/
|
|
CommandEndInvalidationMessages();
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
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);
|
|
|
|
/*
|
|
* Ordinarily we cannot throw away the child's CurTransactionContext,
|
|
* since the data it contains will be needed at upper commit. However, if
|
|
* there isn't actually anything in it, we can throw it away. This avoids
|
|
* a small memory leak in the common case of "trivial" subxacts.
|
|
*/
|
|
if (MemoryContextIsEmpty(s->curTransactionContext))
|
|
{
|
|
MemoryContextDelete(s->curTransactionContext);
|
|
s->curTransactionContext = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
int new_nChildXids;
|
|
|
|
Assert(s->parent != NULL);
|
|
|
|
/*
|
|
* The parent childXids array will need to hold my XID and all my
|
|
* childXids, in addition to the XIDs already there.
|
|
*/
|
|
new_nChildXids = s->parent->nChildXids + s->nChildXids + 1;
|
|
|
|
/* Allocate or enlarge the parent array if necessary */
|
|
if (s->parent->maxChildXids < new_nChildXids)
|
|
{
|
|
int new_maxChildXids;
|
|
TransactionId *new_childXids;
|
|
|
|
/*
|
|
* Make it 2x what's needed right now, to avoid having to enlarge it
|
|
* repeatedly. But we can't go above MaxAllocSize. (The latter limit
|
|
* is what ensures that we don't need to worry about integer overflow
|
|
* here or in the calculation of new_nChildXids.)
|
|
*/
|
|
new_maxChildXids = Min(new_nChildXids * 2,
|
|
(int) (MaxAllocSize / sizeof(TransactionId)));
|
|
|
|
if (new_maxChildXids < new_nChildXids)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
errmsg("maximum number of committed subtransactions (%d) exceeded",
|
|
(int) (MaxAllocSize / sizeof(TransactionId)))));
|
|
|
|
/*
|
|
* We keep the child-XID arrays in TopTransactionContext; this avoids
|
|
* setting up child-transaction contexts for what might be just a few
|
|
* bytes of grandchild XIDs.
|
|
*/
|
|
if (s->parent->childXids == NULL)
|
|
new_childXids =
|
|
MemoryContextAlloc(TopTransactionContext,
|
|
new_maxChildXids * sizeof(TransactionId));
|
|
else
|
|
new_childXids = repalloc(s->parent->childXids,
|
|
new_maxChildXids * sizeof(TransactionId));
|
|
|
|
s->parent->childXids = new_childXids;
|
|
s->parent->maxChildXids = new_maxChildXids;
|
|
}
|
|
|
|
/*
|
|
* Copy all my XIDs to parent's array.
|
|
*
|
|
* 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.
|
|
*/
|
|
s->parent->childXids[s->parent->nChildXids] = s->transactionId;
|
|
|
|
if (s->nChildXids > 0)
|
|
memcpy(&s->parent->childXids[s->parent->nChildXids + 1],
|
|
s->childXids,
|
|
s->nChildXids * sizeof(TransactionId));
|
|
|
|
s->parent->nChildXids = new_nChildXids;
|
|
|
|
/* Release child's array to avoid leakage */
|
|
if (s->childXids != NULL)
|
|
pfree(s->childXids);
|
|
/* We must reset these to avoid double-free if fail later in commit */
|
|
s->childXids = NULL;
|
|
s->nChildXids = 0;
|
|
s->maxChildXids = 0;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* AbortTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* 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.)
|
|
*/
|
|
static TransactionId
|
|
RecordTransactionAbort(bool isSubXact)
|
|
{
|
|
TransactionId xid = GetCurrentTransactionIdIfAny();
|
|
TransactionId latestXid;
|
|
int nrels;
|
|
RelFileNode *rels;
|
|
int nchildren;
|
|
TransactionId *children;
|
|
TimestampTz xact_time;
|
|
|
|
/*
|
|
* 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
|
|
* rels to delete (note that this routine is not responsible for actually
|
|
* deleting 'em). We cannot have any child XIDs, either.
|
|
*/
|
|
if (!TransactionIdIsValid(xid))
|
|
{
|
|
/* Reset XactLastRecEnd until the next transaction writes something */
|
|
if (!isSubXact)
|
|
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
|
|
* need to worry about interlocking against checkpoint start.
|
|
*/
|
|
|
|
/*
|
|
* Check that we haven't aborted halfway through RecordTransactionCommit.
|
|
*/
|
|
if (TransactionIdDidCommit(xid))
|
|
elog(PANIC, "cannot abort transaction %u, it was already committed",
|
|
xid);
|
|
|
|
/* Fetch the data we need for the abort record */
|
|
nrels = smgrGetPendingDeletes(false, &rels);
|
|
nchildren = xactGetCommittedChildren(&children);
|
|
|
|
/* XXX do we really need a critical section here? */
|
|
START_CRIT_SECTION();
|
|
|
|
/* Write the ABORT record */
|
|
if (isSubXact)
|
|
xact_time = GetCurrentTimestamp();
|
|
else
|
|
{
|
|
SetCurrentTransactionStopTimestamp();
|
|
xact_time = xactStopTimestamp;
|
|
}
|
|
|
|
XactLogAbortRecord(xact_time,
|
|
nchildren, children,
|
|
nrels, rels,
|
|
MyXactFlags, InvalidTransactionId);
|
|
|
|
/*
|
|
* 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
|
|
* it is helpful because XactLockTableWait makes use of it to avoid
|
|
* waiting for already-aborted subtransactions. It is OK to do it without
|
|
* having flushed the ABORT record to disk, because in event of a crash
|
|
* we'd be assumed to have aborted anyway.
|
|
*/
|
|
TransactionIdAbortTree(xid, nchildren, children);
|
|
|
|
END_CRIT_SECTION();
|
|
|
|
/* Compute latestXid while we have the child XIDs handy */
|
|
latestXid = TransactionIdLatest(xid, nchildren, children);
|
|
|
|
/*
|
|
* If we're aborting a subtransaction, we can immediately remove failed
|
|
* XIDs from PGPROC's cache of running child XIDs. We do that here for
|
|
* subxacts, because we already have the child XID array at hand. For
|
|
* main xacts, the equivalent happens just after this function returns.
|
|
*/
|
|
if (isSubXact)
|
|
XidCacheRemoveRunningXids(xid, nchildren, children, latestXid);
|
|
|
|
/* Reset XactLastRecEnd until the next transaction writes something */
|
|
if (!isSubXact)
|
|
XactLastRecEnd = 0;
|
|
|
|
/* And clean up local data */
|
|
if (rels)
|
|
pfree(rels);
|
|
|
|
return latestXid;
|
|
}
|
|
|
|
/*
|
|
* AtAbort_Memory
|
|
*/
|
|
static void
|
|
AtAbort_Memory(void)
|
|
{
|
|
/*
|
|
* Switch into TransactionAbortContext, which should have some free space
|
|
* even if nothing else does. We'll work in this context until we've
|
|
* finished cleaning up.
|
|
*
|
|
* It is barely possible to get here when we've not been able to create
|
|
* TransactionAbortContext yet; if so use TopMemoryContext.
|
|
*/
|
|
if (TransactionAbortContext != NULL)
|
|
MemoryContextSwitchTo(TransactionAbortContext);
|
|
else
|
|
MemoryContextSwitchTo(TopMemoryContext);
|
|
}
|
|
|
|
/*
|
|
* AtSubAbort_Memory
|
|
*/
|
|
static void
|
|
AtSubAbort_Memory(void)
|
|
{
|
|
Assert(TransactionAbortContext != NULL);
|
|
|
|
MemoryContextSwitchTo(TransactionAbortContext);
|
|
}
|
|
|
|
|
|
/*
|
|
* AtAbort_ResourceOwner
|
|
*/
|
|
static void
|
|
AtAbort_ResourceOwner(void)
|
|
{
|
|
/*
|
|
* Make sure we have a valid ResourceOwner, if possible (else it will be
|
|
* NULL, which is OK)
|
|
*/
|
|
CurrentResourceOwner = TopTransactionResourceOwner;
|
|
}
|
|
|
|
/*
|
|
* AtSubAbort_ResourceOwner
|
|
*/
|
|
static void
|
|
AtSubAbort_ResourceOwner(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/* Make sure we have a valid ResourceOwner */
|
|
CurrentResourceOwner = s->curTransactionOwner;
|
|
}
|
|
|
|
|
|
/*
|
|
* AtSubAbort_childXids
|
|
*/
|
|
static void
|
|
AtSubAbort_childXids(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* We keep the child-XID arrays in TopTransactionContext (see
|
|
* AtSubCommit_childXids). This means we'd better free the array
|
|
* explicitly at abort to avoid leakage.
|
|
*/
|
|
if (s->childXids != NULL)
|
|
pfree(s->childXids);
|
|
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.
|
|
*/
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* CleanupTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* AtCleanup_Memory
|
|
*/
|
|
static void
|
|
AtCleanup_Memory(void)
|
|
{
|
|
Assert(CurrentTransactionState->parent == NULL);
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/*
|
|
* Clear the special abort context for next time.
|
|
*/
|
|
if (TransactionAbortContext != NULL)
|
|
MemoryContextResetAndDeleteChildren(TransactionAbortContext);
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* Clear the special abort context for next time.
|
|
*/
|
|
if (TransactionAbortContext != NULL)
|
|
MemoryContextResetAndDeleteChildren(TransactionAbortContext);
|
|
|
|
/*
|
|
* Delete the subxact local memory contexts. Its CurTransactionContext can
|
|
* go too (note this also kills CurTransactionContexts from any children
|
|
* of the subxact).
|
|
*/
|
|
if (s->curTransactionContext)
|
|
MemoryContextDelete(s->curTransactionContext);
|
|
s->curTransactionContext = NULL;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* interface routines
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* StartTransaction
|
|
*/
|
|
static void
|
|
StartTransaction(void)
|
|
{
|
|
TransactionState s;
|
|
VirtualTransactionId vxid;
|
|
|
|
/*
|
|
* Let's just make sure the state stack is empty
|
|
*/
|
|
s = &TopTransactionStateData;
|
|
CurrentTransactionState = s;
|
|
|
|
Assert(XactTopTransactionId == InvalidTransactionId);
|
|
|
|
/*
|
|
* check the current transaction state
|
|
*/
|
|
if (s->state != TRANS_DEFAULT)
|
|
elog(WARNING, "StartTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
/*
|
|
* set the current transaction state information appropriately during
|
|
* start processing
|
|
*/
|
|
s->state = TRANS_START;
|
|
s->transactionId = InvalidTransactionId; /* until assigned */
|
|
|
|
/*
|
|
* 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;
|
|
forceSyncCommit = false;
|
|
MyXactFlags = 0;
|
|
|
|
/*
|
|
* reinitialize within-transaction counters
|
|
*/
|
|
s->subTransactionId = TopSubTransactionId;
|
|
currentSubTransactionId = TopSubTransactionId;
|
|
currentCommandId = FirstCommandId;
|
|
currentCommandIdUsed = false;
|
|
|
|
/*
|
|
* initialize reported xid accounting
|
|
*/
|
|
nUnreportedXids = 0;
|
|
s->didLogXid = false;
|
|
|
|
/*
|
|
* must initialize resource-management stuff first
|
|
*/
|
|
AtStart_Memory();
|
|
AtStart_ResourceOwner();
|
|
|
|
/*
|
|
* Assign a new LocalTransactionId, and combine it with the backendId to
|
|
* form a virtual transaction id.
|
|
*/
|
|
vxid.backendId = MyBackendId;
|
|
vxid.localTransactionId = GetNextLocalTransactionId();
|
|
|
|
/*
|
|
* Lock the virtual transaction id before we announce it in the proc array
|
|
*/
|
|
VirtualXactLockTableInsert(vxid);
|
|
|
|
/*
|
|
* 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);
|
|
MyProc->lxid = vxid.localTransactionId;
|
|
|
|
TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
|
|
|
|
/*
|
|
* set transaction_timestamp() (a/k/a now()). We want this to be the same
|
|
* as the first command's statement_timestamp(), so don't do a fresh
|
|
* GetCurrentTimestamp() call (which'd be expensive anyway). Also, mark
|
|
* xactStopTimestamp as unset.
|
|
*/
|
|
xactStartTimestamp = stmtStartTimestamp;
|
|
xactStopTimestamp = 0;
|
|
pgstat_report_xact_timestamp(xactStartTimestamp);
|
|
|
|
/*
|
|
* initialize current transaction state fields
|
|
*
|
|
* note: prevXactReadOnly is not used at the outermost level
|
|
*/
|
|
s->nestingLevel = 1;
|
|
s->gucNestLevel = 1;
|
|
s->childXids = NULL;
|
|
s->nChildXids = 0;
|
|
s->maxChildXids = 0;
|
|
GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
|
|
/* SecurityRestrictionContext should never be set outside a transaction */
|
|
Assert(s->prevSecContext == 0);
|
|
|
|
/*
|
|
* initialize other subsystems for new transaction
|
|
*/
|
|
AtStart_GUC();
|
|
AtStart_Cache();
|
|
AfterTriggerBeginXact();
|
|
|
|
/*
|
|
* done with start processing, set current transaction state to "in
|
|
* progress"
|
|
*/
|
|
s->state = TRANS_INPROGRESS;
|
|
|
|
ShowTransactionState("StartTransaction");
|
|
}
|
|
|
|
|
|
/*
|
|
* CommitTransaction
|
|
*
|
|
* NB: if you change this routine, better look at PrepareTransaction too!
|
|
*/
|
|
static void
|
|
CommitTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
TransactionId latestXid;
|
|
bool is_parallel_worker;
|
|
|
|
is_parallel_worker = (s->blockState == TBLOCK_PARALLEL_INPROGRESS);
|
|
|
|
/* Enforce parallel mode restrictions during parallel worker commit. */
|
|
if (is_parallel_worker)
|
|
EnterParallelMode();
|
|
|
|
ShowTransactionState("CommitTransaction");
|
|
|
|
/*
|
|
* check the current transaction state
|
|
*/
|
|
if (s->state != TRANS_INPROGRESS)
|
|
elog(WARNING, "CommitTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
Assert(s->parent == NULL);
|
|
|
|
/*
|
|
* 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 (;;)
|
|
{
|
|
/*
|
|
* Fire all currently pending deferred triggers.
|
|
*/
|
|
AfterTriggerFireDeferred();
|
|
|
|
/*
|
|
* 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 (!PreCommit_Portals(false))
|
|
break;
|
|
}
|
|
|
|
CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT
|
|
: XACT_EVENT_PRE_COMMIT);
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
/* If we might have parallel workers, clean them up now. */
|
|
if (IsInParallelMode())
|
|
AtEOXact_Parallel(true);
|
|
|
|
/* Shut down the deferred-trigger manager */
|
|
AfterTriggerEndXact(true);
|
|
|
|
/*
|
|
* Let ON COMMIT management do its thing (must happen after closing
|
|
* cursors, to avoid dangling-reference problems)
|
|
*/
|
|
PreCommit_on_commit_actions();
|
|
|
|
/* 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();
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
s->state = TRANS_COMMIT;
|
|
s->parallelModeLevel = 0;
|
|
|
|
if (!is_parallel_worker)
|
|
{
|
|
/*
|
|
* We need to mark our XIDs as committed in pg_xact. This is where we
|
|
* durably commit.
|
|
*/
|
|
latestXid = RecordTransactionCommit();
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We must not mark our XID committed; the parallel master is
|
|
* responsible for that.
|
|
*/
|
|
latestXid = InvalidTransactionId;
|
|
|
|
/*
|
|
* Make sure the master will know about any WAL we wrote before it
|
|
* commits.
|
|
*/
|
|
ParallelWorkerReportLastRecEnd(XactLastRecEnd);
|
|
}
|
|
|
|
TRACE_POSTGRESQL_TRANSACTION_COMMIT(MyProc->lxid);
|
|
|
|
/*
|
|
* Let others know about no transaction in progress by me. Note that this
|
|
* must be done _before_ releasing locks we hold and _after_
|
|
* RecordTransactionCommit.
|
|
*/
|
|
ProcArrayEndTransaction(MyProc, latestXid);
|
|
|
|
/*
|
|
* 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:
|
|
* 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.
|
|
*/
|
|
|
|
CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_COMMIT
|
|
: XACT_EVENT_COMMIT);
|
|
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_BEFORE_LOCKS,
|
|
true, true);
|
|
|
|
/* Check we've released all buffer pins */
|
|
AtEOXact_Buffers(true);
|
|
|
|
/* Clean up the relation cache */
|
|
AtEOXact_RelationCache(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);
|
|
|
|
AtEOXact_MultiXact();
|
|
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_LOCKS,
|
|
true, true);
|
|
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);
|
|
|
|
AtCommit_Notify();
|
|
AtEOXact_GUC(true, 1);
|
|
AtEOXact_SPI(true);
|
|
AtEOXact_on_commit_actions(true);
|
|
AtEOXact_Namespace(true, is_parallel_worker);
|
|
AtEOXact_SMgr();
|
|
AtEOXact_Files();
|
|
AtEOXact_ComboCid();
|
|
AtEOXact_HashTables(true);
|
|
AtEOXact_PgStat(true);
|
|
AtEOXact_Snapshot(true, false);
|
|
AtEOXact_ApplyLauncher(true);
|
|
pgstat_report_xact_timestamp(0);
|
|
|
|
CurrentResourceOwner = NULL;
|
|
ResourceOwnerDelete(TopTransactionResourceOwner);
|
|
s->curTransactionOwner = NULL;
|
|
CurTransactionResourceOwner = NULL;
|
|
TopTransactionResourceOwner = NULL;
|
|
|
|
AtCommit_Memory();
|
|
|
|
s->transactionId = InvalidTransactionId;
|
|
s->subTransactionId = InvalidSubTransactionId;
|
|
s->nestingLevel = 0;
|
|
s->gucNestLevel = 0;
|
|
s->childXids = NULL;
|
|
s->nChildXids = 0;
|
|
s->maxChildXids = 0;
|
|
|
|
XactTopTransactionId = InvalidTransactionId;
|
|
nParallelCurrentXids = 0;
|
|
|
|
/*
|
|
* done with commit processing, set current transaction state back to
|
|
* default
|
|
*/
|
|
s->state = TRANS_DEFAULT;
|
|
|
|
RESUME_INTERRUPTS();
|
|
}
|
|
|
|
|
|
/*
|
|
* PrepareTransaction
|
|
*
|
|
* NB: if you change this routine, better look at CommitTransaction too!
|
|
*/
|
|
static void
|
|
PrepareTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
TransactionId xid = GetCurrentTransactionId();
|
|
GlobalTransaction gxact;
|
|
TimestampTz prepared_at;
|
|
|
|
Assert(!IsInParallelMode());
|
|
|
|
ShowTransactionState("PrepareTransaction");
|
|
|
|
/*
|
|
* check the current transaction state
|
|
*/
|
|
if (s->state != TRANS_INPROGRESS)
|
|
elog(WARNING, "PrepareTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
Assert(s->parent == NULL);
|
|
|
|
/*
|
|
* 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 (;;)
|
|
{
|
|
/*
|
|
* Fire all currently pending deferred triggers.
|
|
*/
|
|
AfterTriggerFireDeferred();
|
|
|
|
/*
|
|
* 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 (!PreCommit_Portals(true))
|
|
break;
|
|
}
|
|
|
|
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.
|
|
*/
|
|
|
|
/* Shut down the deferred-trigger manager */
|
|
AfterTriggerEndXact(true);
|
|
|
|
/*
|
|
* Let ON COMMIT management do its thing (must happen after closing
|
|
* cursors, to avoid dangling-reference problems)
|
|
*/
|
|
PreCommit_on_commit_actions();
|
|
|
|
/* 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 */
|
|
|
|
/*
|
|
* Don't allow PREPARE TRANSACTION if we've accessed a temporary table in
|
|
* this transaction. Having the prepared xact hold locks on another
|
|
* backend's temp table seems a bad idea --- for instance it would prevent
|
|
* the backend from exiting. There are other problems too, such as how to
|
|
* clean up the source backend's local buffers and ON COMMIT state if the
|
|
* prepared xact includes a DROP of a temp table.
|
|
*
|
|
* We must check this after executing any ON COMMIT actions, because they
|
|
* might still access a temp relation.
|
|
*
|
|
* XXX In principle this could be relaxed to allow some useful special
|
|
* cases, such as a temp table created and dropped all within the
|
|
* transaction. That seems to require much more bookkeeping though.
|
|
*/
|
|
if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPREL))
|
|
ereport(ERROR,
|
|
(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")));
|
|
|
|
/*
|
|
* Don't allow PREPARE but for transaction that has/might kill logical
|
|
* replication workers.
|
|
*/
|
|
if (XactManipulatesLogicalReplicationWorkers())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cannot PREPARE a transaction that has manipulated logical replication workers")));
|
|
|
|
/* Prevent cancel/die interrupt while cleaning up */
|
|
HOLD_INTERRUPTS();
|
|
|
|
/*
|
|
* set the current transaction state information appropriately during
|
|
* prepare processing
|
|
*/
|
|
s->state = TRANS_PREPARE;
|
|
|
|
prepared_at = GetCurrentTimestamp();
|
|
|
|
/* Tell bufmgr and smgr to prepare for commit */
|
|
BufmgrCommit();
|
|
|
|
/*
|
|
* Reserve the GID for this transaction. This could fail if the requested
|
|
* GID is invalid or already in use.
|
|
*/
|
|
gxact = MarkAsPreparing(xid, prepareGID, prepared_at,
|
|
GetUserId(), MyDatabaseId);
|
|
prepareGID = NULL;
|
|
|
|
/*
|
|
* Collect data for the 2PC state file. Note that in general, no actual
|
|
* state change should happen in the called modules during this step,
|
|
* since it's still possible to fail before commit, and in that case we
|
|
* want transaction abort to be able to clean up. (In particular, the
|
|
* AtPrepare routines may error out if they find cases they cannot
|
|
* handle.) State cleanup should happen in the PostPrepare routines
|
|
* below. However, some modules can go ahead and clear state here because
|
|
* they wouldn't do anything with it during abort anyway.
|
|
*
|
|
* Note: because the 2PC state file records will be replayed in the same
|
|
* order they are made, the order of these calls has to match the order in
|
|
* which we want things to happen during COMMIT PREPARED or ROLLBACK
|
|
* PREPARED; in particular, pay attention to whether things should happen
|
|
* before or after releasing the transaction's locks.
|
|
*/
|
|
StartPrepare(gxact);
|
|
|
|
AtPrepare_Notify();
|
|
AtPrepare_Locks();
|
|
AtPrepare_PredicateLocks();
|
|
AtPrepare_PgStat();
|
|
AtPrepare_MultiXact();
|
|
AtPrepare_RelationMap();
|
|
|
|
/*
|
|
* Here is where we really truly prepare.
|
|
*
|
|
* We have to record transaction prepares even if we didn't make any
|
|
* updates, because the transaction manager might get confused if we lose
|
|
* a global transaction.
|
|
*/
|
|
EndPrepare(gxact);
|
|
|
|
/*
|
|
* Now we clean up backend-internal state and release internal resources.
|
|
*/
|
|
|
|
/* Reset XactLastRecEnd until the next transaction writes something */
|
|
XactLastRecEnd = 0;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
ProcArrayClearTransaction(MyProc);
|
|
|
|
/*
|
|
* In normal commit-processing, this is all non-critical post-transaction
|
|
* cleanup. When the transaction is prepared, however, it's important
|
|
* that the locks and other per-backend resources are transferred to the
|
|
* prepared transaction's PGPROC entry. Note that if an error is raised
|
|
* here, it's too late to abort the transaction. XXX: This probably should
|
|
* be in a critical section, to force a PANIC if any of this fails, but
|
|
* that cure could be worse than the disease.
|
|
*/
|
|
|
|
CallXactCallbacks(XACT_EVENT_PREPARE);
|
|
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_BEFORE_LOCKS,
|
|
true, true);
|
|
|
|
/* Check we've released all buffer pins */
|
|
AtEOXact_Buffers(true);
|
|
|
|
/* Clean up the relation cache */
|
|
AtEOXact_RelationCache(true);
|
|
|
|
/* notify doesn't need a postprepare call */
|
|
|
|
PostPrepare_PgStat();
|
|
|
|
PostPrepare_Inval();
|
|
|
|
PostPrepare_smgr();
|
|
|
|
PostPrepare_MultiXact(xid);
|
|
|
|
PostPrepare_Locks(xid);
|
|
PostPrepare_PredicateLocks(xid);
|
|
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_LOCKS,
|
|
true, true);
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_AFTER_LOCKS,
|
|
true, true);
|
|
|
|
/*
|
|
* Allow another backend to finish the transaction. After
|
|
* PostPrepare_Twophase(), the transaction is completely detached from our
|
|
* backend. The rest is just non-critical cleanup of backend-local state.
|
|
*/
|
|
PostPrepare_Twophase();
|
|
|
|
/* PREPARE acts the same as COMMIT as far as GUC is concerned */
|
|
AtEOXact_GUC(true, 1);
|
|
AtEOXact_SPI(true);
|
|
AtEOXact_on_commit_actions(true);
|
|
AtEOXact_Namespace(true, false);
|
|
AtEOXact_SMgr();
|
|
AtEOXact_Files();
|
|
AtEOXact_ComboCid();
|
|
AtEOXact_HashTables(true);
|
|
/* don't call AtEOXact_PgStat here; we fixed pgstat state above */
|
|
AtEOXact_Snapshot(true, true);
|
|
pgstat_report_xact_timestamp(0);
|
|
|
|
CurrentResourceOwner = NULL;
|
|
ResourceOwnerDelete(TopTransactionResourceOwner);
|
|
s->curTransactionOwner = NULL;
|
|
CurTransactionResourceOwner = NULL;
|
|
TopTransactionResourceOwner = NULL;
|
|
|
|
AtCommit_Memory();
|
|
|
|
s->transactionId = InvalidTransactionId;
|
|
s->subTransactionId = InvalidSubTransactionId;
|
|
s->nestingLevel = 0;
|
|
s->gucNestLevel = 0;
|
|
s->childXids = NULL;
|
|
s->nChildXids = 0;
|
|
s->maxChildXids = 0;
|
|
|
|
XactTopTransactionId = InvalidTransactionId;
|
|
nParallelCurrentXids = 0;
|
|
|
|
/*
|
|
* done with 1st phase commit processing, set current transaction state
|
|
* back to default
|
|
*/
|
|
s->state = TRANS_DEFAULT;
|
|
|
|
RESUME_INTERRUPTS();
|
|
}
|
|
|
|
|
|
/*
|
|
* AbortTransaction
|
|
*/
|
|
static void
|
|
AbortTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
TransactionId latestXid;
|
|
bool is_parallel_worker;
|
|
|
|
/* Prevent cancel/die interrupt while cleaning up */
|
|
HOLD_INTERRUPTS();
|
|
|
|
/* Make sure we have a valid memory context and resource owner */
|
|
AtAbort_Memory();
|
|
AtAbort_ResourceOwner();
|
|
|
|
/*
|
|
* 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();
|
|
|
|
/* Clear wait information and command progress indicator */
|
|
pgstat_report_wait_end();
|
|
pgstat_progress_end_command();
|
|
|
|
/* Clean up buffer I/O and buffer context locks, too */
|
|
AbortBufferIO();
|
|
UnlockBuffers();
|
|
|
|
/* Reset WAL record construction state */
|
|
XLogResetInsertion();
|
|
|
|
/* Cancel condition variable sleep */
|
|
ConditionVariableCancelSleep();
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
is_parallel_worker = (s->blockState == TBLOCK_PARALLEL_INPROGRESS);
|
|
if (s->state != TRANS_INPROGRESS && s->state != TRANS_PREPARE)
|
|
elog(WARNING, "AbortTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
Assert(s->parent == NULL);
|
|
|
|
/*
|
|
* set the current transaction state information appropriately during the
|
|
* abort processing
|
|
*/
|
|
s->state = TRANS_ABORT;
|
|
|
|
/*
|
|
* 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
|
|
* 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.)
|
|
*/
|
|
SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
|
|
|
|
/* If in parallel mode, clean up workers and exit parallel mode. */
|
|
if (IsInParallelMode())
|
|
{
|
|
AtEOXact_Parallel(false);
|
|
s->parallelModeLevel = 0;
|
|
}
|
|
|
|
/*
|
|
* do abort processing
|
|
*/
|
|
AfterTriggerEndXact(false); /* 'false' means it's abort */
|
|
AtAbort_Portals();
|
|
AtEOXact_LargeObject(false);
|
|
AtAbort_Notify();
|
|
AtEOXact_RelationMap(false);
|
|
AtAbort_Twophase();
|
|
|
|
/*
|
|
* Advertise the fact that we aborted in pg_xact (assuming that we got as
|
|
* far as assigning an XID to advertise). But if we're inside a parallel
|
|
* worker, skip this; the user backend must be the one to write the abort
|
|
* record.
|
|
*/
|
|
if (!is_parallel_worker)
|
|
latestXid = RecordTransactionAbort(false);
|
|
else
|
|
{
|
|
latestXid = InvalidTransactionId;
|
|
|
|
/*
|
|
* Since the parallel master won't get our value of XactLastRecEnd in
|
|
* this case, we nudge WAL-writer ourselves in this case. See related
|
|
* comments in RecordTransactionAbort for why this matters.
|
|
*/
|
|
XLogSetAsyncXactLSN(XactLastRecEnd);
|
|
}
|
|
|
|
TRACE_POSTGRESQL_TRANSACTION_ABORT(MyProc->lxid);
|
|
|
|
/*
|
|
* Let others know about no transaction in progress by me. Note that this
|
|
* must be done _before_ releasing locks we hold and _after_
|
|
* RecordTransactionAbort.
|
|
*/
|
|
ProcArrayEndTransaction(MyProc, latestXid);
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
if (is_parallel_worker)
|
|
CallXactCallbacks(XACT_EVENT_PARALLEL_ABORT);
|
|
else
|
|
CallXactCallbacks(XACT_EVENT_ABORT);
|
|
|
|
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_GUC(false, 1);
|
|
AtEOXact_SPI(false);
|
|
AtEOXact_on_commit_actions(false);
|
|
AtEOXact_Namespace(false, is_parallel_worker);
|
|
AtEOXact_SMgr();
|
|
AtEOXact_Files();
|
|
AtEOXact_ComboCid();
|
|
AtEOXact_HashTables(false);
|
|
AtEOXact_PgStat(false);
|
|
AtEOXact_ApplyLauncher(false);
|
|
pgstat_report_xact_timestamp(0);
|
|
}
|
|
|
|
/*
|
|
* State remains TRANS_ABORT until CleanupTransaction().
|
|
*/
|
|
RESUME_INTERRUPTS();
|
|
}
|
|
|
|
/*
|
|
* CleanupTransaction
|
|
*/
|
|
static void
|
|
CleanupTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* State should still be TRANS_ABORT from AbortTransaction().
|
|
*/
|
|
if (s->state != TRANS_ABORT)
|
|
elog(FATAL, "CleanupTransaction: unexpected state %s",
|
|
TransStateAsString(s->state));
|
|
|
|
/*
|
|
* do abort cleanup processing
|
|
*/
|
|
AtCleanup_Portals(); /* now safe to release portal memory */
|
|
AtEOXact_Snapshot(false, true); /* and release the transaction's snapshots */
|
|
|
|
CurrentResourceOwner = NULL; /* and resource owner */
|
|
if (TopTransactionResourceOwner)
|
|
ResourceOwnerDelete(TopTransactionResourceOwner);
|
|
s->curTransactionOwner = NULL;
|
|
CurTransactionResourceOwner = NULL;
|
|
TopTransactionResourceOwner = NULL;
|
|
|
|
AtCleanup_Memory(); /* and transaction memory */
|
|
|
|
s->transactionId = InvalidTransactionId;
|
|
s->subTransactionId = InvalidSubTransactionId;
|
|
s->nestingLevel = 0;
|
|
s->gucNestLevel = 0;
|
|
s->childXids = NULL;
|
|
s->nChildXids = 0;
|
|
s->maxChildXids = 0;
|
|
s->parallelModeLevel = 0;
|
|
|
|
XactTopTransactionId = InvalidTransactionId;
|
|
nParallelCurrentXids = 0;
|
|
|
|
/*
|
|
* done with abort processing, set current transaction state back to
|
|
* default
|
|
*/
|
|
s->state = TRANS_DEFAULT;
|
|
}
|
|
|
|
/*
|
|
* StartTransactionCommand
|
|
*/
|
|
void
|
|
StartTransactionCommand(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* if we aren't in a transaction block, we just do our usual start
|
|
* transaction.
|
|
*/
|
|
case TBLOCK_DEFAULT:
|
|
StartTransaction();
|
|
s->blockState = TBLOCK_STARTED;
|
|
break;
|
|
|
|
/*
|
|
* We are somewhere in a transaction block or subtransaction and
|
|
* about to start a new command. For now we do nothing, but
|
|
* someday we may do command-local resource initialization. (Note
|
|
* that any needed CommandCounterIncrement was done by the
|
|
* previous CommitTransactionCommand.)
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
break;
|
|
|
|
/*
|
|
* Here we are in a failed transaction block (one of the commands
|
|
* caused an abort) so we do nothing but remain in the abort
|
|
* state. Eventually we will get a ROLLBACK command which will
|
|
* get us out of this state. (It is up to other code to ensure
|
|
* that no commands other than ROLLBACK will be processed in these
|
|
* states.)
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
break;
|
|
|
|
/* These cases are invalid. */
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
case TBLOCK_PREPARE:
|
|
elog(ERROR, "StartTransactionCommand: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We must switch to CurTransactionContext before returning. This is
|
|
* already done if we called StartTransaction, otherwise not.
|
|
*/
|
|
Assert(CurTransactionContext != NULL);
|
|
MemoryContextSwitchTo(CurTransactionContext);
|
|
}
|
|
|
|
/*
|
|
* CommitTransactionCommand
|
|
*/
|
|
void
|
|
CommitTransactionCommand(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* These shouldn't happen. TBLOCK_DEFAULT means the previous
|
|
* StartTransactionCommand didn't set the STARTED state
|
|
* appropriately, while TBLOCK_PARALLEL_INPROGRESS should be ended
|
|
* by EndParallelWorkerTransaction(), not this function.
|
|
*/
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
elog(FATAL, "CommitTransactionCommand: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
|
|
/*
|
|
* If we aren't in a transaction block, just do our usual
|
|
* transaction commit, and return to the idle state.
|
|
*/
|
|
case TBLOCK_STARTED:
|
|
CommitTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* We are completing a "BEGIN TRANSACTION" command, so we change
|
|
* to the "transaction block in progress" state and return. (We
|
|
* assume the BEGIN did nothing to the database, so we need no
|
|
* CommandCounterIncrement.)
|
|
*/
|
|
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:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
CommandCounterIncrement();
|
|
break;
|
|
|
|
/*
|
|
* We are completing a "COMMIT" command. Do it and return to the
|
|
* idle state.
|
|
*/
|
|
case TBLOCK_END:
|
|
CommitTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* Here we are in the middle of a transaction block but one of the
|
|
* commands caused an abort so we do nothing but remain in the
|
|
* abort state. Eventually we will get a ROLLBACK command.
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
break;
|
|
|
|
/*
|
|
* Here we were in an aborted transaction block and we just got
|
|
* the ROLLBACK command from the user, so clean up the
|
|
* already-aborted transaction and return to the idle state.
|
|
*/
|
|
case TBLOCK_ABORT_END:
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* Here we were in a perfectly good transaction block but the user
|
|
* told us to ROLLBACK anyway. We have to abort the transaction
|
|
* and then clean up.
|
|
*/
|
|
case TBLOCK_ABORT_PENDING:
|
|
AbortTransaction();
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* We are completing a "PREPARE TRANSACTION" command. Do it and
|
|
* return to the idle state.
|
|
*/
|
|
case TBLOCK_PREPARE:
|
|
PrepareTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* We were just issued a SAVEPOINT inside a transaction block.
|
|
* Start a subtransaction. (DefineSavepoint already did
|
|
* PushTransaction, so as to have someplace to put the SUBBEGIN
|
|
* state.)
|
|
*/
|
|
case TBLOCK_SUBBEGIN:
|
|
StartSubTransaction();
|
|
s->blockState = TBLOCK_SUBINPROGRESS;
|
|
break;
|
|
|
|
/*
|
|
* 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_SUBRELEASE:
|
|
do
|
|
{
|
|
CommitSubTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
} 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)
|
|
{
|
|
Assert(s->parent == NULL);
|
|
CommitTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
}
|
|
else if (s->blockState == TBLOCK_PREPARE)
|
|
{
|
|
Assert(s->parent == NULL);
|
|
PrepareTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
}
|
|
else
|
|
elog(ERROR, "CommitTransactionCommand: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
|
|
/*
|
|
* The current already-failed subtransaction is ending due to a
|
|
* ROLLBACK or ROLLBACK TO command, so pop it and recursively
|
|
* examine the parent (which could be in any of several states).
|
|
*/
|
|
case TBLOCK_SUBABORT_END:
|
|
CleanupSubTransaction();
|
|
CommitTransactionCommand();
|
|
break;
|
|
|
|
/*
|
|
* As above, but it's not dead yet, so abort first.
|
|
*/
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
AbortSubTransaction();
|
|
CleanupSubTransaction();
|
|
CommitTransactionCommand();
|
|
break;
|
|
|
|
/*
|
|
* The current subtransaction is the target of a ROLLBACK TO
|
|
* command. Abort and pop it, then start a new subtransaction
|
|
* with the same name.
|
|
*/
|
|
case TBLOCK_SUBRESTART:
|
|
{
|
|
char *name;
|
|
int savepointLevel;
|
|
|
|
/* save name and keep Cleanup from freeing it */
|
|
name = s->name;
|
|
s->name = NULL;
|
|
savepointLevel = s->savepointLevel;
|
|
|
|
AbortSubTransaction();
|
|
CleanupSubTransaction();
|
|
|
|
DefineSavepoint(NULL);
|
|
s = CurrentTransactionState; /* changed by push */
|
|
s->name = name;
|
|
s->savepointLevel = savepointLevel;
|
|
|
|
/* This is the same as TBLOCK_SUBBEGIN case */
|
|
AssertState(s->blockState == TBLOCK_SUBBEGIN);
|
|
StartSubTransaction();
|
|
s->blockState = TBLOCK_SUBINPROGRESS;
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* Same as above, but the subtransaction had already failed, so we
|
|
* don't need AbortSubTransaction.
|
|
*/
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
{
|
|
char *name;
|
|
int savepointLevel;
|
|
|
|
/* save name and keep Cleanup from freeing it */
|
|
name = s->name;
|
|
s->name = NULL;
|
|
savepointLevel = s->savepointLevel;
|
|
|
|
CleanupSubTransaction();
|
|
|
|
DefineSavepoint(NULL);
|
|
s = CurrentTransactionState; /* changed by push */
|
|
s->name = name;
|
|
s->savepointLevel = savepointLevel;
|
|
|
|
/* This is the same as TBLOCK_SUBBEGIN case */
|
|
AssertState(s->blockState == TBLOCK_SUBBEGIN);
|
|
StartSubTransaction();
|
|
s->blockState = TBLOCK_SUBINPROGRESS;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* AbortCurrentTransaction
|
|
*/
|
|
void
|
|
AbortCurrentTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
case TBLOCK_DEFAULT:
|
|
if (s->state == TRANS_DEFAULT)
|
|
{
|
|
/* we are idle, so nothing to do */
|
|
}
|
|
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;
|
|
|
|
/*
|
|
* 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". We assume that the user
|
|
* will interpret the error as meaning the BEGIN failed to get him
|
|
* into a transaction block, so we should abort and return to idle
|
|
* state.
|
|
*/
|
|
case TBLOCK_BEGIN:
|
|
AbortTransaction();
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* 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 a ROLLBACK.
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
AbortTransaction();
|
|
s->blockState = TBLOCK_ABORT;
|
|
/* CleanupTransaction happens when we exit TBLOCK_ABORT_END */
|
|
break;
|
|
|
|
/*
|
|
* 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).
|
|
*/
|
|
case TBLOCK_END:
|
|
AbortTransaction();
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* Here, we are already in an aborted transaction state and are
|
|
* waiting for a ROLLBACK, but for some reason we failed again! So
|
|
* we just remain in the abort state.
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
break;
|
|
|
|
/*
|
|
* We are in a failed transaction and we got the ROLLBACK command.
|
|
* We have already aborted, we just need to cleanup and go to idle
|
|
* state.
|
|
*/
|
|
case TBLOCK_ABORT_END:
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* We are in a live transaction and we got a ROLLBACK command.
|
|
* Abort, cleanup, go to idle state.
|
|
*/
|
|
case TBLOCK_ABORT_PENDING:
|
|
AbortTransaction();
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* Here, we failed while trying to PREPARE. Clean up the
|
|
* transaction and return to idle state (we do not want to stay in
|
|
* the transaction).
|
|
*/
|
|
case TBLOCK_PREPARE:
|
|
AbortTransaction();
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* We got an error inside a subtransaction. Abort just the
|
|
* subtransaction, and go to the persistent SUBABORT state until
|
|
* we get ROLLBACK.
|
|
*/
|
|
case TBLOCK_SUBINPROGRESS:
|
|
AbortSubTransaction();
|
|
s->blockState = TBLOCK_SUBABORT;
|
|
break;
|
|
|
|
/*
|
|
* If we failed while trying to create a subtransaction, clean up
|
|
* the broken subtransaction and abort the parent. The same
|
|
* applies if we get a failure while ending a subtransaction.
|
|
*/
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
AbortSubTransaction();
|
|
CleanupSubTransaction();
|
|
AbortCurrentTransaction();
|
|
break;
|
|
|
|
/*
|
|
* Same as above, except the Abort() was already done.
|
|
*/
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
CleanupSubTransaction();
|
|
AbortCurrentTransaction();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* PreventTransactionChain
|
|
*
|
|
* This routine is to be called by statements that must not run inside
|
|
* a transaction block, typically because they have non-rollback-able
|
|
* side effects or do internal commits.
|
|
*
|
|
* If we have already started a transaction block, issue an error; also issue
|
|
* an error if we appear to be running inside a user-defined function (which
|
|
* could issue more commands and possibly cause a failure after the statement
|
|
* completes). Subtransactions are verboten too.
|
|
*
|
|
* isTopLevel: passed down from ProcessUtility to determine whether we are
|
|
* inside a function or multi-query querystring. (We will always fail if
|
|
* this is false, but it's convenient to centralize the check here instead of
|
|
* making callers do it.)
|
|
* stmtType: statement type name, for error messages.
|
|
*/
|
|
void
|
|
PreventTransactionChain(bool isTopLevel, const char *stmtType)
|
|
{
|
|
/*
|
|
* xact block already started?
|
|
*/
|
|
if (IsTransactionBlock())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
|
/* translator: %s represents an SQL statement name */
|
|
errmsg("%s cannot run inside a transaction block",
|
|
stmtType)));
|
|
|
|
/*
|
|
* 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)));
|
|
|
|
/*
|
|
* inside a function call?
|
|
*/
|
|
if (!isTopLevel)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
|
/* translator: %s represents an SQL statement name */
|
|
errmsg("%s cannot be executed from a function or multi-command string",
|
|
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 */
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
*
|
|
* 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 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 warning or error messages.
|
|
*/
|
|
static void
|
|
CheckTransactionChain(bool isTopLevel, bool throwError, const char *stmtType)
|
|
{
|
|
/*
|
|
* xact block already started?
|
|
*/
|
|
if (IsTransactionBlock())
|
|
return;
|
|
|
|
/*
|
|
* subtransaction?
|
|
*/
|
|
if (IsSubTransaction())
|
|
return;
|
|
|
|
/*
|
|
* inside a function call?
|
|
*/
|
|
if (!isTopLevel)
|
|
return;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* isTopLevel: passed down from ProcessUtility to determine whether we are
|
|
* inside a function.
|
|
*/
|
|
bool
|
|
IsInTransactionChain(bool isTopLevel)
|
|
{
|
|
/*
|
|
* Return true on same conditions that would make PreventTransactionChain
|
|
* error out
|
|
*/
|
|
if (IsTransactionBlock())
|
|
return true;
|
|
|
|
if (IsSubTransaction())
|
|
return true;
|
|
|
|
if (!isTopLevel)
|
|
return true;
|
|
|
|
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
|
|
CurrentTransactionState->blockState != TBLOCK_STARTED)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Register or deregister callback functions for start- and end-of-xact
|
|
* operations.
|
|
*
|
|
* 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).
|
|
*
|
|
* At transaction end, the callback occurs post-commit or post-abort, so the
|
|
* callback functions can only do noncritical cleanup.
|
|
*/
|
|
void
|
|
RegisterXactCallback(XactCallback callback, void *arg)
|
|
{
|
|
XactCallbackItem *item;
|
|
|
|
item = (XactCallbackItem *)
|
|
MemoryContextAlloc(TopMemoryContext, sizeof(XactCallbackItem));
|
|
item->callback = callback;
|
|
item->arg = arg;
|
|
item->next = Xact_callbacks;
|
|
Xact_callbacks = item;
|
|
}
|
|
|
|
void
|
|
UnregisterXactCallback(XactCallback callback, void *arg)
|
|
{
|
|
XactCallbackItem *item;
|
|
XactCallbackItem *prev;
|
|
|
|
prev = NULL;
|
|
for (item = Xact_callbacks; item; prev = item, item = item->next)
|
|
{
|
|
if (item->callback == callback && item->arg == arg)
|
|
{
|
|
if (prev)
|
|
prev->next = item->next;
|
|
else
|
|
Xact_callbacks = item->next;
|
|
pfree(item);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
CallXactCallbacks(XactEvent event)
|
|
{
|
|
XactCallbackItem *item;
|
|
|
|
for (item = Xact_callbacks; item; item = item->next)
|
|
(*item->callback) (event, item->arg);
|
|
}
|
|
|
|
|
|
/*
|
|
* Register or deregister callback functions for start- and end-of-subxact
|
|
* operations.
|
|
*
|
|
* Pretty much same as above, but for subtransaction events.
|
|
*
|
|
* At subtransaction end, the callback occurs post-subcommit or post-subabort,
|
|
* so the callback functions can only do noncritical cleanup. At
|
|
* subtransaction start, the callback is called when the subtransaction has
|
|
* finished initializing.
|
|
*/
|
|
void
|
|
RegisterSubXactCallback(SubXactCallback callback, void *arg)
|
|
{
|
|
SubXactCallbackItem *item;
|
|
|
|
item = (SubXactCallbackItem *)
|
|
MemoryContextAlloc(TopMemoryContext, sizeof(SubXactCallbackItem));
|
|
item->callback = callback;
|
|
item->arg = arg;
|
|
item->next = SubXact_callbacks;
|
|
SubXact_callbacks = item;
|
|
}
|
|
|
|
void
|
|
UnregisterSubXactCallback(SubXactCallback callback, void *arg)
|
|
{
|
|
SubXactCallbackItem *item;
|
|
SubXactCallbackItem *prev;
|
|
|
|
prev = NULL;
|
|
for (item = SubXact_callbacks; item; prev = item, item = item->next)
|
|
{
|
|
if (item->callback == callback && item->arg == arg)
|
|
{
|
|
if (prev)
|
|
prev->next = item->next;
|
|
else
|
|
SubXact_callbacks = item->next;
|
|
pfree(item);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
CallSubXactCallbacks(SubXactEvent event,
|
|
SubTransactionId mySubid,
|
|
SubTransactionId parentSubid)
|
|
{
|
|
SubXactCallbackItem *item;
|
|
|
|
for (item = SubXact_callbacks; item; item = item->next)
|
|
(*item->callback) (event, mySubid, parentSubid, item->arg);
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* transaction block support
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* 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_PARALLEL_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. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
case TBLOCK_PREPARE:
|
|
elog(FATAL, "BeginTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* PrepareTransactionBlock
|
|
* This executes a PREPARE command.
|
|
*
|
|
* Since PREPARE may actually do a ROLLBACK, the result indicates what
|
|
* happened: TRUE for PREPARE, FALSE for ROLLBACK.
|
|
*
|
|
* Note that we don't actually do anything here except change blockState.
|
|
* The real work will be done in the upcoming PrepareTransaction().
|
|
* We do it this way because it's not convenient to change memory context,
|
|
* resource owner, etc while executing inside a Portal.
|
|
*/
|
|
bool
|
|
PrepareTransactionBlock(char *gid)
|
|
{
|
|
TransactionState s;
|
|
bool result;
|
|
|
|
/* Set up to commit the current transaction */
|
|
result = EndTransactionBlock();
|
|
|
|
/* If successful, change outer tblock state to PREPARE */
|
|
if (result)
|
|
{
|
|
s = CurrentTransactionState;
|
|
|
|
while (s->parent != NULL)
|
|
s = s->parent;
|
|
|
|
if (s->blockState == TBLOCK_END)
|
|
{
|
|
/* Save GID where PrepareTransaction can find it again */
|
|
prepareGID = MemoryContextStrdup(TopTransactionContext, gid);
|
|
|
|
s->blockState = TBLOCK_PREPARE;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* ignore case where we are not in a transaction;
|
|
* EndTransactionBlock already issued a warning.
|
|
*/
|
|
Assert(s->blockState == TBLOCK_STARTED);
|
|
/* Don't send back a PREPARE result tag... */
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* EndTransactionBlock
|
|
* This executes a COMMIT command.
|
|
*
|
|
* Since COMMIT may actually do a ROLLBACK, the result indicates what
|
|
* happened: TRUE for COMMIT, FALSE for ROLLBACK.
|
|
*
|
|
* Note that we don't actually do anything here except change blockState.
|
|
* The real work will be done in the upcoming CommitTransactionCommand().
|
|
* We do it this way because it's not convenient to change memory context,
|
|
* resource owner, etc while executing inside a Portal.
|
|
*/
|
|
bool
|
|
EndTransactionBlock(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
bool result = false;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* We are in a transaction block, so tell CommitTransactionCommand
|
|
* to COMMIT.
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
s->blockState = TBLOCK_END;
|
|
result = true;
|
|
break;
|
|
|
|
/*
|
|
* We are in a failed transaction block. Tell
|
|
* CommitTransactionCommand it's time to exit the block.
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
s->blockState = TBLOCK_ABORT_END;
|
|
break;
|
|
|
|
/*
|
|
* We are in a live subtransaction block. Set up to subcommit all
|
|
* open subtransactions and then commit the main transaction.
|
|
*/
|
|
case TBLOCK_SUBINPROGRESS:
|
|
while (s->parent != NULL)
|
|
{
|
|
if (s->blockState == TBLOCK_SUBINPROGRESS)
|
|
s->blockState = TBLOCK_SUBCOMMIT;
|
|
else
|
|
elog(FATAL, "EndTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
s = s->parent;
|
|
}
|
|
if (s->blockState == TBLOCK_INPROGRESS)
|
|
s->blockState = TBLOCK_END;
|
|
else
|
|
elog(FATAL, "EndTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
result = true;
|
|
break;
|
|
|
|
/*
|
|
* Here we are inside an aborted subtransaction. Treat the COMMIT
|
|
* as ROLLBACK: set up to abort everything and exit the main
|
|
* transaction.
|
|
*/
|
|
case TBLOCK_SUBABORT:
|
|
while (s->parent != NULL)
|
|
{
|
|
if (s->blockState == TBLOCK_SUBINPROGRESS)
|
|
s->blockState = TBLOCK_SUBABORT_PENDING;
|
|
else if (s->blockState == TBLOCK_SUBABORT)
|
|
s->blockState = TBLOCK_SUBABORT_END;
|
|
else
|
|
elog(FATAL, "EndTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
s = s->parent;
|
|
}
|
|
if (s->blockState == TBLOCK_INPROGRESS)
|
|
s->blockState = TBLOCK_ABORT_PENDING;
|
|
else if (s->blockState == TBLOCK_ABORT)
|
|
s->blockState = TBLOCK_ABORT_END;
|
|
else
|
|
elog(FATAL, "EndTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
|
|
/*
|
|
* The user issued COMMIT when not inside a transaction. Issue a
|
|
* WARNING, staying in TBLOCK_STARTED state. The upcoming call to
|
|
* CommitTransactionCommand() will then close the transaction and
|
|
* 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")));
|
|
result = true;
|
|
break;
|
|
|
|
/*
|
|
* The user issued a COMMIT that somehow ran inside a parallel
|
|
* worker. We can't cope with that.
|
|
*/
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
ereport(FATAL,
|
|
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
|
errmsg("cannot commit during a parallel operation")));
|
|
break;
|
|
|
|
/* These cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
case TBLOCK_PREPARE:
|
|
elog(FATAL, "EndTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* UserAbortTransactionBlock
|
|
* This executes a ROLLBACK command.
|
|
*
|
|
* As above, we don't actually do anything here except change blockState.
|
|
*/
|
|
void
|
|
UserAbortTransactionBlock(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* We are inside a transaction block and we got a ROLLBACK command
|
|
* from the user, so tell CommitTransactionCommand to abort and
|
|
* exit the transaction block.
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
s->blockState = TBLOCK_ABORT_PENDING;
|
|
break;
|
|
|
|
/*
|
|
* We are inside a failed transaction block and we got a ROLLBACK
|
|
* command from the user. Abort processing is already done, so
|
|
* CommitTransactionCommand just has to cleanup and go back to
|
|
* idle state.
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
s->blockState = TBLOCK_ABORT_END;
|
|
break;
|
|
|
|
/*
|
|
* We are inside a subtransaction. Mark everything up to top
|
|
* level as exitable.
|
|
*/
|
|
case TBLOCK_SUBINPROGRESS:
|
|
case TBLOCK_SUBABORT:
|
|
while (s->parent != NULL)
|
|
{
|
|
if (s->blockState == TBLOCK_SUBINPROGRESS)
|
|
s->blockState = TBLOCK_SUBABORT_PENDING;
|
|
else if (s->blockState == TBLOCK_SUBABORT)
|
|
s->blockState = TBLOCK_SUBABORT_END;
|
|
else
|
|
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
s = s->parent;
|
|
}
|
|
if (s->blockState == TBLOCK_INPROGRESS)
|
|
s->blockState = TBLOCK_ABORT_PENDING;
|
|
else if (s->blockState == TBLOCK_ABORT)
|
|
s->blockState = TBLOCK_ABORT_END;
|
|
else
|
|
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
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")));
|
|
s->blockState = TBLOCK_ABORT_PENDING;
|
|
break;
|
|
|
|
/*
|
|
* The user issued an ABORT that somehow ran inside a parallel
|
|
* worker. We can't cope with that.
|
|
*/
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
ereport(FATAL,
|
|
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
|
errmsg("cannot abort during a parallel operation")));
|
|
break;
|
|
|
|
/* These cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
case TBLOCK_PREPARE:
|
|
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* DefineSavepoint
|
|
* This executes a SAVEPOINT command.
|
|
*/
|
|
void
|
|
DefineSavepoint(char *name)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* Workers synchronize transaction state at the beginning of each parallel
|
|
* operation, so we can't account for new subtransactions after that
|
|
* point. (Note that this check will certainly error out if s->blockState
|
|
* is TBLOCK_PARALLEL_INPROGRESS, so we can treat that as an invalid case
|
|
* below.)
|
|
*/
|
|
if (IsInParallelMode())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
|
errmsg("cannot define savepoints during a parallel operation")));
|
|
|
|
switch (s->blockState)
|
|
{
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
/* Normal subtransaction start */
|
|
PushTransaction();
|
|
s = CurrentTransactionState; /* changed by push */
|
|
|
|
/*
|
|
* Savepoint names, like the TransactionState block itself, live
|
|
* in TopTransactionContext.
|
|
*/
|
|
if (name)
|
|
s->name = MemoryContextStrdup(TopTransactionContext, name);
|
|
break;
|
|
|
|
/* These cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
case TBLOCK_PREPARE:
|
|
elog(FATAL, "DefineSavepoint: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ReleaseSavepoint
|
|
* This executes a RELEASE command.
|
|
*
|
|
* As above, we don't actually do anything here except change blockState.
|
|
*/
|
|
void
|
|
ReleaseSavepoint(List *options)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
TransactionState target,
|
|
xact;
|
|
ListCell *cell;
|
|
char *name = NULL;
|
|
|
|
/*
|
|
* Workers synchronize transaction state at the beginning of each parallel
|
|
* operation, so we can't account for transaction state change after that
|
|
* point. (Note that this check will certainly error out if s->blockState
|
|
* is TBLOCK_PARALLEL_INPROGRESS, so we can treat that as an invalid case
|
|
* below.)
|
|
*/
|
|
if (IsInParallelMode())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
|
errmsg("cannot release savepoints during a parallel operation")));
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* We can't rollback to a savepoint if there is no savepoint
|
|
* defined.
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
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_PARALLEL_INPROGRESS:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
case TBLOCK_PREPARE:
|
|
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));
|
|
|
|
for (target = s; PointerIsValid(target); target = target->parent)
|
|
{
|
|
if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
|
|
break;
|
|
}
|
|
|
|
if (!PointerIsValid(target))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
|
|
/* disallow crossing savepoint level boundaries */
|
|
if (target->savepointLevel != s->savepointLevel)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
|
|
/*
|
|
* Mark "commit pending" all subtransactions up to the target
|
|
* subtransaction. The actual commits will happen when control gets to
|
|
* CommitTransactionCommand.
|
|
*/
|
|
xact = CurrentTransactionState;
|
|
for (;;)
|
|
{
|
|
Assert(xact->blockState == TBLOCK_SUBINPROGRESS);
|
|
xact->blockState = TBLOCK_SUBRELEASE;
|
|
if (xact == target)
|
|
break;
|
|
xact = xact->parent;
|
|
Assert(PointerIsValid(xact));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* RollbackToSavepoint
|
|
* This executes a ROLLBACK TO <savepoint> command.
|
|
*
|
|
* As above, we don't actually do anything here except change blockState.
|
|
*/
|
|
void
|
|
RollbackToSavepoint(List *options)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
TransactionState target,
|
|
xact;
|
|
ListCell *cell;
|
|
char *name = NULL;
|
|
|
|
/*
|
|
* Workers synchronize transaction state at the beginning of each parallel
|
|
* operation, so we can't account for transaction state change after that
|
|
* point. (Note that this check will certainly error out if s->blockState
|
|
* is TBLOCK_PARALLEL_INPROGRESS, so we can treat that as an invalid case
|
|
* below.)
|
|
*/
|
|
if (IsInParallelMode())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
|
errmsg("cannot rollback to savepoints during a parallel operation")));
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* We can't rollback to a savepoint if there is no savepoint
|
|
* defined.
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_ABORT:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
break;
|
|
|
|
/*
|
|
* There is at least one savepoint, so proceed.
|
|
*/
|
|
case TBLOCK_SUBINPROGRESS:
|
|
case TBLOCK_SUBABORT:
|
|
break;
|
|
|
|
/* These cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
case TBLOCK_PREPARE:
|
|
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));
|
|
|
|
for (target = s; PointerIsValid(target); target = target->parent)
|
|
{
|
|
if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
|
|
break;
|
|
}
|
|
|
|
if (!PointerIsValid(target))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
|
|
/* disallow crossing savepoint level boundaries */
|
|
if (target->savepointLevel != s->savepointLevel)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
|
|
/*
|
|
* Mark "abort pending" all subtransactions up to the target
|
|
* subtransaction. The actual aborts will happen when control gets to
|
|
* CommitTransactionCommand.
|
|
*/
|
|
xact = CurrentTransactionState;
|
|
for (;;)
|
|
{
|
|
if (xact == target)
|
|
break;
|
|
if (xact->blockState == TBLOCK_SUBINPROGRESS)
|
|
xact->blockState = TBLOCK_SUBABORT_PENDING;
|
|
else if (xact->blockState == TBLOCK_SUBABORT)
|
|
xact->blockState = TBLOCK_SUBABORT_END;
|
|
else
|
|
elog(FATAL, "RollbackToSavepoint: unexpected state %s",
|
|
BlockStateAsString(xact->blockState));
|
|
xact = xact->parent;
|
|
Assert(PointerIsValid(xact));
|
|
}
|
|
|
|
/* And mark the target as "restart pending" */
|
|
if (xact->blockState == TBLOCK_SUBINPROGRESS)
|
|
xact->blockState = TBLOCK_SUBRESTART;
|
|
else if (xact->blockState == TBLOCK_SUBABORT)
|
|
xact->blockState = TBLOCK_SUBABORT_RESTART;
|
|
else
|
|
elog(FATAL, "RollbackToSavepoint: unexpected state %s",
|
|
BlockStateAsString(xact->blockState));
|
|
}
|
|
|
|
/*
|
|
* BeginInternalSubTransaction
|
|
* This is the same as DefineSavepoint except it allows TBLOCK_STARTED,
|
|
* TBLOCK_END, and TBLOCK_PREPARE states, and therefore it can safely be
|
|
* used in functions that might be called when not inside a BEGIN block
|
|
* or when running deferred triggers at COMMIT/PREPARE time. Also, it
|
|
* automatically does CommitTransactionCommand/StartTransactionCommand
|
|
* instead of expecting the caller to do it.
|
|
*/
|
|
void
|
|
BeginInternalSubTransaction(char *name)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* Workers synchronize transaction state at the beginning of each parallel
|
|
* operation, so we can't account for new subtransactions after that
|
|
* point. We might be able to make an exception for the type of
|
|
* subtransaction established by this function, which is typically used in
|
|
* contexts where we're going to release or roll back the subtransaction
|
|
* before proceeding further, so that no enduring change to the
|
|
* transaction state occurs. For now, however, we prohibit this case along
|
|
* with all the others.
|
|
*/
|
|
if (IsInParallelMode())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
|
errmsg("cannot start subtransactions during a parallel operation")));
|
|
|
|
switch (s->blockState)
|
|
{
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_END:
|
|
case TBLOCK_PREPARE:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
/* Normal subtransaction start */
|
|
PushTransaction();
|
|
s = CurrentTransactionState; /* changed by push */
|
|
|
|
/*
|
|
* Savepoint names, like the TransactionState block itself, live
|
|
* in TopTransactionContext.
|
|
*/
|
|
if (name)
|
|
s->name = MemoryContextStrdup(TopTransactionContext, name);
|
|
break;
|
|
|
|
/* These cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
elog(FATAL, "BeginInternalSubTransaction: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
CommitTransactionCommand();
|
|
StartTransactionCommand();
|
|
}
|
|
|
|
/*
|
|
* ReleaseCurrentSubTransaction
|
|
*
|
|
* RELEASE (ie, commit) the innermost subtransaction, regardless of its
|
|
* savepoint name (if any).
|
|
* NB: do NOT use CommitTransactionCommand/StartTransactionCommand with this.
|
|
*/
|
|
void
|
|
ReleaseCurrentSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* Workers synchronize transaction state at the beginning of each parallel
|
|
* operation, so we can't account for commit of subtransactions after that
|
|
* point. This should not happen anyway. Code calling this would
|
|
* typically have called BeginInternalSubTransaction() first, failing
|
|
* there.
|
|
*/
|
|
if (IsInParallelMode())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
|
errmsg("cannot commit subtransactions during a parallel operation")));
|
|
|
|
if (s->blockState != TBLOCK_SUBINPROGRESS)
|
|
elog(ERROR, "ReleaseCurrentSubTransaction: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
Assert(s->state == TRANS_INPROGRESS);
|
|
MemoryContextSwitchTo(CurTransactionContext);
|
|
CommitSubTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
Assert(s->state == TRANS_INPROGRESS);
|
|
}
|
|
|
|
/*
|
|
* RollbackAndReleaseCurrentSubTransaction
|
|
*
|
|
* ROLLBACK and RELEASE (ie, abort) the innermost subtransaction, regardless
|
|
* of its savepoint name (if any).
|
|
* NB: do NOT use CommitTransactionCommand/StartTransactionCommand with this.
|
|
*/
|
|
void
|
|
RollbackAndReleaseCurrentSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* Unlike ReleaseCurrentSubTransaction(), this is nominally permitted
|
|
* during parallel operations. That's because we may be in the master,
|
|
* recovering from an error thrown while we were in parallel mode. We
|
|
* won't reach here in a worker, because BeginInternalSubTransaction()
|
|
* will have failed.
|
|
*/
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/* Must be in a subtransaction */
|
|
case TBLOCK_SUBINPROGRESS:
|
|
case TBLOCK_SUBABORT:
|
|
break;
|
|
|
|
/* These cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
case TBLOCK_PREPARE:
|
|
elog(FATAL, "RollbackAndReleaseCurrentSubTransaction: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Abort the current subtransaction, if needed.
|
|
*/
|
|
if (s->blockState == TBLOCK_SUBINPROGRESS)
|
|
AbortSubTransaction();
|
|
|
|
/* And clean it up, too */
|
|
CleanupSubTransaction();
|
|
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
AssertState(s->blockState == TBLOCK_SUBINPROGRESS ||
|
|
s->blockState == TBLOCK_INPROGRESS ||
|
|
s->blockState == TBLOCK_STARTED);
|
|
}
|
|
|
|
/*
|
|
* AbortOutOfAnyTransaction
|
|
*
|
|
* This routine is provided for error recovery purposes. It aborts any
|
|
* active transaction or transaction block, leaving the system in a known
|
|
* idle state.
|
|
*/
|
|
void
|
|
AbortOutOfAnyTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* Get out of any transaction or nested transaction
|
|
*/
|
|
do
|
|
{
|
|
switch (s->blockState)
|
|
{
|
|
case TBLOCK_DEFAULT:
|
|
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_INPROGRESS:
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
case TBLOCK_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_PREPARE:
|
|
/* In a transaction, so clean up */
|
|
AbortTransaction();
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_ABORT_END:
|
|
/* AbortTransaction already done, still need Cleanup */
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* In a subtransaction, so clean it up and abort parent too
|
|
*/
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
AbortSubTransaction();
|
|
CleanupSubTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
break;
|
|
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
/* As above, but AbortSubTransaction already done */
|
|
CleanupSubTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
break;
|
|
}
|
|
} while (s->blockState != TBLOCK_DEFAULT);
|
|
|
|
/* Should be out of all subxacts now */
|
|
Assert(s->parent == NULL);
|
|
}
|
|
|
|
/*
|
|
* 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_SUBBEGIN:
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_PARALLEL_INPROGRESS:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBRELEASE:
|
|
case TBLOCK_SUBCOMMIT:
|
|
case TBLOCK_PREPARE:
|
|
return 'T'; /* in transaction */
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_ABORT_END:
|
|
case TBLOCK_SUBABORT_END:
|
|
case TBLOCK_ABORT_PENDING:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBRESTART:
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
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;
|
|
|
|
if (s->nestingLevel >= 2)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* StartSubTransaction
|
|
*
|
|
* If you're wondering why this is separate from PushTransaction: it's because
|
|
* we can't conveniently do this stuff right inside DefineSavepoint. The
|
|
* SAVEPOINT utility command will be executed inside a Portal, and if we
|
|
* muck with CurrentMemoryContext or CurrentResourceOwner then exit from
|
|
* the Portal will undo those settings. So we make DefineSavepoint just
|
|
* push a dummy transaction block, and when control returns to the main
|
|
* idle loop, CommitTransactionCommand will be called, and we'll come here
|
|
* to finish starting the subtransaction.
|
|
*/
|
|
static void
|
|
StartSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->state != TRANS_DEFAULT)
|
|
elog(WARNING, "StartSubTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
s->state = TRANS_START;
|
|
|
|
/*
|
|
* Initialize subsystems for new subtransaction
|
|
*
|
|
* must initialize resource-management stuff first
|
|
*/
|
|
AtSubStart_Memory();
|
|
AtSubStart_ResourceOwner();
|
|
AtSubStart_Notify();
|
|
AfterTriggerBeginSubXact();
|
|
|
|
s->state = TRANS_INPROGRESS;
|
|
|
|
/*
|
|
* Call start-of-subxact callbacks
|
|
*/
|
|
CallSubXactCallbacks(SUBXACT_EVENT_START_SUB, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
|
|
ShowTransactionState("StartSubTransaction");
|
|
}
|
|
|
|
/*
|
|
* CommitSubTransaction
|
|
*
|
|
* The caller has to make sure to always reassign CurrentTransactionState
|
|
* if it has a local pointer to it after calling this function.
|
|
*/
|
|
static void
|
|
CommitSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
ShowTransactionState("CommitSubTransaction");
|
|
|
|
if (s->state != TRANS_INPROGRESS)
|
|
elog(WARNING, "CommitSubTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
/* Pre-commit processing goes here */
|
|
|
|
CallSubXactCallbacks(SUBXACT_EVENT_PRE_COMMIT_SUB, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
|
|
/* If in parallel mode, clean up workers and exit parallel mode. */
|
|
if (IsInParallelMode())
|
|
{
|
|
AtEOSubXact_Parallel(true, s->subTransactionId);
|
|
s->parallelModeLevel = 0;
|
|
}
|
|
|
|
/* 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
|
|
* perform that step, if required, as part of the atomic update of the
|
|
* whole transaction tree at top level commit or abort.
|
|
*/
|
|
|
|
/* Post-commit cleanup */
|
|
if (TransactionIdIsValid(s->transactionId))
|
|
AtSubCommit_childXids();
|
|
AfterTriggerEndSubXact(true);
|
|
AtSubCommit_Portals(s->subTransactionId,
|
|
s->parent->subTransactionId,
|
|
s->parent->curTransactionOwner);
|
|
AtEOSubXact_LargeObject(true, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtSubCommit_Notify();
|
|
|
|
CallSubXactCallbacks(SUBXACT_EVENT_COMMIT_SUB, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
|
|
ResourceOwnerRelease(s->curTransactionOwner,
|
|
RESOURCE_RELEASE_BEFORE_LOCKS,
|
|
true, false);
|
|
AtEOSubXact_RelationCache(true, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtEOSubXact_Inval(true);
|
|
AtSubCommit_smgr();
|
|
|
|
/*
|
|
* The only lock we actually release here is the subtransaction XID lock.
|
|
*/
|
|
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);
|
|
ResourceOwnerRelease(s->curTransactionOwner,
|
|
RESOURCE_RELEASE_AFTER_LOCKS,
|
|
true, false);
|
|
|
|
AtEOXact_GUC(true, s->gucNestLevel);
|
|
AtEOSubXact_SPI(true, s->subTransactionId);
|
|
AtEOSubXact_on_commit_actions(true, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtEOSubXact_Namespace(true, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtEOSubXact_Files(true, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtEOSubXact_HashTables(true, s->nestingLevel);
|
|
AtEOSubXact_PgStat(true, s->nestingLevel);
|
|
AtSubCommit_Snapshot(s->nestingLevel);
|
|
|
|
/*
|
|
* We need to restore the upper transaction's read-only state, in case the
|
|
* upper is read-write while the child is read-only; GUC will incorrectly
|
|
* think it should leave the child state in place.
|
|
*/
|
|
XactReadOnly = s->prevXactReadOnly;
|
|
|
|
CurrentResourceOwner = s->parent->curTransactionOwner;
|
|
CurTransactionResourceOwner = s->parent->curTransactionOwner;
|
|
ResourceOwnerDelete(s->curTransactionOwner);
|
|
s->curTransactionOwner = NULL;
|
|
|
|
AtSubCommit_Memory();
|
|
|
|
s->state = TRANS_DEFAULT;
|
|
|
|
PopTransaction();
|
|
}
|
|
|
|
/*
|
|
* AbortSubTransaction
|
|
*/
|
|
static void
|
|
AbortSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/* Prevent cancel/die interrupt while cleaning up */
|
|
HOLD_INTERRUPTS();
|
|
|
|
/* Make sure we have a valid memory context and resource owner */
|
|
AtSubAbort_Memory();
|
|
AtSubAbort_ResourceOwner();
|
|
|
|
/*
|
|
* 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();
|
|
|
|
pgstat_report_wait_end();
|
|
pgstat_progress_end_command();
|
|
AbortBufferIO();
|
|
UnlockBuffers();
|
|
|
|
/* Reset WAL record construction state */
|
|
XLogResetInsertion();
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
ShowTransactionState("AbortSubTransaction");
|
|
|
|
if (s->state != TRANS_INPROGRESS)
|
|
elog(WARNING, "AbortSubTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
s->state = TRANS_ABORT;
|
|
|
|
/*
|
|
* Reset user ID which might have been changed transiently. (See notes in
|
|
* AbortTransaction.)
|
|
*/
|
|
SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
|
|
|
|
/* Exit from parallel mode, if necessary. */
|
|
if (IsInParallelMode())
|
|
{
|
|
AtEOSubXact_Parallel(false, s->subTransactionId);
|
|
s->parallelModeLevel = 0;
|
|
}
|
|
|
|
/*
|
|
* We can skip all this stuff if the subxact failed before creating a
|
|
* ResourceOwner...
|
|
*/
|
|
if (s->curTransactionOwner)
|
|
{
|
|
AfterTriggerEndSubXact(false);
|
|
AtSubAbort_Portals(s->subTransactionId,
|
|
s->parent->subTransactionId,
|
|
s->curTransactionOwner,
|
|
s->parent->curTransactionOwner);
|
|
AtEOSubXact_LargeObject(false, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtSubAbort_Notify();
|
|
|
|
/* Advertise the fact that we aborted in pg_xact. */
|
|
(void) RecordTransactionAbort(true);
|
|
|
|
/* Post-abort cleanup */
|
|
if (TransactionIdIsValid(s->transactionId))
|
|
AtSubAbort_childXids();
|
|
|
|
CallSubXactCallbacks(SUBXACT_EVENT_ABORT_SUB, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
|
|
ResourceOwnerRelease(s->curTransactionOwner,
|
|
RESOURCE_RELEASE_BEFORE_LOCKS,
|
|
false, false);
|
|
AtEOSubXact_RelationCache(false, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtEOSubXact_Inval(false);
|
|
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);
|
|
AtEOSubXact_on_commit_actions(false, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtEOSubXact_Namespace(false, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtEOSubXact_Files(false, s->subTransactionId,
|
|
s->parent->subTransactionId);
|
|
AtEOSubXact_HashTables(false, s->nestingLevel);
|
|
AtEOSubXact_PgStat(false, s->nestingLevel);
|
|
AtSubAbort_Snapshot(s->nestingLevel);
|
|
}
|
|
|
|
/*
|
|
* Restore the upper transaction's read-only state, too. This should be
|
|
* redundant with GUC's cleanup but we may as well do it for consistency
|
|
* with the commit case.
|
|
*/
|
|
XactReadOnly = s->prevXactReadOnly;
|
|
|
|
RESUME_INTERRUPTS();
|
|
}
|
|
|
|
/*
|
|
* CleanupSubTransaction
|
|
*
|
|
* The caller has to make sure to always reassign CurrentTransactionState
|
|
* if it has a local pointer to it after calling this function.
|
|
*/
|
|
static void
|
|
CleanupSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
ShowTransactionState("CleanupSubTransaction");
|
|
|
|
if (s->state != TRANS_ABORT)
|
|
elog(WARNING, "CleanupSubTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
AtSubCleanup_Portals(s->subTransactionId);
|
|
|
|
CurrentResourceOwner = s->parent->curTransactionOwner;
|
|
CurTransactionResourceOwner = s->parent->curTransactionOwner;
|
|
if (s->curTransactionOwner)
|
|
ResourceOwnerDelete(s->curTransactionOwner);
|
|
s->curTransactionOwner = NULL;
|
|
|
|
AtSubCleanup_Memory();
|
|
|
|
s->state = TRANS_DEFAULT;
|
|
|
|
PopTransaction();
|
|
}
|
|
|
|
/*
|
|
* PushTransaction
|
|
* Create transaction state stack entry 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));
|
|
|
|
/*
|
|
* Assign a subtransaction ID, watching out for counter wraparound.
|
|
*/
|
|
currentSubTransactionId += 1;
|
|
if (currentSubTransactionId == InvalidSubTransactionId)
|
|
{
|
|
currentSubTransactionId -= 1;
|
|
pfree(s);
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
errmsg("cannot have more than 2^32-1 subtransactions in a transaction")));
|
|
}
|
|
|
|
/*
|
|
* We can now stack a minimally valid subtransaction without fear of
|
|
* failure.
|
|
*/
|
|
s->transactionId = InvalidTransactionId; /* until assigned */
|
|
s->subTransactionId = currentSubTransactionId;
|
|
s->parent = p;
|
|
s->nestingLevel = p->nestingLevel + 1;
|
|
s->gucNestLevel = NewGUCNestLevel();
|
|
s->savepointLevel = p->savepointLevel;
|
|
s->state = TRANS_DEFAULT;
|
|
s->blockState = TBLOCK_SUBBEGIN;
|
|
GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
|
|
s->prevXactReadOnly = XactReadOnly;
|
|
s->parallelModeLevel = 0;
|
|
|
|
CurrentTransactionState = s;
|
|
|
|
/*
|
|
* AbortSubTransaction and CleanupSubTransaction have to be able to cope
|
|
* with the subtransaction from here on out; in particular they should not
|
|
* assume that it necessarily has a transaction context, resource owner,
|
|
* or XID.
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* 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 while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
if (s->parent == NULL)
|
|
elog(FATAL, "PopTransaction with no parent");
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* EstimateTransactionStateSpace
|
|
* Estimate the amount of space that will be needed by
|
|
* SerializeTransactionState. It would be OK to overestimate slightly,
|
|
* but it's simple for us to work out the precise value, so we do.
|
|
*/
|
|
Size
|
|
EstimateTransactionStateSpace(void)
|
|
{
|
|
TransactionState s;
|
|
Size nxids = 6; /* iso level, deferrable, top & current XID,
|
|
* command counter, XID count */
|
|
|
|
for (s = CurrentTransactionState; s != NULL; s = s->parent)
|
|
{
|
|
if (TransactionIdIsValid(s->transactionId))
|
|
nxids = add_size(nxids, 1);
|
|
nxids = add_size(nxids, s->nChildXids);
|
|
}
|
|
|
|
nxids = add_size(nxids, nParallelCurrentXids);
|
|
return mul_size(nxids, sizeof(TransactionId));
|
|
}
|
|
|
|
/*
|
|
* SerializeTransactionState
|
|
* Write out relevant details of our transaction state that will be
|
|
* needed by a parallel worker.
|
|
*
|
|
* We need to save and restore XactDeferrable, XactIsoLevel, and the XIDs
|
|
* associated with this transaction. The first eight bytes of the result
|
|
* contain XactDeferrable and XactIsoLevel; the next twelve bytes contain the
|
|
* XID of the top-level transaction, the XID of the current transaction
|
|
* (or, in each case, InvalidTransactionId if none), and the current command
|
|
* counter. After that, the next 4 bytes contain a count of how many
|
|
* additional XIDs follow; this is followed by all of those XIDs one after
|
|
* another. We emit the XIDs in sorted order for the convenience of the
|
|
* receiving process.
|
|
*/
|
|
void
|
|
SerializeTransactionState(Size maxsize, char *start_address)
|
|
{
|
|
TransactionState s;
|
|
Size nxids = 0;
|
|
Size i = 0;
|
|
Size c = 0;
|
|
TransactionId *workspace;
|
|
TransactionId *result = (TransactionId *) start_address;
|
|
|
|
result[c++] = (TransactionId) XactIsoLevel;
|
|
result[c++] = (TransactionId) XactDeferrable;
|
|
result[c++] = XactTopTransactionId;
|
|
result[c++] = CurrentTransactionState->transactionId;
|
|
result[c++] = (TransactionId) currentCommandId;
|
|
Assert(maxsize >= c * sizeof(TransactionId));
|
|
|
|
/*
|
|
* If we're running in a parallel worker and launching a parallel worker
|
|
* of our own, we can just pass along the information that was passed to
|
|
* us.
|
|
*/
|
|
if (nParallelCurrentXids > 0)
|
|
{
|
|
result[c++] = nParallelCurrentXids;
|
|
Assert(maxsize >= (nParallelCurrentXids + c) * sizeof(TransactionId));
|
|
memcpy(&result[c], ParallelCurrentXids,
|
|
nParallelCurrentXids * sizeof(TransactionId));
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* OK, we need to generate a sorted list of XIDs that our workers should
|
|
* view as current. First, figure out how many there are.
|
|
*/
|
|
for (s = CurrentTransactionState; s != NULL; s = s->parent)
|
|
{
|
|
if (TransactionIdIsValid(s->transactionId))
|
|
nxids = add_size(nxids, 1);
|
|
nxids = add_size(nxids, s->nChildXids);
|
|
}
|
|
Assert((c + 1 + nxids) * sizeof(TransactionId) <= maxsize);
|
|
|
|
/* Copy them to our scratch space. */
|
|
workspace = palloc(nxids * sizeof(TransactionId));
|
|
for (s = CurrentTransactionState; s != NULL; s = s->parent)
|
|
{
|
|
if (TransactionIdIsValid(s->transactionId))
|
|
workspace[i++] = s->transactionId;
|
|
memcpy(&workspace[i], s->childXids,
|
|
s->nChildXids * sizeof(TransactionId));
|
|
i += s->nChildXids;
|
|
}
|
|
Assert(i == nxids);
|
|
|
|
/* Sort them. */
|
|
qsort(workspace, nxids, sizeof(TransactionId), xidComparator);
|
|
|
|
/* Copy data into output area. */
|
|
result[c++] = (TransactionId) nxids;
|
|
memcpy(&result[c], workspace, nxids * sizeof(TransactionId));
|
|
}
|
|
|
|
/*
|
|
* StartParallelWorkerTransaction
|
|
* Start a parallel worker transaction, restoring the relevant
|
|
* transaction state serialized by SerializeTransactionState.
|
|
*/
|
|
void
|
|
StartParallelWorkerTransaction(char *tstatespace)
|
|
{
|
|
TransactionId *tstate = (TransactionId *) tstatespace;
|
|
|
|
Assert(CurrentTransactionState->blockState == TBLOCK_DEFAULT);
|
|
StartTransaction();
|
|
|
|
XactIsoLevel = (int) tstate[0];
|
|
XactDeferrable = (bool) tstate[1];
|
|
XactTopTransactionId = tstate[2];
|
|
CurrentTransactionState->transactionId = tstate[3];
|
|
currentCommandId = tstate[4];
|
|
nParallelCurrentXids = (int) tstate[5];
|
|
ParallelCurrentXids = &tstate[6];
|
|
|
|
CurrentTransactionState->blockState = TBLOCK_PARALLEL_INPROGRESS;
|
|
}
|
|
|
|
/*
|
|
* EndParallelWorkerTransaction
|
|
* End a parallel worker transaction.
|
|
*/
|
|
void
|
|
EndParallelWorkerTransaction(void)
|
|
{
|
|
Assert(CurrentTransactionState->blockState == TBLOCK_PARALLEL_INPROGRESS);
|
|
CommitTransaction();
|
|
CurrentTransactionState->blockState = TBLOCK_DEFAULT;
|
|
}
|
|
|
|
/*
|
|
* ShowTransactionState
|
|
* Debug support
|
|
*/
|
|
static void
|
|
ShowTransactionState(const char *str)
|
|
{
|
|
/* skip work if message will definitely not be printed */
|
|
if (log_min_messages <= DEBUG5 || client_min_messages <= DEBUG5)
|
|
ShowTransactionStateRec(str, CurrentTransactionState);
|
|
}
|
|
|
|
/*
|
|
* ShowTransactionStateRec
|
|
* Recursive subroutine for ShowTransactionState
|
|
*/
|
|
static void
|
|
ShowTransactionStateRec(const char *str, TransactionState s)
|
|
{
|
|
StringInfoData buf;
|
|
|
|
initStringInfo(&buf);
|
|
|
|
if (s->nChildXids > 0)
|
|
{
|
|
int i;
|
|
|
|
appendStringInfo(&buf, ", children: %u", s->childXids[0]);
|
|
for (i = 1; i < s->nChildXids; i++)
|
|
appendStringInfo(&buf, " %u", s->childXids[i]);
|
|
}
|
|
|
|
if (s->parent)
|
|
ShowTransactionStateRec(str, s->parent);
|
|
|
|
/* use ereport to suppress computation if msg will not be printed */
|
|
ereport(DEBUG5,
|
|
(errmsg_internal("%s(%d) name: %s; blockState: %s; state: %s, xid/subid/cid: %u/%u/%u%s%s",
|
|
str, s->nestingLevel,
|
|
PointerIsValid(s->name) ? s->name : "unnamed",
|
|
BlockStateAsString(s->blockState),
|
|
TransStateAsString(s->state),
|
|
(unsigned int) s->transactionId,
|
|
(unsigned int) s->subTransactionId,
|
|
(unsigned int) currentCommandId,
|
|
currentCommandIdUsed ? " (used)" : "",
|
|
buf.data)));
|
|
|
|
pfree(buf.data);
|
|
}
|
|
|
|
/*
|
|
* 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_PARALLEL_INPROGRESS:
|
|
return "PARALLEL_INPROGRESS";
|
|
case TBLOCK_END:
|
|
return "END";
|
|
case TBLOCK_ABORT:
|
|
return "ABORT";
|
|
case TBLOCK_ABORT_END:
|
|
return "ABORT END";
|
|
case TBLOCK_ABORT_PENDING:
|
|
return "ABORT PEND";
|
|
case TBLOCK_PREPARE:
|
|
return "PREPARE";
|
|
case TBLOCK_SUBBEGIN:
|
|
return "SUB BEGIN";
|
|
case TBLOCK_SUBINPROGRESS:
|
|
return "SUB INPROGRS";
|
|
case TBLOCK_SUBRELEASE:
|
|
return "SUB RELEASE";
|
|
case TBLOCK_SUBCOMMIT:
|
|
return "SUB COMMIT";
|
|
case TBLOCK_SUBABORT:
|
|
return "SUB ABORT";
|
|
case TBLOCK_SUBABORT_END:
|
|
return "SUB ABORT END";
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
return "SUB ABRT PEND";
|
|
case TBLOCK_SUBRESTART:
|
|
return "SUB RESTART";
|
|
case TBLOCK_SUBABORT_RESTART:
|
|
return "SUB AB RESTRT";
|
|
}
|
|
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_INPROGRESS:
|
|
return "INPROGR";
|
|
case TRANS_COMMIT:
|
|
return "COMMIT";
|
|
case TRANS_ABORT:
|
|
return "ABORT";
|
|
case TRANS_PREPARE:
|
|
return "PREPARE";
|
|
}
|
|
return "UNRECOGNIZED";
|
|
}
|
|
|
|
/*
|
|
* xactGetCommittedChildren
|
|
*
|
|
* 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!).
|
|
* If there are no subxacts, *ptr is set to NULL.
|
|
*/
|
|
int
|
|
xactGetCommittedChildren(TransactionId **ptr)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->nChildXids == 0)
|
|
*ptr = NULL;
|
|
else
|
|
*ptr = s->childXids;
|
|
|
|
return s->nChildXids;
|
|
}
|
|
|
|
/*
|
|
* XLOG support routines
|
|
*/
|
|
|
|
|
|
/*
|
|
* Log the commit record for a plain or twophase transaction commit.
|
|
*
|
|
* A 2pc commit will be emitted when twophase_xid is valid, a plain one
|
|
* otherwise.
|
|
*/
|
|
XLogRecPtr
|
|
XactLogCommitRecord(TimestampTz commit_time,
|
|
int nsubxacts, TransactionId *subxacts,
|
|
int nrels, RelFileNode *rels,
|
|
int nmsgs, SharedInvalidationMessage *msgs,
|
|
bool relcacheInval, bool forceSync,
|
|
int xactflags, TransactionId twophase_xid)
|
|
{
|
|
xl_xact_commit xlrec;
|
|
xl_xact_xinfo xl_xinfo;
|
|
xl_xact_dbinfo xl_dbinfo;
|
|
xl_xact_subxacts xl_subxacts;
|
|
xl_xact_relfilenodes xl_relfilenodes;
|
|
xl_xact_invals xl_invals;
|
|
xl_xact_twophase xl_twophase;
|
|
xl_xact_origin xl_origin;
|
|
|
|
uint8 info;
|
|
|
|
Assert(CritSectionCount > 0);
|
|
|
|
xl_xinfo.xinfo = 0;
|
|
|
|
/* decide between a plain and 2pc commit */
|
|
if (!TransactionIdIsValid(twophase_xid))
|
|
info = XLOG_XACT_COMMIT;
|
|
else
|
|
info = XLOG_XACT_COMMIT_PREPARED;
|
|
|
|
/* First figure out and collect all the information needed */
|
|
|
|
xlrec.xact_time = commit_time;
|
|
|
|
if (relcacheInval)
|
|
xl_xinfo.xinfo |= XACT_COMPLETION_UPDATE_RELCACHE_FILE;
|
|
if (forceSyncCommit)
|
|
xl_xinfo.xinfo |= XACT_COMPLETION_FORCE_SYNC_COMMIT;
|
|
if ((xactflags & XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK))
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_AE_LOCKS;
|
|
|
|
/*
|
|
* Check if the caller would like to ask standbys for immediate feedback
|
|
* once this commit is applied.
|
|
*/
|
|
if (synchronous_commit >= SYNCHRONOUS_COMMIT_REMOTE_APPLY)
|
|
xl_xinfo.xinfo |= XACT_COMPLETION_APPLY_FEEDBACK;
|
|
|
|
/*
|
|
* Relcache invalidations requires information about the current database
|
|
* and so does logical decoding.
|
|
*/
|
|
if (nmsgs > 0 || XLogLogicalInfoActive())
|
|
{
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_DBINFO;
|
|
xl_dbinfo.dbId = MyDatabaseId;
|
|
xl_dbinfo.tsId = MyDatabaseTableSpace;
|
|
}
|
|
|
|
if (nsubxacts > 0)
|
|
{
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_SUBXACTS;
|
|
xl_subxacts.nsubxacts = nsubxacts;
|
|
}
|
|
|
|
if (nrels > 0)
|
|
{
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
|
|
xl_relfilenodes.nrels = nrels;
|
|
}
|
|
|
|
if (nmsgs > 0)
|
|
{
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_INVALS;
|
|
xl_invals.nmsgs = nmsgs;
|
|
}
|
|
|
|
if (TransactionIdIsValid(twophase_xid))
|
|
{
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
|
|
xl_twophase.xid = twophase_xid;
|
|
}
|
|
|
|
/* dump transaction origin information */
|
|
if (replorigin_session_origin != InvalidRepOriginId)
|
|
{
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_ORIGIN;
|
|
|
|
xl_origin.origin_lsn = replorigin_session_origin_lsn;
|
|
xl_origin.origin_timestamp = replorigin_session_origin_timestamp;
|
|
}
|
|
|
|
if (xl_xinfo.xinfo != 0)
|
|
info |= XLOG_XACT_HAS_INFO;
|
|
|
|
/* Then include all the collected data into the commit record. */
|
|
|
|
XLogBeginInsert();
|
|
|
|
XLogRegisterData((char *) (&xlrec), sizeof(xl_xact_commit));
|
|
|
|
if (xl_xinfo.xinfo != 0)
|
|
XLogRegisterData((char *) (&xl_xinfo.xinfo), sizeof(xl_xinfo.xinfo));
|
|
|
|
if (xl_xinfo.xinfo & XACT_XINFO_HAS_DBINFO)
|
|
XLogRegisterData((char *) (&xl_dbinfo), sizeof(xl_dbinfo));
|
|
|
|
if (xl_xinfo.xinfo & XACT_XINFO_HAS_SUBXACTS)
|
|
{
|
|
XLogRegisterData((char *) (&xl_subxacts),
|
|
MinSizeOfXactSubxacts);
|
|
XLogRegisterData((char *) subxacts,
|
|
nsubxacts * sizeof(TransactionId));
|
|
}
|
|
|
|
if (xl_xinfo.xinfo & XACT_XINFO_HAS_RELFILENODES)
|
|
{
|
|
XLogRegisterData((char *) (&xl_relfilenodes),
|
|
MinSizeOfXactRelfilenodes);
|
|
XLogRegisterData((char *) rels,
|
|
nrels * sizeof(RelFileNode));
|
|
}
|
|
|
|
if (xl_xinfo.xinfo & XACT_XINFO_HAS_INVALS)
|
|
{
|
|
XLogRegisterData((char *) (&xl_invals), MinSizeOfXactInvals);
|
|
XLogRegisterData((char *) msgs,
|
|
nmsgs * sizeof(SharedInvalidationMessage));
|
|
}
|
|
|
|
if (xl_xinfo.xinfo & XACT_XINFO_HAS_TWOPHASE)
|
|
XLogRegisterData((char *) (&xl_twophase), sizeof(xl_xact_twophase));
|
|
|
|
if (xl_xinfo.xinfo & XACT_XINFO_HAS_ORIGIN)
|
|
XLogRegisterData((char *) (&xl_origin), sizeof(xl_xact_origin));
|
|
|
|
/* we allow filtering by xacts */
|
|
XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
|
|
|
|
return XLogInsert(RM_XACT_ID, info);
|
|
}
|
|
|
|
/*
|
|
* Log the commit record for a plain or twophase transaction abort.
|
|
*
|
|
* A 2pc abort will be emitted when twophase_xid is valid, a plain one
|
|
* otherwise.
|
|
*/
|
|
XLogRecPtr
|
|
XactLogAbortRecord(TimestampTz abort_time,
|
|
int nsubxacts, TransactionId *subxacts,
|
|
int nrels, RelFileNode *rels,
|
|
int xactflags, TransactionId twophase_xid)
|
|
{
|
|
xl_xact_abort xlrec;
|
|
xl_xact_xinfo xl_xinfo;
|
|
xl_xact_subxacts xl_subxacts;
|
|
xl_xact_relfilenodes xl_relfilenodes;
|
|
xl_xact_twophase xl_twophase;
|
|
|
|
uint8 info;
|
|
|
|
Assert(CritSectionCount > 0);
|
|
|
|
xl_xinfo.xinfo = 0;
|
|
|
|
/* decide between a plain and 2pc abort */
|
|
if (!TransactionIdIsValid(twophase_xid))
|
|
info = XLOG_XACT_ABORT;
|
|
else
|
|
info = XLOG_XACT_ABORT_PREPARED;
|
|
|
|
|
|
/* First figure out and collect all the information needed */
|
|
|
|
xlrec.xact_time = abort_time;
|
|
|
|
if ((xactflags & XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK))
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_AE_LOCKS;
|
|
|
|
if (nsubxacts > 0)
|
|
{
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_SUBXACTS;
|
|
xl_subxacts.nsubxacts = nsubxacts;
|
|
}
|
|
|
|
if (nrels > 0)
|
|
{
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
|
|
xl_relfilenodes.nrels = nrels;
|
|
}
|
|
|
|
if (TransactionIdIsValid(twophase_xid))
|
|
{
|
|
xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
|
|
xl_twophase.xid = twophase_xid;
|
|
}
|
|
|
|
if (xl_xinfo.xinfo != 0)
|
|
info |= XLOG_XACT_HAS_INFO;
|
|
|
|
/* Then include all the collected data into the abort record. */
|
|
|
|
XLogBeginInsert();
|
|
|
|
XLogRegisterData((char *) (&xlrec), MinSizeOfXactAbort);
|
|
|
|
if (xl_xinfo.xinfo != 0)
|
|
XLogRegisterData((char *) (&xl_xinfo), sizeof(xl_xinfo));
|
|
|
|
if (xl_xinfo.xinfo & XACT_XINFO_HAS_SUBXACTS)
|
|
{
|
|
XLogRegisterData((char *) (&xl_subxacts),
|
|
MinSizeOfXactSubxacts);
|
|
XLogRegisterData((char *) subxacts,
|
|
nsubxacts * sizeof(TransactionId));
|
|
}
|
|
|
|
if (xl_xinfo.xinfo & XACT_XINFO_HAS_RELFILENODES)
|
|
{
|
|
XLogRegisterData((char *) (&xl_relfilenodes),
|
|
MinSizeOfXactRelfilenodes);
|
|
XLogRegisterData((char *) rels,
|
|
nrels * sizeof(RelFileNode));
|
|
}
|
|
|
|
if (xl_xinfo.xinfo & XACT_XINFO_HAS_TWOPHASE)
|
|
XLogRegisterData((char *) (&xl_twophase), sizeof(xl_xact_twophase));
|
|
|
|
return XLogInsert(RM_XACT_ID, info);
|
|
}
|
|
|
|
/*
|
|
* 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_parsed_commit *parsed,
|
|
TransactionId xid,
|
|
XLogRecPtr lsn,
|
|
RepOriginId origin_id)
|
|
{
|
|
TransactionId max_xid;
|
|
int i;
|
|
TimestampTz commit_time;
|
|
|
|
Assert(TransactionIdIsValid(xid));
|
|
|
|
max_xid = TransactionIdLatest(xid, parsed->nsubxacts, parsed->subxacts);
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
Assert(((parsed->xinfo & XACT_XINFO_HAS_ORIGIN) == 0) ==
|
|
(origin_id == InvalidRepOriginId));
|
|
|
|
if (parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
|
|
commit_time = parsed->origin_timestamp;
|
|
else
|
|
commit_time = parsed->xact_time;
|
|
|
|
/* Set the transaction commit timestamp and metadata */
|
|
TransactionTreeSetCommitTsData(xid, parsed->nsubxacts, parsed->subxacts,
|
|
commit_time, origin_id, false);
|
|
|
|
if (standbyState == STANDBY_DISABLED)
|
|
{
|
|
/*
|
|
* Mark the transaction committed in pg_xact.
|
|
*/
|
|
TransactionIdCommitTree(xid, parsed->nsubxacts, parsed->subxacts);
|
|
}
|
|
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_xact. 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, parsed->nsubxacts, parsed->subxacts, lsn);
|
|
|
|
/*
|
|
* We must mark clog before we update the ProcArray.
|
|
*/
|
|
ExpireTreeKnownAssignedTransactionIds(
|
|
xid, parsed->nsubxacts, parsed->subxacts, 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(
|
|
parsed->msgs, parsed->nmsgs,
|
|
XactCompletionRelcacheInitFileInval(parsed->xinfo),
|
|
parsed->dbId, parsed->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.
|
|
*/
|
|
if (parsed->xinfo & XACT_XINFO_HAS_AE_LOCKS)
|
|
StandbyReleaseLockTree(xid, 0, NULL);
|
|
}
|
|
|
|
if (parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
|
|
{
|
|
/* recover apply progress */
|
|
replorigin_advance(origin_id, parsed->origin_lsn, lsn,
|
|
false /* backward */ , false /* WAL */ );
|
|
}
|
|
|
|
/* Make sure files supposed to be dropped are dropped */
|
|
if (parsed->nrels > 0)
|
|
{
|
|
/*
|
|
* 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 (i = 0; i < parsed->nrels; i++)
|
|
{
|
|
SMgrRelation srel = smgropen(parsed->xnodes[i], InvalidBackendId);
|
|
ForkNumber fork;
|
|
|
|
for (fork = 0; fork <= MAX_FORKNUM; fork++)
|
|
XLogDropRelation(parsed->xnodes[i], fork);
|
|
smgrdounlink(srel, true);
|
|
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(parsed->xinfo))
|
|
XLogFlush(lsn);
|
|
|
|
/*
|
|
* If asked by the primary (because someone is waiting for a synchronous
|
|
* commit = remote_apply), we will need to ask walreceiver to send a reply
|
|
* immediately.
|
|
*/
|
|
if (XactCompletionApplyFeedback(parsed->xinfo))
|
|
XLogRequestWalReceiverReply();
|
|
}
|
|
|
|
/*
|
|
* 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_parsed_abort *parsed, TransactionId xid)
|
|
{
|
|
int i;
|
|
TransactionId max_xid;
|
|
|
|
Assert(TransactionIdIsValid(xid));
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
max_xid = TransactionIdLatest(xid,
|
|
parsed->nsubxacts,
|
|
parsed->subxacts);
|
|
|
|
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_xact, no need for async stuff */
|
|
TransactionIdAbortTree(xid, parsed->nsubxacts, parsed->subxacts);
|
|
}
|
|
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_xact, no need for async stuff */
|
|
TransactionIdAbortTree(xid, parsed->nsubxacts, parsed->subxacts);
|
|
|
|
/*
|
|
* We must update the ProcArray after we have marked clog.
|
|
*/
|
|
ExpireTreeKnownAssignedTransactionIds(
|
|
xid, parsed->nsubxacts, parsed->subxacts, 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.
|
|
*/
|
|
if (parsed->xinfo & XACT_XINFO_HAS_AE_LOCKS)
|
|
StandbyReleaseLockTree(xid, parsed->nsubxacts, parsed->subxacts);
|
|
}
|
|
|
|
/* Make sure files supposed to be dropped are dropped */
|
|
for (i = 0; i < parsed->nrels; i++)
|
|
{
|
|
SMgrRelation srel = smgropen(parsed->xnodes[i], InvalidBackendId);
|
|
ForkNumber fork;
|
|
|
|
for (fork = 0; fork <= MAX_FORKNUM; fork++)
|
|
XLogDropRelation(parsed->xnodes[i], fork);
|
|
smgrdounlink(srel, true);
|
|
smgrclose(srel);
|
|
}
|
|
}
|
|
|
|
void
|
|
xact_redo(XLogReaderState *record)
|
|
{
|
|
uint8 info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK;
|
|
|
|
/* Backup blocks are not used in xact records */
|
|
Assert(!XLogRecHasAnyBlockRefs(record));
|
|
|
|
if (info == XLOG_XACT_COMMIT)
|
|
{
|
|
xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record);
|
|
xl_xact_parsed_commit parsed;
|
|
|
|
ParseCommitRecord(XLogRecGetInfo(record), xlrec, &parsed);
|
|
xact_redo_commit(&parsed, XLogRecGetXid(record),
|
|
record->EndRecPtr, XLogRecGetOrigin(record));
|
|
}
|
|
else if (info == XLOG_XACT_COMMIT_PREPARED)
|
|
{
|
|
xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record);
|
|
xl_xact_parsed_commit parsed;
|
|
|
|
ParseCommitRecord(XLogRecGetInfo(record), xlrec, &parsed);
|
|
xact_redo_commit(&parsed, parsed.twophase_xid,
|
|
record->EndRecPtr, XLogRecGetOrigin(record));
|
|
|
|
/* Delete TwoPhaseState gxact entry and/or 2PC file. */
|
|
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
|
|
PrepareRedoRemove(parsed.twophase_xid, false);
|
|
LWLockRelease(TwoPhaseStateLock);
|
|
}
|
|
else if (info == XLOG_XACT_ABORT)
|
|
{
|
|
xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record);
|
|
xl_xact_parsed_abort parsed;
|
|
|
|
ParseAbortRecord(XLogRecGetInfo(record), xlrec, &parsed);
|
|
xact_redo_abort(&parsed, XLogRecGetXid(record));
|
|
}
|
|
else if (info == XLOG_XACT_ABORT_PREPARED)
|
|
{
|
|
xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record);
|
|
xl_xact_parsed_abort parsed;
|
|
|
|
ParseAbortRecord(XLogRecGetInfo(record), xlrec, &parsed);
|
|
xact_redo_abort(&parsed, parsed.twophase_xid);
|
|
|
|
/* Delete TwoPhaseState gxact entry and/or 2PC file. */
|
|
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
|
|
PrepareRedoRemove(parsed.twophase_xid, false);
|
|
LWLockRelease(TwoPhaseStateLock);
|
|
}
|
|
else if (info == XLOG_XACT_PREPARE)
|
|
{
|
|
/*
|
|
* Store xid and start/end pointers of the WAL record in TwoPhaseState
|
|
* gxact entry.
|
|
*/
|
|
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
|
|
PrepareRedoAdd(XLogRecGetData(record),
|
|
record->ReadRecPtr,
|
|
record->EndRecPtr);
|
|
LWLockRelease(TwoPhaseStateLock);
|
|
}
|
|
else if (info == XLOG_XACT_ASSIGNMENT)
|
|
{
|
|
xl_xact_assignment *xlrec = (xl_xact_assignment *) XLogRecGetData(record);
|
|
|
|
if (standbyState >= STANDBY_INITIALIZED)
|
|
ProcArrayApplyXidAssignment(xlrec->xtop,
|
|
xlrec->nsubxacts, xlrec->xsub);
|
|
}
|
|
else
|
|
elog(PANIC, "xact_redo: unknown op code %u", info);
|
|
}
|