Allow foreground transactions to perform undo actions on abort.
authorAmit Kapila <[email protected]>
Thu, 13 Jun 2019 10:12:46 +0000 (15:42 +0530)
committerKuntal Ghosh <[email protected]>
Fri, 19 Jul 2019 08:48:51 +0000 (14:18 +0530)
We always perform rollback actions after cleaning up the current
(sub)transaction.  This will ensure that we perform the actions immediately
after an error (and release the locks) rather than when the user issues
Rollback command at some later point of time.  We are releasing the locks
after the undo actions are applied.  The reason to delay lock release is
that if we release locks before applying undo actions, then the parallel
session can acquire the lock before us which can lead to deadlock.

Amit Kapila and Dilip Kumar  with inputs from Robert Haas

13 files changed:
src/backend/access/transam/twophase.c
src/backend/access/transam/varsup.c
src/backend/access/transam/xact.c
src/backend/access/undo/README.UndoProcessing [new file with mode: 0644]
src/backend/access/undo/undoaccess.c
src/backend/utils/error/elog.c
src/backend/utils/init/globals.c
src/backend/utils/resowner/resowner.c
src/include/access/transam.h
src/include/access/twophase.h
src/include/access/xact.h
src/include/miscadmin.h
src/include/utils/elog.h

index 477709bbc23c3fecbec2042513abe1f932a79d2a..c401885465c4a1e0c41d4a00c9fafd29548ce620 100644 (file)
@@ -82,6 +82,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/twophase_rmgr.h"
+#include "access/undorequest.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
@@ -927,6 +928,16 @@ typedef struct TwoPhaseFileHeader
        uint16          gidlen;                 /* length of the GID - GID follows the header */
        XLogRecPtr      origin_lsn;             /* lsn of this record at origin node */
        TimestampTz origin_timestamp;   /* time of prepare at origin node */
+
+       /*
+        * We need the locations of the start and end undo record pointers when
+        * rollbacks are to be performed for prepared transactions using undo-based
+        * relations.  We need to store this information in the file as the user
+        * might rollback the prepared transaction after recovery and for that we
+        * need it's start and end undo locations.
+        */
+       UndoRecPtr      start_urec_ptr[UndoLogCategories];
+       UndoRecPtr      end_urec_ptr[UndoLogCategories];
 } TwoPhaseFileHeader;
 
 /*
@@ -1001,7 +1012,8 @@ save_state_data(const void *data, uint32 len)
  * Initializes data structure and inserts the 2PC file header record.
  */
 void
-StartPrepare(GlobalTransaction gxact)
+StartPrepare(GlobalTransaction gxact, UndoRecPtr *start_urec_ptr,
+                        UndoRecPtr *end_urec_ptr)
 {
        PGPROC     *proc = &ProcGlobal->allProcs[gxact->pgprocno];
        PGXACT     *pgxact = &ProcGlobal->allPgXact[gxact->pgprocno];
@@ -1032,6 +1044,11 @@ StartPrepare(GlobalTransaction gxact)
        hdr.database = proc->databaseId;
        hdr.prepared_at = gxact->prepared_at;
        hdr.owner = gxact->owner;
+
+       /* save the start and end undo record pointers */
+       memcpy(hdr.start_urec_ptr, start_urec_ptr, sizeof(hdr.start_urec_ptr));
+       memcpy(hdr.end_urec_ptr, end_urec_ptr, sizeof(hdr.end_urec_ptr));
+
        hdr.nsubxacts = xactGetCommittedChildren(&children);
        hdr.ncommitrels = smgrGetPendingDeletes(true, &commitrels);
        hdr.nabortrels = smgrGetPendingDeletes(false, &abortrels);
@@ -1468,6 +1485,12 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
        RelFileNode *delrels;
        int                     ndelrels;
        SharedInvalidationMessage *invalmsgs;
+       UndoRecPtr      start_urec_ptr[UndoLogCategories];
+       UndoRecPtr      end_urec_ptr[UndoLogCategories];
+       bool            undo_action_pushed[UndoLogCategories];
+       uint32          epoch;
+       int                     i;
+       FullTransactionId full_xid;
 
        /*
         * Validate the GID, and lock the GXACT to ensure that two backends do not
@@ -1505,6 +1528,10 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
        invalmsgs = (SharedInvalidationMessage *) bufptr;
        bufptr += MAXALIGN(hdr->ninvalmsgs * sizeof(SharedInvalidationMessage));
 
+       /* save the start and end undo record pointers */
+       memcpy(start_urec_ptr, hdr->start_urec_ptr, sizeof(start_urec_ptr));
+       memcpy(end_urec_ptr, hdr->end_urec_ptr, sizeof(end_urec_ptr));
+
        /* compute latestXid among all children */
        latestXid = TransactionIdLatest(xid, hdr->nsubxacts, children);
 
@@ -1518,6 +1545,13 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
         * TransactionIdIsInProgress will stop saying the prepared xact is in
         * progress), then run the post-commit or post-abort callbacks. The
         * callbacks will release the locks the transaction held.
