From e92123c9135648e3450d1bfbb90d079d59dfe623 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Thu, 13 Jun 2019 15:42:46 +0530 Subject: [PATCH 12/13] Allow foreground transactions to perform undo actions on abort. 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 --- src/backend/access/transam/twophase.c | 82 ++++- src/backend/access/transam/varsup.c | 24 ++ src/backend/access/transam/xact.c | 490 +++++++++++++++++++++++++- src/backend/access/undo/README.UndoProcessing | 39 ++ src/backend/access/undo/undoaccess.c | 7 + src/backend/access/undo/undoaction.c | 23 +- src/backend/storage/ipc/ipc.c | 7 + src/backend/tcop/postgres.c | 8 +- src/backend/utils/error/elog.c | 23 +- src/backend/utils/init/globals.c | 1 + src/backend/utils/resowner/resowner.c | 11 +- src/include/access/transam.h | 1 + src/include/access/twophase.h | 3 +- src/include/access/xact.h | 11 + src/include/miscadmin.h | 15 + src/include/utils/elog.h | 2 + 16 files changed, 725 insertions(+), 22 deletions(-) create mode 100644 src/backend/access/undo/README.UndoProcessing diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 477709bbc2..4d34d11ddb 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -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" @@ -909,7 +910,7 @@ TwoPhaseGetDummyProc(TransactionId xid, bool lock_held) /* * Header for a 2PC state file */ -#define TWOPHASE_MAGIC 0x57F94534 /* format identifier */ +#define TWOPHASE_MAGIC 0x57F94535 /* format identifier */ typedef struct TwoPhaseFileHeader { @@ -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 its 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 startUrecPtr[UndoLogCategories]; + UndoRecPtr endUrecPtr[UndoLogCategories]; + bool uRequestRegistered[UndoLogCategories]; + uint32 epoch; + int i; + FullTransactionId fXid; /* * 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(startUrecPtr, hdr->start_urec_ptr, sizeof(startUrecPtr)); + memcpy(endUrecPtr, hdr->end_urec_ptr, sizeof(endUrecPtr)); + /* 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); + fXid = 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 CheckAndRegisterUndoRequest for further details. + */ + for (i = 0; i < UndoLogCategories; i++) + { + if (endUrecPtr[i] != InvalidUndoRecPtr && i != UNDO_TEMP) + { + uRequestRegistered[i] = RegisterUndoRequest(endUrecPtr[i], + startUrecPtr[i], + hdr->database, + fXid); + } + } + RecordTransactionAbortPrepared(xid, hdr->nsubxacts, children, hdr->nabortrels, abortrels, gid); + } ProcArrayRemove(proc, latestXid); @@ -1614,6 +1674,24 @@ FinishPreparedTransaction(const char *gid, bool isCommit) RESUME_INTERRUPTS(); + if (!isCommit) + { + /* + * We need to perform undo actions while we are still in a transaction. + */ + if (!ProcessUndoRequestForEachLogCat(fXid, hdr->database, + endUrecPtr, startUrecPtr, + uRequestRegistered, false)) + { + /* Abort the failed transaction. */ + AbortOutOfAnyTransaction(); + FlushErrorState(); + + /* Restart our transaction. */ + StartTransactionCommand(); + } + } + pfree(buf); } diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 5b759ec7f3..fd01989302 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -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; +} diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index d7930c077d..60a9a0a18c 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -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,16 @@ 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 log category */ + UndoRecPtr startUrecPtr[UndoLogCategories]; /* this is 'to' location */ + UndoRecPtr latestUrecPtr[UndoLogCategories]; /* this is 'from' + * location */ + /* + * whether the undo request is registered to be processed by worker later? + */ + bool undoRequestResgistered[UndoLogCategories]; + struct TransactionStateData *parent; /* back link to parent */ } TransactionStateData; @@ -339,6 +353,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 CheckAndRegisterUndoRequest(void); /* ---------------------------------------------------------------- @@ -362,9 +377,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 +738,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 +925,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 */ @@ -1968,6 +1988,14 @@ StartTransaction(void) nUnreportedXids = 0; s->didLogXid = false; + /* initialize undo information for the transaction */ + memset(s->startUrecPtr, InvalidUndoRecPtr, + sizeof(UndoRecPtr) * UndoLogCategories); + memset(s->latestUrecPtr, InvalidUndoRecPtr, + sizeof(UndoRecPtr) * UndoLogCategories); + memset(s->undoRequestResgistered, false, + sizeof(bool) * UndoLogCategories); + /* * must initialize resource-management stuff first */ @@ -2264,6 +2292,8 @@ CommitTransaction(void) XactTopFullTransactionId = InvalidFullTransactionId; nParallelCurrentXids = 0; + ResetUndoActionsInfo(); + /* * done with commit processing, set current transaction state back to * default @@ -2433,7 +2463,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, s->startUrecPtr, s->latestUrecPtr); AtPrepare_Notify(); AtPrepare_Locks(); @@ -2622,7 +2652,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 +2812,8 @@ CleanupTransaction(void) XactTopFullTransactionId = InvalidFullTransactionId; nParallelCurrentXids = 0; + ResetUndoActionsInfo(); + /* * done with abort processing, set current transaction state back to * default @@ -2845,6 +2879,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 +2942,18 @@ 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 ReleaseResourcesAndProcessUndo 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,13 @@ 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: + CheckAndRegisterUndoRequest(); AbortTransaction(); + ReleaseResourcesAndProcessUndo(); CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; if (s->chain) @@ -3087,7 +3134,9 @@ CommitTransactionCommand(void) * As above, but it's not dead yet, so abort first. */ case TBLOCK_SUBABORT_PENDING: + CheckAndRegisterUndoRequest(); AbortSubTransaction(); + ReleaseResourcesAndProcessUndo(); CleanupSubTransaction(); CommitTransactionCommand(); break; @@ -3107,7 +3156,9 @@ CommitTransactionCommand(void) s->name = NULL; savepointLevel = s->savepointLevel; + CheckAndRegisterUndoRequest(); AbortSubTransaction(); + ReleaseResourcesAndProcessUndo(); CleanupSubTransaction(); DefineSavepoint(NULL); @@ -3175,7 +3226,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(!NeedToPerformUndoActions()); if (s->state == TRANS_START) s->state = TRANS_INPROGRESS; AbortTransaction(); @@ -3190,7 +3245,9 @@ AbortCurrentTransaction(void) */ case TBLOCK_STARTED: case TBLOCK_IMPLICIT_INPROGRESS: + CheckAndRegisterUndoRequest(); AbortTransaction(); + ReleaseResourcesAndProcessUndo(); CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; break; @@ -3201,8 +3258,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(!NeedToPerformUndoActions()); AbortTransaction(); CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; @@ -3215,7 +3276,9 @@ AbortCurrentTransaction(void) */ case TBLOCK_INPROGRESS: case TBLOCK_PARALLEL_INPROGRESS: + CheckAndRegisterUndoRequest(); AbortTransaction(); + ReleaseResourcesAndProcessUndo(); s->blockState = TBLOCK_ABORT; /* CleanupTransaction happens when we exit TBLOCK_ABORT_END */ break; @@ -3226,7 +3289,9 @@ AbortCurrentTransaction(void) * the transaction). */ case TBLOCK_END: + CheckAndRegisterUndoRequest(); AbortTransaction(); + ReleaseResourcesAndProcessUndo(); CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; break; @@ -3255,7 +3320,9 @@ AbortCurrentTransaction(void) * Abort, cleanup, go to idle state. */ case TBLOCK_ABORT_PENDING: + CheckAndRegisterUndoRequest(); AbortTransaction(); + ReleaseResourcesAndProcessUndo(); CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; break; @@ -3266,7 +3333,9 @@ AbortCurrentTransaction(void) * the transaction). */ case TBLOCK_PREPARE: + CheckAndRegisterUndoRequest(); AbortTransaction(); + ReleaseResourcesAndProcessUndo(); CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; break; @@ -3277,7 +3346,9 @@ AbortCurrentTransaction(void) * we get ROLLBACK. */ case TBLOCK_SUBINPROGRESS: + CheckAndRegisterUndoRequest(); AbortSubTransaction(); + ReleaseResourcesAndProcessUndo(); s->blockState = TBLOCK_SUBABORT; break; @@ -3291,7 +3362,9 @@ AbortCurrentTransaction(void) case TBLOCK_SUBCOMMIT: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: + CheckAndRegisterUndoRequest(); AbortSubTransaction(); + ReleaseResourcesAndProcessUndo(); CleanupSubTransaction(); AbortCurrentTransaction(); break; @@ -3304,6 +3377,19 @@ 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 + * ReleaseResourcesAndProcessUndo 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; } } @@ -3633,6 +3719,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 +3913,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 +4031,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 +4173,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; @@ -4157,6 +4251,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; @@ -4266,6 +4362,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 +4482,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; @@ -4473,17 +4573,25 @@ 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; } + /* Try to push rollback request to worker if possible. */ + CheckAndRegisterUndoRequest(); + /* * Abort the current subtransaction, if needed. */ if (s->blockState == TBLOCK_SUBINPROGRESS) AbortSubTransaction(); + /* Execute undo actions */ + ReleaseResourcesAndProcessUndo(); + /* And clean it up, too */ CleanupSubTransaction(); @@ -4529,7 +4637,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(!NeedToPerformUndoActions()); if (s->state == TRANS_START) s->state = TRANS_INPROGRESS; AbortTransaction(); @@ -4545,7 +4657,27 @@ AbortOutOfAnyTransaction(void) case TBLOCK_ABORT_PENDING: case TBLOCK_PREPARE: /* In a transaction, so clean up */ + CheckAndRegisterUndoRequest(); AbortTransaction(); + ReleaseResourcesAndProcessUndo(); + 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. + * + * AbortTransaction is already done, still need to release + * locks and perform cleanup. + */ + ResetUndoActionsInfo(); + ResourceOwnerRelease(s->curTransactionOwner, + RESOURCE_RELEASE_LOCKS, + false, + true); + s->state = TRANS_ABORT; CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; break; @@ -4572,7 +4704,9 @@ AbortOutOfAnyTransaction(void) case TBLOCK_SUBCOMMIT: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: + CheckAndRegisterUndoRequest(); AbortSubTransaction(); + ReleaseResourcesAndProcessUndo(); CleanupSubTransaction(); s = CurrentTransactionState; /* changed by pop */ break; @@ -4592,6 +4726,24 @@ AbortOutOfAnyTransaction(void) 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. + * + * AbortSubTransaction is already done, still need to release + * locks and perform cleanup. + */ + ResetUndoActionsInfo(); + ResourceOwnerRelease(s->curTransactionOwner, + RESOURCE_RELEASE_LOCKS, + false, + false); + s->state = TRANS_ABORT; + CleanupSubTransaction(); + s = CurrentTransactionState; /* changed by pop */ + break; } } while (s->blockState != TBLOCK_DEFAULT); @@ -4666,6 +4818,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 */ } @@ -4722,6 +4876,14 @@ StartSubTransaction(void) AtSubStart_Notify(); AfterTriggerBeginSubXact(); + /* initialize undo information for the transaction */ + memset(s->startUrecPtr, InvalidUndoRecPtr, + sizeof(UndoRecPtr) * UndoLogCategories); + memset(s->latestUrecPtr, InvalidUndoRecPtr, + sizeof(UndoRecPtr) * UndoLogCategories); + memset(s->undoRequestResgistered, false, + sizeof(bool) * UndoLogCategories); + s->state = TRANS_INPROGRESS; /* @@ -4743,6 +4905,7 @@ static void CommitSubTransaction(void) { TransactionState s = CurrentTransactionState; + int i; ShowTransactionState("CommitSubTransaction"); @@ -4765,6 +4928,19 @@ CommitSubTransaction(void) /* Do the actual "commit", such as it is */ s->state = TRANS_COMMIT; + /* + * Propagate the undo pointers from current transaction to parent so that + * if parent transaction get aborted we must not skip performing undo for + * this transaction. + */ + for (i = 0; i < UndoLogCategories; i++) + { + if (UndoRecPtrIsValid(s->latestUrecPtr[i])) + s->parent->latestUrecPtr[i] = s->latestUrecPtr[i]; + if (!UndoRecPtrIsValid(s->parent->startUrecPtr[i])) + s->parent->startUrecPtr[i] = s->startUrecPtr[i]; + } + /* Must CCI to ensure commands of subtransaction are seen as done */ CommandCounterIncrement(); @@ -4909,7 +5085,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 +5513,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 +5533,8 @@ BlockStateAsString(TBlockState blockState) return "SUBRESTART"; case TBLOCK_SUBABORT_RESTART: return "SUBABORT_RESTART"; + case TBLOCK_SUBUNDO: + return "SUBUNDO"; } return "UNRECOGNIZED"; } @@ -5379,6 +5560,8 @@ TransStateAsString(TransState state) return "ABORT"; case TRANS_PREPARE: return "PREPARE"; + case TRANS_UNDO: + return "UNDO"; } return "UNRECOGNIZED"; } @@ -5977,3 +6160,284 @@ xact_redo(XLogReaderState *record) else elog(PANIC, "xact_redo: unknown op code %u", info); } + +/* + * 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->startUrecPtr[category])) + CurrentTransactionState->startUrecPtr[category] = urec_ptr; + CurrentTransactionState->latestUrecPtr[category] = urec_ptr; +} + +/* + * ResetUndoActionsInfo - reset the start and end undo record pointers. + */ +void +ResetUndoActionsInfo(void) +{ + TransactionState s = CurrentTransactionState; + + /* initialize undo information for the transaction */ + memset(s->startUrecPtr, InvalidUndoRecPtr, + sizeof(UndoRecPtr) * UndoLogCategories); + memset(s->latestUrecPtr, InvalidUndoRecPtr, + sizeof(UndoRecPtr) * UndoLogCategories); + memset(s->undoRequestResgistered, false, + sizeof(bool) * UndoLogCategories); +} + +/* + * NeedToPerformUndoActions - Returns true, if the current transaction needs + * to perform undo actions, false otherwise. + * + * This function needs to be called before we release the locks during abort + * so that we can skip releasing them if required. We don't release the locks + * till we execute undo actions otherwise, there is a risk of deadlock. + */ +bool +NeedToPerformUndoActions(void) +{ + TransactionState s = CurrentTransactionState; + int i; + + for (i = 0; i < UndoLogCategories; i++) + { + if (UndoRecPtrIsValid(s->latestUrecPtr[i])) + return true; + } + + return false; +} + +/* + * CheckAndRegisterUndoRequest - Register the request for applying 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 +CheckAndRegisterUndoRequest() +{ + TransactionState s = CurrentTransactionState; + bool result; + int i; + + /* + * We don't want to apply the undo actions when we are already cleaning up + * for FATAL error. See ReleaseResourcesAndProcessUndo. + */ + if (SemiCritSectionCount > 0) + { + ResetUndoActionsInfo(); + return; + } + + if (!NeedToPerformUndoActions()) + 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. See execute_undo_actions for detailed + * explanation. + */ + if (IsSubTransaction()) + return; + + for (i = 0; i < UndoLogCategories; i++) + { + /* + * 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 (i != UNDO_TEMP && UndoRecPtrIsValid(s->latestUrecPtr[i])) + { + result = RegisterUndoRequest(s->latestUrecPtr[i], + s->startUrecPtr[i], + MyDatabaseId, + GetTopFullTransactionId()); + s->undoRequestResgistered[i] = result; + } + } +} + +/* + * ReleaseResourcesAndProcessUndo - Release resources and process undo request. + * + * 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 +ReleaseResourcesAndProcessUndo(void) +{ + TransactionState s = CurrentTransactionState; + + /* + * We don't want to apply the undo actions when we are already cleaning up + * for FATAL error. One of the main reasons is that we might be already + * processing undo actions for a (sub)transaction when we reach here + * (for ex. error happens while processing undo actions for a + * subtransaction). + */ + if (SemiCritSectionCount > 0) + { + ResetUndoActionsInfo(); + return; + } + + if (!NeedToPerformUndoActions()) + return; + + /* + * State should still be TRANS_ABORT from AbortTransaction(). + */ + if (s->state != TRANS_ABORT) + elog(FATAL, "ReleaseResourcesAndProcessUndo: unexpected state %s", + TransStateAsString(s->state)); + + /* + * 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; + } + 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; + + /* + * We ignore the return value here as in either case we need to release + * the resources and allow caller to proceed with further cleanup. + */ + (void) ProcessUndoRequestForEachLogCat(GetTopFullTransactionId(), + MyDatabaseId, + s->latestUrecPtr, s->startUrecPtr, + s->undoRequestResgistered, + IsSubTransaction()); + + /* Reset undo information */ + ResetUndoActionsInfo(); + + /* Release the locks after processing undo request. */ + ResourceOwnerRelease(s->curTransactionOwner, + RESOURCE_RELEASE_LOCKS, + false, + !IsSubTransaction()); + + /* + * Here we again put back the transaction in abort state so that callers + * can proceed with the cleanup work. + */ + s->state = TRANS_ABORT; +} + +/* + * ProcessUndoRequestForEachLogCat - Perform undo actions for all the undo logs. + * + * Returns true, if we are able to successfully perform the actions, + * false, otherwise. + * + * If we get any error while performing undo actions, we just insert the + * request into an error queue for later processing by undo launcher and allow + * the main transaction to continue. In such a case the callers are + * responsible to release the resources. + */ +bool +ProcessUndoRequestForEachLogCat(FullTransactionId fxid, Oid dbid, + UndoRecPtr *end_urec_ptr, UndoRecPtr *start_urec_ptr, + bool *undoRequestResgistered, bool isSubTrans) +{ + UndoRequestInfo urinfo; + int i; + uint32 save_holdoff; + bool success = true; + + for (i = 0; i < UndoLogCategories; i++) + { + if (end_urec_ptr[i] && !undoRequestResgistered[i]) + { + save_holdoff = InterruptHoldoffCount; + + PG_TRY(); + { + /* for subtransactions, we do partial rollback. */ + execute_undo_actions(fxid, + end_urec_ptr[i], + start_urec_ptr[i], + !isSubTrans); + } + PG_CATCH(); + { + /* + * 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. + */ + ResetUndoRequestInfo(&urinfo); + urinfo.dbid = dbid; + urinfo.full_xid = fxid; + urinfo.start_urec_ptr = start_urec_ptr[i]; + 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 should never reach here when we are in a semi-critical-section. */ + Assert(SemiCritSectionCount == 0); + } + PG_END_TRY(); + } + } + + return success; +} diff --git a/src/backend/access/undo/README.UndoProcessing b/src/backend/access/undo/README.UndoProcessing new file mode 100644 index 0000000000..e0caf9efeb --- /dev/null +++ b/src/backend/access/undo/README.UndoProcessing @@ -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. diff --git a/src/backend/access/undo/undoaccess.c b/src/backend/access/undo/undoaccess.c index 66c3175d03..55f51169b3 100644 --- a/src/backend/access/undo/undoaccess.c +++ b/src/backend/access/undo/undoaccess.c @@ -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); } diff --git a/src/backend/access/undo/undoaction.c b/src/backend/access/undo/undoaction.c index d130eb8f14..b6169639ce 100644 --- a/src/backend/access/undo/undoaction.c +++ b/src/backend/access/undo/undoaction.c @@ -457,11 +457,29 @@ execute_undo_actions(FullTransactionId full_xid, UndoRecPtr from_urecptr, UndoRecPtr to_urecptr, bool complete_xact) { UndoRecPtr last_log_start_urec_ptr = to_urecptr; + UndoLogCategory logcat = UndoRecPtrGetCategory(to_urecptr); /* 'from' and 'to' pointers must be valid. */ Assert(from_urecptr != InvalidUndoRecPtr); Assert(to_urecptr != InvalidUndoRecPtr); + /* + * We need to execute the undo actions in a semi-critical section for + * + * (a) Subtransactions. 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. The reason why that + * can happen is because for undo-based AM's we don't need to have a + * separate transaction id for subtransactions and once the main + * transaction commits the tuples modified by subtransactions will become + * visible. + * + * (b) Temp tables. We don't expect background workers to process undo of + * temporary tables as the same won't be accessible. + */ + if (!complete_xact || logcat == UNDO_TEMP) + START_SEMI_CRIT_SECTION(); + /* * Here we compute the last log start urp which is used for fetching the * undo records and updating the undo action progress. @@ -472,7 +490,7 @@ execute_undo_actions(FullTransactionId full_xid, UndoRecPtr from_urecptr, */ if (complete_xact) { - if (UndoRecPtrGetCategory(to_urecptr) == UNDO_TEMP) + if (logcat == UNDO_TEMP) { UndoRecPtr end_urec_ptr = from_urecptr; @@ -521,4 +539,7 @@ execute_undo_actions(FullTransactionId full_xid, UndoRecPtr from_urecptr, * Undo actions are applied so delete the hash table entry. */ RollbackHTRemoveEntry(full_xid, to_urecptr, false); + + if (!complete_xact || logcat == UNDO_TEMP) + END_SEMI_CRIT_SECTION(); } diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c index 05d02c23f5..22f0bac6f2 100644 --- a/src/backend/storage/ipc/ipc.c +++ b/src/backend/storage/ipc/ipc.c @@ -193,6 +193,13 @@ proc_exit_prepare(int code) /* do our shared memory exits first */ shmem_exit(code); + /* + * We need to clear semi-critical-section after exiting from shmem as we + * can again use it for executing undo actions via + * AbortOutOfAnyTransaction which is called during shmem exit. + */ + SemiCritSectionCount = 0; + elog(DEBUG3, "proc_exit(%d): %d callbacks to make", code, on_proc_exit_index); diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index a6505c7335..68eaacfa87 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -594,7 +594,9 @@ ProcessClientWriteInterrupt(bool blocked) * Don't mess with whereToSendOutput if ProcessInterrupts wouldn't * do anything. */ - if (InterruptHoldoffCount == 0 && CritSectionCount == 0) + if (InterruptHoldoffCount == 0 && + CritSectionCount == 0 && + SemiCritSectionCount == 0) { /* * We don't want to send the client the error message, as a) @@ -2985,7 +2987,9 @@ void ProcessInterrupts(void) { /* OK to accept any interrupts now? */ - if (InterruptHoldoffCount != 0 || CritSectionCount != 0) + if (InterruptHoldoffCount != 0 || + CritSectionCount != 0 || + SemiCritSectionCount != 0) return; InterruptPending = false; diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 8b4720ef3a..d24828f543 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -259,12 +259,16 @@ 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. If we are inside a semi-critical section, all errors become FATAL + * errors. See miscadmin.h. */ if (elevel == ERROR) { if (PG_exception_stack == NULL || ExitOnAnyError || - proc_exit_inprogress) + proc_exit_inprogress || + SemiCritSectionCount > 0) elevel = FATAL; } @@ -454,6 +458,7 @@ errfinish(int dummy,...) QueryCancelHoldoffCount = 0; CritSectionCount = 0; /* should be unnecessary, but... */ + SemiCritSectionCount = 0; /* * Note that we leave CurrentMemoryContext set to ErrorContext. The @@ -1164,6 +1169,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. diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index 3bf96de256..9faeb84819 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -36,6 +36,7 @@ volatile sig_atomic_t ConfigReloadPending = false; volatile uint32 InterruptHoldoffCount = 0; volatile uint32 QueryCancelHoldoffCount = 0; volatile uint32 CritSectionCount = 0; +volatile uint32 SemiCritSectionCount = 0; int MyProcPid; pg_time_t MyStartTime; diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index 7be11c48ab..7fb832997f 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -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 ReleaseResourcesAndProcessUndo. + */ if (isTopLevel) { /* @@ -565,7 +571,8 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, */ if (owner == TopTransactionResourceOwner) { - ProcReleaseLocks(isCommit); + if (isCommit || !NeedToPerformUndoActions()) + ProcReleaseLocks(isCommit); ReleasePredicateLocks(isCommit, false); } } @@ -598,7 +605,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, if (isCommit) LockReassignCurrentOwner(locks, nlocks); - else + else if (!NeedToPerformUndoActions()) LockReleaseCurrentOwner(locks, nlocks); } } diff --git a/src/include/access/transam.h b/src/include/access/transam.h index 01f248a41e..7796f7248c 100644 --- a/src/include/access/transam.h +++ b/src/include/access/transam.h @@ -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 diff --git a/src/include/access/twophase.h b/src/include/access/twophase.h index b9a531c96e..497b92f2b8 100644 --- a/src/include/access/twophase.h +++ b/src/include/access/twophase.h @@ -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); diff --git a/src/include/access/xact.h b/src/include/access/xact.h index a20726afa0..2ca61ea79d 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -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" @@ -428,6 +429,16 @@ extern XLogRecPtr XactLogAbortRecord(TimestampTz abort_time, const char *twophase_gid); extern void xact_redo(XLogReaderState *record); +/* functions to allow undo execution */ +extern void SetCurrentUndoLocation(UndoRecPtr urec_ptr, + UndoLogCategory category); +extern void ResetUndoActionsInfo(void); +extern bool NeedToPerformUndoActions(void); +extern void ReleaseResourcesAndProcessUndo(void); +extern bool ProcessUndoRequestForEachLogCat(FullTransactionId fxid, Oid dbid, + UndoRecPtr *end_urec_ptr, UndoRecPtr *start_urec_ptr, + bool *undoRequestResgistered, bool isSubTrans); + /* xactdesc.c */ extern void xact_desc(StringInfo buf, XLogReaderState *record); extern const char *xact_identify(uint8 info); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 1afc4d3b5d..5efc7a2a9a 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -74,6 +74,12 @@ * *critical* code should be marked as a critical section! Currently, this * mechanism is only used for XLOG-related code. * + * Similar to the "critical section", we have another mechanism known as + * "semi critical section". It generally has similar behaviour as + * "critical section" with the difference that it causes any ereport(ERROR) to + * become ereport(FATAL). Currently this is used by undo-machinery, but it + * can be used in other places too. + * *****************************************************************************/ /* in globals.c */ @@ -90,6 +96,7 @@ extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost; extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount; extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount; extern PGDLLIMPORT volatile uint32 CritSectionCount; +extern PGDLLIMPORT volatile uint32 SemiCritSectionCount; /* in tcop/postgres.c */ extern void ProcessInterrupts(void); @@ -137,6 +144,14 @@ do { \ CritSectionCount--; \ } while(0) +#define START_SEMI_CRIT_SECTION() (SemiCritSectionCount++) + +#define END_SEMI_CRIT_SECTION() \ +do { \ + Assert(SemiCritSectionCount > 0); \ + SemiCritSectionCount--; \ +} while(0) + /***************************************************************************** * globals.h -- * diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index dbfd8efd26..ed31351bfa 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -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); -- 2.16.2.windows.1