#include "access/subtrans.h"
#include "access/transam.h"
#include "access/twophase.h"
+#include "access/undorequest.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
TRANS_INPROGRESS, /* inside a valid transaction */
TRANS_COMMIT, /* commit in progress */
TRANS_ABORT, /* abort in progress */
- TRANS_PREPARE /* prepare in progress */
+ TRANS_PREPARE, /* prepare in progress */
+ TRANS_UNDO /* undo apply in progress */
} TransState;
/*
TBLOCK_ABORT_END, /* failed xact, ROLLBACK received */
TBLOCK_ABORT_PENDING, /* live xact, ROLLBACK received */
TBLOCK_PREPARE, /* live xact, PREPARE received */
+ TBLOCK_UNDO, /* failed xact, awaiting undo to be applied */
/* subtransaction states */
TBLOCK_SUBBEGIN, /* starting a subtransaction */
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 */
+ TBLOCK_SUBABORT_RESTART, /* failed subxact, ROLLBACK TO received */
+ TBLOCK_SUBUNDO /* failed subxact, awaiting undo to be applied */
} TBlockState;
/*
bool didLogXid; /* has xid been included in WAL record? */
int parallelModeLevel; /* Enter/ExitParallelMode counter */
bool chain; /* start a new block after this one */
+
+ /* start and end undo record location for each persistence level */
+ UndoRecPtr start_urec_ptr[UndoLogCategories]; /* this is 'to' location */
+ UndoRecPtr latest_urec_ptr[UndoLogCategories]; /* this is 'from'
+ * location */
+ bool undo_req_pushed[UndoLogCategories]; /* undo request pushed
+ * to worker? */
+ bool performUndoActions;
+
struct TransactionStateData *parent; /* back link to parent */
} TransactionStateData;
static void ShowTransactionStateRec(const char *str, TransactionState state);
static const char *BlockStateAsString(TBlockState blockState);
static const char *TransStateAsString(TransState state);
+static void PushUndoRequest(void);
/* ----------------------------------------------------------------
* 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.
+ * state is TRANS_INPROGRESS or TRANS_UNDO.
*/
- return (s->state == TRANS_INPROGRESS);
+ return (s->state == TRANS_INPROGRESS || s->state == TRANS_UNDO);
}
/*
{
TransactionState s;
+ /*
+ * The subtransaction is not considered active if it is being aborted or
+ * in undo apply state, even though it may still have an entry on the
+ * state stack.
+ */
for (s = CurrentTransactionState; s != NULL; s = s->parent)
{
- if (s->state == TRANS_ABORT)
+ if (s->state == TRANS_ABORT || s->state == TRANS_UNDO)
continue;
if (s->subTransactionId == subxid)
return true;
* 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.
+ * or in undo apply state 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)
+ if (s->state == TRANS_ABORT || s->state == TRANS_UNDO)
continue;
if (!FullTransactionIdIsValid(s->fullTransactionId))
continue; /* it can't have any child XIDs either */
{
TransactionState s;
VirtualTransactionId vxid;
+ int i;
/*
* Let's just make sure the state stack is empty
nUnreportedXids = 0;
s->didLogXid = false;
+ /* initialize undo record locations for the transaction */
+ for (i = 0; i < UndoLogCategories; i++)
+ {
+ s->start_urec_ptr[i] = InvalidUndoRecPtr;
+ s->latest_urec_ptr[i] = InvalidUndoRecPtr;
+ s->undo_req_pushed[i] = false;
+ }
+ s->performUndoActions = false;
+
/*
* must initialize resource-management stuff first
*/
XactTopFullTransactionId = InvalidFullTransactionId;
nParallelCurrentXids = 0;
+ ResetUndoActionsInfo();
+
/*
* done with commit processing, set current transaction state back to
* default
* NB: if you change this routine, better look at CommitTransaction too!
*/
static void
-PrepareTransaction(void)
+PrepareTransaction(UndoRecPtr *start_urec_ptr, UndoRecPtr *end_urec_ptr)
{
TransactionState s = CurrentTransactionState;
TransactionId xid = GetCurrentTransactionId();
* PREPARED; in particular, pay attention to whether things should happen
* before or after releasing the transaction's locks.
*/
- StartPrepare(gxact);
+ StartPrepare(gxact, start_urec_ptr, end_urec_ptr);
AtPrepare_Notify();
AtPrepare_Locks();
* check the current transaction state
*/
is_parallel_worker = (s->blockState == TBLOCK_PARALLEL_INPROGRESS);
- if (s->state != TRANS_INPROGRESS && s->state != TRANS_PREPARE)
+ if (s->state != TRANS_INPROGRESS &&
+ s->state != TRANS_PREPARE &&
+ s->state != TRANS_UNDO)
elog(WARNING, "AbortTransaction while in %s state",
TransStateAsString(s->state));
Assert(s->parent == NULL);
XactTopFullTransactionId = InvalidFullTransactionId;
nParallelCurrentXids = 0;
+ ResetUndoActionsInfo();
+
/*
* done with abort processing, set current transaction state back to
* default
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
case TBLOCK_PREPARE:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(ERROR, "StartTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState));
break;
* StartTransactionCommand didn't set the STARTED state
* appropriately, while TBLOCK_PARALLEL_INPROGRESS should be ended
* by EndParallelWorkerTransaction(), not this function.
+ *
+ * TBLOCK_(SUB)UNDO means the error has occurred while applying
+ * undo for a (sub)transaction. We can't reach here as while
+ * applying undo via top-level transaction, if we get an error,
+ * then it is handled by ApplyUndoActions and for subtransaction,
+ * we promote the error to fatal in such a situation.
*/
case TBLOCK_DEFAULT:
case TBLOCK_PARALLEL_INPROGRESS:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(FATAL, "CommitTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState));
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.
+ * told us to ROLLBACK anyway. We have to abort the transaction,
+ * apply the undo actions if any and then clean up.
*/
case TBLOCK_ABORT_PENDING:
+ UndoActionsRequired();
+ PushUndoRequest();
AbortTransaction();
+ ApplyUndoActions();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
if (s->chain)
* return to the idle state.
*/
case TBLOCK_PREPARE:
- PrepareTransaction();
+ PrepareTransaction(s->start_urec_ptr, s->latest_urec_ptr);
s->blockState = TBLOCK_DEFAULT;
break;
case TBLOCK_SUBCOMMIT:
do
{
+ int i;
+
+ /*
+ * Before cleaning up the current sub transaction state,
+ * overwrite parent transaction's latest_urec_ptr with current
+ * transaction's latest_urec_ptr so that in case parent
+ * transaction get aborted we must not skip performing undo
+ * for this transaction. Also set the start_urec_ptr if
+ * parent start_urec_ptr is not valid.
+ */
+ for (i = 0; i < UndoLogCategories; i++)
+ {
+ if (UndoRecPtrIsValid(s->latest_urec_ptr[i]))
+ s->parent->latest_urec_ptr[i] = s->latest_urec_ptr[i];
+ if (!UndoRecPtrIsValid(s->parent->start_urec_ptr[i]))
+ s->parent->start_urec_ptr[i] = s->start_urec_ptr[i];
+ }
+
CommitSubTransaction();
s = CurrentTransactionState; /* changed by pop */
} while (s->blockState == TBLOCK_SUBCOMMIT);
else if (s->blockState == TBLOCK_PREPARE)
{
Assert(s->parent == NULL);
- PrepareTransaction();
+ PrepareTransaction(s->start_urec_ptr, s->latest_urec_ptr);
s->blockState = TBLOCK_DEFAULT;
}
else
* As above, but it's not dead yet, so abort first.
*/
case TBLOCK_SUBABORT_PENDING:
+ UndoActionsRequired();
+ PushUndoRequest();
AbortSubTransaction();
+ ApplyUndoActions();
CleanupSubTransaction();
CommitTransactionCommand();
break;
s->name = NULL;
savepointLevel = s->savepointLevel;
+ UndoActionsRequired();
+ PushUndoRequest();
AbortSubTransaction();
+ ApplyUndoActions();
CleanupSubTransaction();
DefineSavepoint(NULL);
{
TransactionState s = CurrentTransactionState;
+ /*
+ * Here, we just detect whether there are any pending undo actions so that
+ * we can skip releasing the locks during abort transaction. We don't
+ * release the locks till we execute undo actions otherwise, there is a
+ * risk of deadlock.
+ */
+ UndoActionsRequired();
+
switch (s->blockState)
{
case TBLOCK_DEFAULT:
* incompletely started transaction. First, adjust the
* low-level state to suppress warning message from
* AbortTransaction.
+ *
+ * In this state, we must not have performed any operation
+ * which can generate undo.
*/
+ Assert(!s->performUndoActions);
if (s->state == TRANS_START)
s->state = TRANS_INPROGRESS;
AbortTransaction();
*/
case TBLOCK_STARTED:
case TBLOCK_IMPLICIT_INPROGRESS:
+ PushUndoRequest();
AbortTransaction();
+ ApplyUndoActions();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
break;
* 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.
+ *
+ * In this state, we must not have performed any operation which
+ * which can generate undo.
*/
case TBLOCK_BEGIN:
+ Assert(!s->performUndoActions);
AbortTransaction();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
*/
case TBLOCK_INPROGRESS:
case TBLOCK_PARALLEL_INPROGRESS:
+ PushUndoRequest();
AbortTransaction();
+ ApplyUndoActions();
s->blockState = TBLOCK_ABORT;
/* CleanupTransaction happens when we exit TBLOCK_ABORT_END */
break;
* the transaction).
*/
case TBLOCK_END:
+ PushUndoRequest();
AbortTransaction();
+ ApplyUndoActions();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
break;
* Abort, cleanup, go to idle state.
*/
case TBLOCK_ABORT_PENDING:
+ PushUndoRequest();
AbortTransaction();
+ ApplyUndoActions();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
break;
* the transaction).
*/
case TBLOCK_PREPARE:
+ PushUndoRequest();
AbortTransaction();
+ ApplyUndoActions();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
break;
* we get ROLLBACK.
*/
case TBLOCK_SUBINPROGRESS:
+ PushUndoRequest();
AbortSubTransaction();
+ ApplyUndoActions();
s->blockState = TBLOCK_SUBABORT;
break;
case TBLOCK_SUBCOMMIT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBRESTART:
+ PushUndoRequest();
AbortSubTransaction();
+ ApplyUndoActions();
CleanupSubTransaction();
AbortCurrentTransaction();
break;
CleanupSubTransaction();
AbortCurrentTransaction();
break;
+
+ /*
+ * The error occurred while applying undo for a (sub)transaction.
+ * We can't reach here as while applying undo via top-level
+ * transaction, if we get an error, then it is handled by
+ * ApplyUndoActions and for subtransaction, we promote the error
+ * to fatal in such a situation.
+ */
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
+ elog(FATAL, "AbortCurrentTransaction: unexpected state %s",
+ BlockStateAsString(s->blockState));
+ break;
}
}
+/*
+ * PushUndoRequest - Register the request for apllying undo actions.
+ *
+ * It sets the transaction state to indicate whether the request is pushed to
+ * the background worker which is used later to decide whether to apply the
+ * actions.
+ *
+ * It is important to do this before marking the transaction as aborted in
+ * clog otherwise, it is quite possible that discard worker miss this rollback
+ * request from the computation of oldestXidHavingUnappliedUndo. This is
+ * because it might do that computation before backend can register it in the
+ * rollback hash table. So, neither oldestXmin computation will consider it
+ * nor the hash table pass would have that value.
+ */
+static void
+PushUndoRequest()
+{
+ TransactionState s = CurrentTransactionState;
+ bool result;
+ volatile int per_level;
+
+ if (!s->performUndoActions)
+ return;
+ /*
+ * We can't postpone applying undo actions for subtransactions as the
+ * modifications made by aborted subtransaction must not be visible even if
+ * the main transaction commits.
+ */
+ if (IsSubTransaction())
+ return;
+
+ for (per_level = 0; per_level < UndoLogCategories; per_level++)
+ {
+ /*
+ * We can't push the undo actions for temp table to background
+ * workers as the the temp tables are only accessible in the
+ * backend that has created them.
+ */
+ if (per_level != UNDO_TEMP && s->latest_urec_ptr[per_level])
+ {
+ result = RegisterRollbackReq(s->latest_urec_ptr[per_level],
+ s->start_urec_ptr[per_level],
+ MyDatabaseId,
+ GetTopFullTransactionId());
+ s->undo_req_pushed[per_level] = result;
+ }
+ }
+}
+
+/*
+ * ApplyUndoActions - Execute undo actions for current (sub)xact.
+ *
+ * To execute undo actions during abort, we bring the transaction to a clean
+ * state by releasing the required resources and put it in a new state
+ * TRANS_UNDO.
+ *
+ * Note that we release locks after applying undo actions. We skip them
+ * during Abort(Sub)Transaction as otherwise there is always a risk of
+ * deadlock when we need to re-take them during processing of undo actions.
+ */
+void
+ApplyUndoActions(void)
+{
+ TransactionState s = CurrentTransactionState;
+ bool ret;
+
+ if (!s->performUndoActions)
+ return;
+
+ /*
+ * State should still be TRANS_ABORT from AbortTransaction().
+ */
+ if (s->state != TRANS_ABORT)
+ elog(FATAL, "ApplyUndoActions: unexpected state %s",
+ TransStateAsString(s->state));
+
+ /*
+ * We promote the error level to FATAL if we get an error while applying
+ * undo for the subtransaction. See errstart. So, we should never reach
+ * here for such a case.
+ */
+ Assert(!applying_subxact_undo);
+
+ /*
+ * Do abort cleanup processing before applying the undo actions. We must
+ * do this before applying the undo actions to remove the effects of
+ * failed transaction.
+ */
+ if (IsSubTransaction())
+ {
+ AtSubCleanup_Portals(s->subTransactionId);
+ s->blockState = TBLOCK_SUBUNDO;
+ applying_subxact_undo = true;
+
+ /* We can't afford to allow cancel of subtransaction's rollback. */
+ HOLD_CANCEL_INTERRUPTS();
+ }
+ else
+ {
+ AtCleanup_Portals(); /* now safe to release portal memory */
+ AtEOXact_Snapshot(false, true); /* and release the transaction's
+ * snapshots */
+ s->fullTransactionId = InvalidFullTransactionId;
+ s->subTransactionId = TopSubTransactionId;
+ s->blockState = TBLOCK_UNDO;
+ }
+
+ s->state = TRANS_UNDO;
+
+ ret = PerformUndoActions(GetTopFullTransactionId(), MyDatabaseId,
+ s->latest_urec_ptr, s->start_urec_ptr,
+ s->undo_req_pushed,
+ IsSubTransaction());
+
+ if (!ret)
+ {
+ /*
+ * This should take care of releasing the locks held under
+ * TopTransactionResourceOwner.
+ */
+ AbortTransaction();
+ }
+
+ /* Reset undo information */
+ ResetUndoActionsInfo();
+
+ applying_subxact_undo = false;
+
+ /* Release the locks after applying undo actions. */
+ if (IsSubTransaction())
+ {
+ ResourceOwnerRelease(s->curTransactionOwner,
+ RESOURCE_RELEASE_LOCKS,
+ false, false);
+ RESUME_CANCEL_INTERRUPTS();
+ }
+ else
+ {
+ ResourceOwnerRelease(s->curTransactionOwner,
+ RESOURCE_RELEASE_LOCKS,
+ false, true);
+ }
+
+ /*
+ * Here we again put back the transaction in abort state so that callers
+ * can proceed with the cleanup work.
+ */
+ s->state = TRANS_ABORT;
+}
+
/*
* PreventInTransactionBlock
*
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
case TBLOCK_PREPARE:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(FATAL, "BeginTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
case TBLOCK_PREPARE:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
case TBLOCK_PREPARE:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
case TBLOCK_PREPARE:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(FATAL, "DefineSavepoint: unexpected state %s",
BlockStateAsString(s->blockState));
break;
TransactionState s = CurrentTransactionState;
TransactionState target,
xact;
+ UndoRecPtr latest_urec_ptr[UndoLogCategories];
+ UndoRecPtr start_urec_ptr[UndoLogCategories];
+ int i = 0;
+
+ /*
+ * Remember the 'from' and 'to' locations of the current transaction so
+ * that we can propagate it to parent transaction. This is required
+ * because in case the parent transaction get aborted we must not skip
+ * performing undo for this transaction.
+ */
+ memcpy(latest_urec_ptr, s->latest_urec_ptr, sizeof(latest_urec_ptr));
+ memcpy(start_urec_ptr, s->start_urec_ptr, sizeof(start_urec_ptr));
/*
* Workers synchronize transaction state at the beginning of each parallel
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
case TBLOCK_PREPARE:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(FATAL, "ReleaseSavepoint: unexpected state %s",
BlockStateAsString(s->blockState));
break;
if (xact == target)
break;
xact = xact->parent;
+
+ /*
+ * Propagate the 'from' and 'to' undo locations to parent transaction.
+ */
+ for (i = 0; i < UndoLogCategories; i++)
+ {
+ if (!UndoRecPtrIsValid(latest_urec_ptr[i]))
+ latest_urec_ptr[i] = xact->latest_urec_ptr[i];
+
+ if (UndoRecPtrIsValid(xact->start_urec_ptr[i]))
+ start_urec_ptr[i] = xact->start_urec_ptr[i];
+ }
+
+
Assert(PointerIsValid(xact));
}
+
+ /*
+ * Before cleaning up the current sub transaction state, overwrite parent
+ * transaction's latest_urec_ptr with current transaction's
+ * latest_urec_ptr so that in case parent transaction get aborted we will
+ * not skip performing undo for this transaction. Also set the
+ * start_urec_ptr if parent start_urec_ptr is not valid.
+ */
+ for (i = 0; i < UndoLogCategories; i++)
+ {
+ if (UndoRecPtrIsValid(latest_urec_ptr[i]))
+ xact->parent->latest_urec_ptr[i] = latest_urec_ptr[i];
+ if (!UndoRecPtrIsValid(xact->parent->start_urec_ptr[i]))
+ xact->parent->start_urec_ptr[i] = start_urec_ptr[i];
+ }
}
/*
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
case TBLOCK_PREPARE:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(FATAL, "RollbackToSavepoint: unexpected state %s",
BlockStateAsString(s->blockState));
break;
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(FATAL, "BeginInternalSubTransaction: unexpected state %s",
BlockStateAsString(s->blockState));
break;
ReleaseCurrentSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
+ int i;
/*
* Workers synchronize transaction state at the beginning of each parallel
BlockStateAsString(s->blockState));
Assert(s->state == TRANS_INPROGRESS);
MemoryContextSwitchTo(CurTransactionContext);
+
+ /*
+ * Before cleaning up the current sub transaction state, overwrite parent
+ * transaction's latest_urec_ptr with current transaction's
+ * latest_urec_ptr so that in case parent transaction get aborted we will
+ * not skip performing undo for this transaction.
+ */
+ for (i = 0; i < UndoLogCategories; i++)
+ {
+ if (UndoRecPtrIsValid(s->latest_urec_ptr[i]))
+ s->parent->latest_urec_ptr[i] = s->latest_urec_ptr[i];
+
+ if (!UndoRecPtrIsValid(s->parent->start_urec_ptr[i]))
+ s->parent->start_urec_ptr[i] = s->start_urec_ptr[i];
+ }
+
CommitSubTransaction();
s = CurrentTransactionState; /* changed by pop */
Assert(s->state == TRANS_INPROGRESS);
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
case TBLOCK_PREPARE:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
elog(FATAL, "RollbackAndReleaseCurrentSubTransaction: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
+ /*
+ * Set the information required to perform undo actions. Note that, it
+ * must be done before AbortSubTransaction as we need to skip releasing
+ * locks if that is the case. See ApplyUndoActions.
+ */
+ UndoActionsRequired();
+
+ /* Try to push rollback request to worker if possible. */
+ PushUndoRequest();
+
/*
* Abort the current subtransaction, if needed.
*/
if (s->blockState == TBLOCK_SUBINPROGRESS)
AbortSubTransaction();
+ /* Execute undo actions */
+ ApplyUndoActions();
+
/* And clean it up, too */
CleanupSubTransaction();
*/
do
{
+ /*
+ * Here, we just detect whether there are any pending undo actions so that
+ * we can skip releasing the locks during abort transaction. We don't
+ * release the locks till we execute undo actions otherwise, there is a
+ * risk of deadlock.
+ */
+ UndoActionsRequired();
+
switch (s->blockState)
{
case TBLOCK_DEFAULT:
* incompletely started transaction. First, adjust the
* low-level state to suppress warning message from
* AbortTransaction.
+ *
+ * In this state, we must not have performed any operation
+ * which can generate undo.
*/
+ Assert(!s->performUndoActions);
if (s->state == TRANS_START)
s->state = TRANS_INPROGRESS;
AbortTransaction();
case TBLOCK_ABORT_PENDING:
case TBLOCK_PREPARE:
/* In a transaction, so clean up */
+ PushUndoRequest();
+ AbortTransaction();
+ ApplyUndoActions();
+ CleanupTransaction();
+ s->blockState = TBLOCK_DEFAULT;
+ break;
+ case TBLOCK_UNDO:
+
+ /*
+ * We reach here when we got error while applying undo
+ * actions, so we don't want to again start applying it. Undo
+ * workers can take care of it.
+ */
+ ResetUndoActionsInfo();
AbortTransaction();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
case TBLOCK_SUBCOMMIT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBRESTART:
+ PushUndoRequest();
+ AbortSubTransaction();
+ ApplyUndoActions();
+ CleanupSubTransaction();
+ s = CurrentTransactionState; /* changed by pop */
+ break;
+ case TBLOCK_SUBUNDO:
+
+ /*
+ * We reach here when we got error while applying undo
+ * actions, so we don't want to again start applying it. Undo
+ * workers can take care of it.
+ */
+ ResetUndoActionsInfo();
AbortSubTransaction();
CleanupSubTransaction();
s = CurrentTransactionState; /* changed by pop */
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
+ case TBLOCK_UNDO:
+ case TBLOCK_SUBUNDO:
return 'E'; /* in failed transaction */
}
StartSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
+ int i;
if (s->state != TRANS_DEFAULT)
elog(WARNING, "StartSubTransaction while in %s state",
AtSubStart_Notify();
AfterTriggerBeginSubXact();
+ /* initialize undo record locations for the transaction */
+ for (i = 0; i < UndoLogCategories; i++)
+ {
+ s->start_urec_ptr[i] = InvalidUndoRecPtr;
+ s->latest_urec_ptr[i] = InvalidUndoRecPtr;
+ s->undo_req_pushed[i] = false;
+ }
+ s->performUndoActions = false;
+
s->state = TRANS_INPROGRESS;
/*
*/
ShowTransactionState("AbortSubTransaction");
- if (s->state != TRANS_INPROGRESS)
+ if (s->state != TRANS_INPROGRESS &&
+ s->state != TRANS_UNDO)
elog(WARNING, "AbortSubTransaction while in %s state",
TransStateAsString(s->state));
return "ABORT_PENDING";
case TBLOCK_PREPARE:
return "PREPARE";
+ case TBLOCK_UNDO:
+ return "UNDO";
case TBLOCK_SUBBEGIN:
return "SUBBEGIN";
case TBLOCK_SUBINPROGRESS:
return "SUBRESTART";
case TBLOCK_SUBABORT_RESTART:
return "SUBABORT_RESTART";
+ case TBLOCK_SUBUNDO:
+ return "SUBUNDO";
}
return "UNRECOGNIZED";
}
return "ABORT";
case TRANS_PREPARE:
return "PREPARE";
+ case TRANS_UNDO:
+ return "UNDO";
}
return "UNRECOGNIZED";
}
else
elog(PANIC, "xact_redo: unknown op code %u", info);
}
+
+/*
+ * UndoActionsRequired - Set the information required to perform undo actions.
+ *
+ * This function needs to be called before we release the locks during abort
+ * so that we can skip releasing the locks if required.
+ */
+void
+UndoActionsRequired(void)
+{
+ TransactionState s = CurrentTransactionState;
+ int i;
+
+ for (i = 0; i < UndoLogCategories; i++)
+ {
+ if (s->latest_urec_ptr[i])
+ {
+ s->performUndoActions = true;
+ break;
+ }
+ }
+}
+
+/*
+ * ResetUndoActionsInfo - reset the start and end undo record pointers.
+ */
+void
+ResetUndoActionsInfo(void)
+{
+ TransactionState s = CurrentTransactionState;
+ int i;
+
+ s->performUndoActions = false;
+ for (i = 0; i < UndoLogCategories; i++)
+ {
+ s->start_urec_ptr[i] = InvalidUndoRecPtr;
+ s->latest_urec_ptr[i] = InvalidUndoRecPtr;
+ }
+}
+
+/*
+ * CanPerformUndoActions - Returns true, if the current transaction can
+ * perform undo actions, false otherwise.
+ */
+bool
+CanPerformUndoActions(void)
+{
+ TransactionState s = CurrentTransactionState;
+
+ return s->performUndoActions;
+}
+
+/*
+ * PerformUndoActions - Perform undo actions for all the undo logs.
+ *
+ * Returns true, if we are able to successfully perform the actions,
+ * false, otherwise.
+ */
+bool
+PerformUndoActions(FullTransactionId fxid, Oid dbid, UndoRecPtr *end_urec_ptr,
+ UndoRecPtr *start_urec_ptr, bool *undo_req_pushed,
+ bool isSubTrans)
+{
+ volatile UndoRequestInfo urinfo;
+ uint32 save_holdoff;
+ int per_level;
+ bool success = true;
+
+ for (per_level = 0; per_level < UndoLogCategories; per_level++)
+ {
+ if (end_urec_ptr[per_level] && !undo_req_pushed[per_level])
+ {
+ save_holdoff = InterruptHoldoffCount;
+
+ PG_TRY();
+ {
+ /*
+ * Prepare required undo request info so that it can be used in
+ * exception.
+ */
+ ResetUndoRequestInfo(&urinfo);
+ urinfo.dbid = dbid;
+ urinfo.full_xid = fxid;
+ urinfo.start_urec_ptr = start_urec_ptr[per_level];
+
+ /* for subtransactions, we do partial rollback. */
+ execute_undo_actions(urinfo.full_xid,
+ end_urec_ptr[per_level],
+ start_urec_ptr[per_level],
+ !isSubTrans);
+ }
+ PG_CATCH();
+ {
+ if (per_level == UNDO_TEMP)
+ pg_rethrow_as_fatal();
+
+ /*
+ * Add the request into an error queue so that it can be
+ * processed in a timely fashion.
+ *
+ * If we fail to add the request in an error queue, then mark
+ * the entry status as invalid and continue to process the
+ * remaining undo requests if any. This request will be later
+ * added back to the queue by discard worker.
+ */
+ if (!InsertRequestIntoErrorUndoQueue(&urinfo))
+ RollbackHTMarkEntryInvalid(urinfo.full_xid,
+ urinfo.start_urec_ptr);
+ /*
+ * Errors can reset holdoff count, so restore back. This is
+ * required because this function can be called after holding
+ * interrupts.
+ */
+ InterruptHoldoffCount = save_holdoff;
+
+ /* Send the error only to server log. */
+ err_out_to_client(false);
+ EmitErrorReport();
+
+ success = false;
+
+ /*
+ * We promote the error level to FATAL if we get an error
+ * while applying undo for the subtransaction. See errstart.
+ * So, we should never reach here for such a case.
+ */
+ Assert(!applying_subxact_undo);
+ }
+ PG_END_TRY();
+ }
+ }
+
+ return success;
+}
+
+/*
+ * SetCurrentUndoLocation
+ *
+ * Sets the 'from' and 'to' location for the current transaction.
+ */
+void
+SetCurrentUndoLocation(UndoRecPtr urec_ptr, UndoLogCategory category)
+{
+ /*
+ * Set the start undo record pointer for first undo record in a
+ * subtransaction.
+ */
+ if (!UndoRecPtrIsValid(CurrentTransactionState->start_urec_ptr[category]))
+ CurrentTransactionState->start_urec_ptr[category] = urec_ptr;
+ CurrentTransactionState->latest_urec_ptr[category] = urec_ptr;
+
+}