+        *
+        * XXX Note that, unlike non-prepared transactions, we don't skip
+        * releasing the locks when we have to perform the undo actions.  The
+        * reason is that here the locks are not directly associated with current
+        * transaction, rather it has to acquire those locks to apply undo actions.
+        * So, if we don't release the locks for prepared transaction, the undo
+        * applying transaction will wait forever.
         */
        if (isCommit)
                RecordTransactionCommitPrepared(xid,
@@ -1526,10 +1560,36 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
                                                                                hdr->ninvalmsgs, invalmsgs,
                                                                                hdr->initfileinval, gid);
        else
+       {
+               /*
+                * We don't allow XIDs with an age of more than 2 billion in undo, so
+                * we can infer the epoch here. (XXX We can add full transaction id in
+                * TwoPhaseFileHeader instead.)
+                */
+               epoch = GetEpochForXid(hdr->xid);
+               full_xid = FullTransactionIdFromEpochAndXid(epoch, hdr->xid);
+
+               /*
+                * Register the rollback request to apply undo actions.  It is
+                * important to do this before marking it aborted in clog, see
+                * comments atop PushUndoRequest for further details.
+                */
+               for (i = 0; i < UndoLogCategories; i++)
+               {
+                       if (end_urec_ptr[i] != InvalidUndoRecPtr && i != UNDO_TEMP)
+                       {
+                               undo_action_pushed[i] = RegisterRollbackReq(end_urec_ptr[i],
+                                                                                                                       start_urec_ptr[i],
+                                                                                                                       hdr->database,
+                                                                                                                       full_xid);
+                       }
+               }
+
                RecordTransactionAbortPrepared(xid,
                                                                           hdr->nsubxacts, children,
                                                                           hdr->nabortrels, abortrels,
                                                                           gid);
+       }
 
        ProcArrayRemove(proc, latestXid);
 
@@ -1612,6 +1672,25 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
 
        MyLockedGxact = NULL;
 
+       if (!isCommit)
+       {
+               /*
+                * Perform undo actions, if there are undologs for this transaction.
+                * We need to perform undo actions while we are still in a transaction.
+                */
+               if (!PerformUndoActions(full_xid, hdr->database, end_urec_ptr,
+                                                               start_urec_ptr, undo_action_pushed,
+                                                               false))
+               {
+                       /* Abort the failed transaction. */
+                       AbortOutOfAnyTransaction();
+                       FlushErrorState();
+
+                       /* Restart our transaction. */
+                       StartTransactionCommand();
+               }
+       }
+
        RESUME_INTERRUPTS();
 
        pfree(buf);
index 5b759ec7f3f6011fc13df33ab8fd78817e4c3221..fd019893020b3193057618504232eee041d900e6 100644 (file)
@@ -566,3 +566,27 @@ GetNewObjectId(void)
 
        return result;
 }
+
+/*
+ * Get epoch for the given xid.
+ */
+uint32
+GetEpochForXid(TransactionId xid)
+{
+       FullTransactionId next_fxid;
+       TransactionId next_xid;
+       uint32          epoch;
+
+       next_fxid = ReadNextFullTransactionId();
+       next_xid = XidFromFullTransactionId(next_fxid);
+       epoch = EpochFromFullTransactionId(next_fxid);
+
+       /*
+        * If xid is numerically bigger than next_xid, it has to be from the last
+        * epoch.
+        */
+       if (unlikely(xid > next_xid))
+               epoch--;
+
+       return epoch;
+}
index d7930c077de028a76f94ec18e0786457e921f4f0..d760796475ff1be211b92f42df9c6fda20ac527d 100644 (file)
@@ -26,6 +26,7 @@
 #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"
@@ -128,7 +129,8 @@ typedef enum TransState
        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;
 
 /*
@@ -153,6 +155,7 @@ typedef enum TBlockState
        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 */
@@ -163,7 +166,8 @@ typedef enum TBlockState
        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;
 
 /*
@@ -191,6 +195,15 @@ typedef struct TransactionStateData
        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;
 
@@ -339,6 +352,7 @@ 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);
+static void PushUndoRequest(void);
 
 
 /* ----------------------------------------------------------------
@@ -362,9 +376,9 @@ IsTransactionState(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);
 }
 
 /*
@@ -723,9 +737,14 @@ SubTransactionIsActive(SubTransactionId subxid)
 {
        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;
@@ -905,15 +924,15 @@ TransactionIdIsCurrentTransactionId(TransactionId xid)
         * 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 */
@@ -1885,6 +1904,7 @@ StartTransaction(void)
 {
        TransactionState s;
        VirtualTransactionId vxid;
+       int                     i;
 
        /*
         * Let's just make sure the state stack is empty
@@ -1968,6 +1988,15 @@ StartTransaction(void)
        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
         */
@@ -2264,6 +2293,8 @@ CommitTransaction(void)
        XactTopFullTransactionId = InvalidFullTransactionId;
        nParallelCurrentXids = 0;
 
+       ResetUndoActionsInfo();
+
        /*
         * done with commit processing, set current transaction state back to
         * default
@@ -2280,7 +2311,7 @@ CommitTransaction(void)
  * 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();
@@ -2433,7 +2464,7 @@ PrepareTransaction(void)
         * 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();
@@ -2622,7 +2653,9 @@ AbortTransaction(void)
         * 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);
@@ -2780,6 +2813,8 @@ CleanupTransaction(void)
        XactTopFullTransactionId = InvalidFullTransactionId;
        nParallelCurrentXids = 0;
 
+       ResetUndoActionsInfo();
+
        /*
         * done with abort processing, set current transaction state back to
         * default
@@ -2845,6 +2880,8 @@ StartTransactionCommand(void)
                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;
@@ -2906,9 +2943,17 @@ CommitTransactionCommand(void)
                         * 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;
@@ -2987,11 +3032,14 @@ CommitTransactionCommand(void)
 
                        /*
                         * 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)
@@ -3008,7 +3056,7 @@ CommitTransactionCommand(void)
                         * return to the idle state.
                         */
                case TBLOCK_PREPARE:
-                       PrepareTransaction();
+                       PrepareTransaction(s->start_urec_ptr, s->latest_urec_ptr);
                        s->blockState = TBLOCK_DEFAULT;
                        break;
 
@@ -3052,6 +3100,24 @@ CommitTransactionCommand(void)
                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);
@@ -3065,7 +3131,7 @@ CommitTransactionCommand(void)
                        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
@@ -3087,7 +3153,10 @@ CommitTransactionCommand(void)
                         * As above, but it's not dead yet, so abort first.
                         */
                case TBLOCK_SUBABORT_PENDING:
+                       UndoActionsRequired();
+                       PushUndoRequest();
                        AbortSubTransaction();
+                       ApplyUndoActions();
                        CleanupSubTransaction();
                        CommitTransactionCommand();
                        break;
@@ -3107,7 +3176,10 @@ CommitTransactionCommand(void)
                                s->name = NULL;
                                savepointLevel = s->savepointLevel;
 
+                               UndoActionsRequired();
+                               PushUndoRequest();
                                AbortSubTransaction();
+                               ApplyUndoActions();
                                CleanupSubTransaction();
 
                                DefineSavepoint(NULL);
@@ -3160,6 +3232,14 @@ AbortCurrentTransaction(void)
 {
        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:
@@ -3175,7 +3255,11 @@ AbortCurrentTransaction(void)
                                 * 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();
@@ -3190,7 +3274,9 @@ AbortCurrentTransaction(void)
                         */
                case TBLOCK_STARTED:
                case TBLOCK_IMPLICIT_INPROGRESS:
+                       PushUndoRequest();
                        AbortTransaction();
+                       ApplyUndoActions();
                        CleanupTransaction();
                        s->blockState = TBLOCK_DEFAULT;
                        break;
@@ -3201,8 +3287,12 @@ AbortCurrentTransaction(void)
                         * 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;
@@ -3215,7 +3305,9 @@ AbortCurrentTransaction(void)
                         */
                case TBLOCK_INPROGRESS:
                case TBLOCK_PARALLEL_INPROGRESS:
+                       PushUndoRequest();
                        AbortTransaction();
+                       ApplyUndoActions();
                        s->blockState = TBLOCK_ABORT;
                        /* CleanupTransaction happens when we exit TBLOCK_ABORT_END */
                        break;
@@ -3226,7 +3318,9 @@ AbortCurrentTransaction(void)
                         * the transaction).
                         */
                case TBLOCK_END:
+                       PushUndoRequest();
                        AbortTransaction();
+                       ApplyUndoActions();
                        CleanupTransaction();
                        s->blockState = TBLOCK_DEFAULT;
                        break;
@@ -3255,7 +3349,9 @@ AbortCurrentTransaction(void)
                         * Abort, cleanup, go to idle state.
                         */
                case TBLOCK_ABORT_PENDING:
+                       PushUndoRequest();
                        AbortTransaction();
+                       ApplyUndoActions();
                        CleanupTransaction();
                        s->blockState = TBLOCK_DEFAULT;
                        break;
@@ -3266,7 +3362,9 @@ AbortCurrentTransaction(void)
                         * the transaction).
                         */
                case TBLOCK_PREPARE:
+                       PushUndoRequest();
                        AbortTransaction();
+                       ApplyUndoActions();
                        CleanupTransaction();
                        s->blockState = TBLOCK_DEFAULT;
                        break;
@@ -3277,7 +3375,9 @@ AbortCurrentTransaction(void)
                         * we get ROLLBACK.
                         */
                case TBLOCK_SUBINPROGRESS:
+                       PushUndoRequest();
                        AbortSubTransaction();
+                       ApplyUndoActions();
                        s->blockState = TBLOCK_SUBABORT;
                        break;
 
@@ -3291,7 +3391,9 @@ AbortCurrentTransaction(void)
                case TBLOCK_SUBCOMMIT:
                case TBLOCK_SUBABORT_PENDING:
                case TBLOCK_SUBRESTART:
+                       PushUndoRequest();
                        AbortSubTransaction();
+                       ApplyUndoActions();
                        CleanupSubTransaction();
                        AbortCurrentTransaction();
                        break;
@@ -3304,9 +3406,172 @@ AbortCurrentTransaction(void)
                        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
  *
@@ -3633,6 +3898,8 @@ BeginTransactionBlock(void)
                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;
@@ -3825,6 +4092,8 @@ EndTransactionBlock(bool chain)
                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;
@@ -3941,6 +4210,8 @@ UserAbortTransactionBlock(bool chain)
                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;
@@ -4081,6 +4352,8 @@ DefineSavepoint(const char *name)
                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;
@@ -4099,6 +4372,18 @@ ReleaseSavepoint(const char *name)
        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
@@ -4157,6 +4442,8 @@ ReleaseSavepoint(const char *name)
                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;
@@ -4192,8 +4479,37 @@ ReleaseSavepoint(const char *name)
                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];
+       }
 }
 
 /*
@@ -4266,6 +4582,8 @@ RollbackToSavepoint(const char *name)
                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;
@@ -4384,6 +4702,8 @@ BeginInternalSubTransaction(const char *name)
                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;
@@ -4404,6 +4724,7 @@ void
 ReleaseCurrentSubTransaction(void)
 {
        TransactionState s = CurrentTransactionState;
+       int                     i;
 
        /*
         * Workers synchronize transaction state at the beginning of each parallel
@@ -4422,6 +4743,22 @@ ReleaseCurrentSubTransaction(void)
                         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);
@@ -4473,17 +4810,32 @@ RollbackAndReleaseCurrentSubTransaction(void)
                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();
 
@@ -4514,6 +4866,14 @@ AbortOutOfAnyTransaction(void)
         */
        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:
@@ -4529,7 +4889,11 @@ AbortOutOfAnyTransaction(void)
                                         * 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();
@@ -4545,6 +4909,20 @@ AbortOutOfAnyTransaction(void)
                        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;
@@ -4572,6 +4950,20 @@ AbortOutOfAnyTransaction(void)
                        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 */
@@ -4666,6 +5058,8 @@ TransactionBlockStatusCode(void)
                case TBLOCK_SUBABORT_PENDING:
                case TBLOCK_SUBRESTART:
                case TBLOCK_SUBABORT_RESTART:
+               case TBLOCK_UNDO:
+               case TBLOCK_SUBUNDO:
                        return 'E';                     /* in failed transaction */
        }
 
@@ -4705,6 +5099,7 @@ static void
 StartSubTransaction(void)
 {
        TransactionState s = CurrentTransactionState;
+       int                     i;
 
        if (s->state != TRANS_DEFAULT)
                elog(WARNING, "StartSubTransaction while in %s state",
@@ -4722,6 +5117,15 @@ StartSubTransaction(void)
        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;
 
        /*
@@ -4909,7 +5313,8 @@ AbortSubTransaction(void)
         */
        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));
 
@@ -5336,6 +5741,8 @@ BlockStateAsString(TBlockState blockState)
                        return "ABORT_PENDING";
                case TBLOCK_PREPARE:
                        return "PREPARE";
+               case TBLOCK_UNDO:
+                       return "UNDO";
                case TBLOCK_SUBBEGIN:
                        return "SUBBEGIN";
                case TBLOCK_SUBINPROGRESS:
@@ -5354,6 +5761,8 @@ BlockStateAsString(TBlockState blockState)
                        return "SUBRESTART";
                case TBLOCK_SUBABORT_RESTART:
                        return "SUBABORT_RESTART";
+               case TBLOCK_SUBUNDO:
+                       return "SUBUNDO";
        }
        return "UNRECOGNIZED";
 }
@@ -5379,6 +5788,8 @@ TransStateAsString(TransState state)
                        return "ABORT";
                case TRANS_PREPARE:
                        return "PREPARE";
+               case TRANS_UNDO:
+                       return "UNDO";
        }
        return "UNRECOGNIZED";
 }
@@ -5977,3 +6388,155 @@ xact_redo(XLogReaderState *record)
        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;
+
+}
diff --git a/src/backend/access/undo/README.UndoProcessing b/src/backend/access/undo/README.UndoProcessing
new file mode 100644 (file)
index 0000000..e0caf9e
--- /dev/null
@@ -0,0 +1,39 @@
+src/backend/access/undo/README.UndoProcessing
+
+Transaction Rollbacks and Undo Processing
+------------------------------------------
+We always perform rollback actions after cleaning up the current
+(sub)transaction.  This will ensure that we perform the actions immediately
+after error rather than when user issues Rollback command at some later point
+of time.  We are releasing the locks after the undo actions are applied.  The
+reason to delay lock release is that if we release locks before applying undo
+actions, then the parallel session can acquire the lock before us which can
+lead to deadlock.  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 which indicates that undo apply is in progress.  This
+state is considered as a valid state which means that it is safe to initiate a
+database access, acquire heavyweight locks, etc. in this state.  We have also
+introduced new block states TBLOCK_UNDO and TBLOCK_SUBUNDO, so that if we get
+an error while applying undo, we don't restart applying it again and rather
+just perform Abort/Cleanup of transaction.
+
+We promote the error to FATAL error if it occurred while applying undo for a
+subtransaction.  The reason we can't proceed without applying subtransaction's
+undo is that the modifications made in that case must not be visible even if
+the main transaction commits.  Normally, the backends that receive the request
+to perform Rollback (To Savepoint) applies the undo actions, but there are
+cases where it is preferable to push the requests to background workers.  The
+main reasons to push the requests to background workers are (a) The rollback
+request is very large, pushing such a request to background workers will allow
+us to return control to users quickly.  There is a guc rollback_overflow_size
+which indicates that rollbacks greater than the configured size are performed
+lazily by background workers. (b) We got an error while applying the undo
+actions.
+
+We do have some restrictions on which requests can be pushed to the background
+workers.  In single user mode, all the requests are performed in foreground.
+We can't push the undo actions for temp table to background workers as the temp
+tables are only accessible in the backend that has created them.  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.
index 66c3175d0323ad0730696c902058a089f42a83fd..55f51169b3a6eb73a3b0ddcf414e3be3f22f9826 100644 (file)
@@ -1009,6 +1009,13 @@ InsertPreparedUndo(UndoRecordInsertContext *context)
                        Assert(bufidx < MAX_BUFFER_PER_UNDO);
                } while (true);
 
+               /*
+                * Set the current undo location for a transaction.  This is required
+                * to perform rollback during abort of transaction.
+                */
+               SetCurrentUndoLocation(prepared_undo->urp,
+                                                          context->alloc_context.category);
+
                /* Advance the insert pointer past this record. */
                UndoLogAdvanceFinal(prepared_undo->urp, prepared_undo->size);
        }
index 8b4720ef3abe9d852341f59f298ed34dddb54313..c66526902395521aeb68d07baac2d9b9651aa458 100644 (file)
@@ -259,12 +259,18 @@ errstart(int elevel, const char *filename, int lineno,
                 * 3. the error occurred after proc_exit has begun to run.  (It's
                 * proc_exit's responsibility to see that this doesn't turn into
                 * infinite recursion!)
+                *
+                * 4. the error occurred while applying undo for a subtransaction. (We
+                * can't proceed without applying subtransaction's undo as the
+                * modifications made in that case must not be visible even if the
+                * main transaction commits.)
                 */
                if (elevel == ERROR)
                {
                        if (PG_exception_stack == NULL ||
                                ExitOnAnyError ||
-                               proc_exit_inprogress)
+                               proc_exit_inprogress ||
+                               applying_subxact_undo)
                                elevel = FATAL;
                }
 
@@ -1164,6 +1170,22 @@ internalerrquery(const char *query)
        return 0;                                       /* return value does not matter */
 }
 
+/*
+ * err_out_to_client --- sets whether to send error output to client or not.
+ */
+int
+err_out_to_client(bool out_to_client)
+{
+       ErrorData  *edata = &errordata[errordata_stack_depth];
+
+       /* we don't bother incrementing recursion_depth */
+       CHECK_STACK_DEPTH();
+
+       edata->output_to_client = out_to_client;
+
+       return 0;                                       /* return value does not matter */
+}
+
 /*
  * err_generic_string -- used to set individual ErrorData string fields
  * identified by PG_DIAG_xxx codes.
@@ -1762,6 +1784,18 @@ pg_re_throw(void)
                                                 __FILE__, __LINE__);
 }
 
+/*
+ * pg_rethrow_as_fatal - Promote the error level to fatal.
+ */
+void
+pg_rethrow_as_fatal(void)
+{
+       ErrorData  *edata = &errordata[errordata_stack_depth];
+
+       Assert(errordata_stack_depth >= 0);
+       edata->elevel = FATAL;
+       PG_RE_THROW();
+}
 
 /*
  * GetErrorContextStack - Return the context stack, for display/diags
index 3bf96de256df6d06c7fd04dd9287cce10f0a137d..4e751d03ba96902e76a1f80abd3baf39750e635b 100644 (file)
@@ -122,6 +122,12 @@ int                        work_mem = 1024;
 int                    maintenance_work_mem = 16384;
 int                    max_parallel_maintenance_workers = 2;
 
+/*
+ * We need this variable primarily to promote the error level to FATAL if we
+ * get any error while performing undo actions for a subtransaction.
+ */
+bool           applying_subxact_undo = false;
+
 /*
  * Primary determinants of sizes of shared-memory structures.
  *
index 7be11c48abe0d5d588cb0dfc210d429d8a2e3ab9..3b2a28bfe88e544ae0de4b52eca2cd5753012fa1 100644 (file)
@@ -20,6 +20,7 @@
  */
 #include "postgres.h"
 
+#include "access/xact.h"
 #include "jit/jit.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
@@ -556,6 +557,11 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
        }
        else if (phase == RESOURCE_RELEASE_LOCKS)
        {
+               /*
+                * For aborts, we don't want to release the locks immediately if we have
+                * some pending undo actions to perform.  Instead, we release them after
+                * applying undo actions.  See ApplyUndoActions.
+                */
                if (isTopLevel)
                {
                        /*
@@ -565,7 +571,8 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
                         */
                        if (owner == TopTransactionResourceOwner)
                        {
-                               ProcReleaseLocks(isCommit);
+                               if (!CanPerformUndoActions())
+                                       ProcReleaseLocks(isCommit);
                                ReleasePredicateLocks(isCommit, false);
                        }
                }
@@ -598,7 +605,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 
                        if (isCommit)
                                LockReassignCurrentOwner(locks, nlocks);
-                       else
+                       else if (!CanPerformUndoActions())
                                LockReleaseCurrentOwner(locks, nlocks);
                }
        }
index 01f248a41eb73ade450664a933a5549313648390..7796f7248c4e4a2b436aa0918eb6ac74ff3887a7 100644 (file)
@@ -231,6 +231,7 @@ extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
 extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid);
 extern bool ForceTransactionIdLimitUpdate(void);
 extern Oid     GetNewObjectId(void);
+extern uint32 GetEpochForXid(TransactionId xid);
 
 /*
  * Some frontend programs include this header.  For compilers that emit static
index b9a531c96e3eb48ccf6c6a1dcb931d73103b3b61..497b92f2b8db47c5667688e46277ffd533f18f99 100644 (file)
@@ -14,6 +14,7 @@
 #ifndef TWOPHASE_H
 #define TWOPHASE_H
 
+#include "access/undolog.h"
 #include "access/xlogdefs.h"
 #include "access/xact.h"
 #include "datatype/timestamp.h"
@@ -41,7 +42,7 @@ extern GlobalTransaction MarkAsPreparing(TransactionId xid, const char *gid,
                                                                                 TimestampTz prepared_at,
                                                                                 Oid owner, Oid databaseid);
 
-extern void StartPrepare(GlobalTransaction gxact);
+extern void StartPrepare(GlobalTransaction gxact, UndoRecPtr *, UndoRecPtr *);
 extern void EndPrepare(GlobalTransaction gxact);
 extern bool StandbyTransactionIdIsPrepared(TransactionId xid);
 
index a20726afa0e10d66fae29ed8c3e19786d514a9d3..6bcdc809e2566f5b72033db3fcaddc071d7b1001 100644 (file)
@@ -14,6 +14,7 @@
 #ifndef XACT_H
 #define XACT_H
 
+#include "access/undolog.h"
 #include "access/transam.h"
 #include "access/xlogreader.h"
 #include "lib/stringinfo.h"
@@ -427,6 +428,15 @@ extern XLogRecPtr XactLogAbortRecord(TimestampTz abort_time,
                                                                         int xactflags, TransactionId twophase_xid,
                                                                         const char *twophase_gid);
 extern void xact_redo(XLogReaderState *record);
+extern void ApplyUndoActions(void);
+extern void UndoActionsRequired(void);
+extern void ResetUndoActionsInfo(void);
+extern bool CanPerformUndoActions(void);
+extern bool PerformUndoActions(FullTransactionId fxid, Oid dbid,
+                               UndoRecPtr *end_urec_ptr, UndoRecPtr *start_urec_ptr,
+                               bool *undo_req_pushed, bool isSubTrans);
+extern void SetCurrentUndoLocation(UndoRecPtr urec_ptr,
+                               UndoLogCategory category);
 
 /* xactdesc.c */
 extern void xact_desc(StringInfo buf, XLogReaderState *record);
index 1afc4d3b5d39ed256df68259ce946de4fc3049b3..388dc97a50cf51840e2e275b731f2e52ec0c6995 100644 (file)
@@ -246,6 +246,8 @@ extern PGDLLIMPORT int work_mem;
 extern PGDLLIMPORT int maintenance_work_mem;
 extern PGDLLIMPORT int max_parallel_maintenance_workers;
 
+extern bool applying_subxact_undo;
+
 extern int     VacuumCostPageHit;
 extern int     VacuumCostPageMiss;
 extern int     VacuumCostPageDirty;
index dbfd8efd269656d82e8cb311dc81ed75b608fe91..0b227ab42f7be2131be45c986b61ed90780ffa75 100644 (file)
@@ -195,6 +195,8 @@ extern int  errposition(int cursorpos);
 extern int     internalerrposition(int cursorpos);
 extern int     internalerrquery(const char *query);
 
+extern int     err_out_to_client(bool out_to_client);
+
 extern int     err_generic_string(int field, const char *str);
 
 extern int     geterrcode(void);
@@ -384,6 +386,7 @@ extern void FlushErrorState(void);
 extern void ReThrowError(ErrorData *edata) pg_attribute_noreturn();
 extern void ThrowErrorData(ErrorData *edata);
 extern void pg_re_throw(void) pg_attribute_noreturn();
+extern void pg_rethrow_as_fatal(void);
 
 extern char *GetErrorContextStack(void);