From b0ad139a73df896595f850de733557de71e59b1c Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Thu, 13 Jun 2019 15:42:46 +0530 Subject: [PATCH] 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 | 81 ++- src/backend/access/transam/varsup.c | 24 + src/backend/access/transam/xact.c | 595 +++++++++++++++++- src/backend/access/undo/README.UndoProcessing | 39 ++ src/backend/access/undo/undoaccess.c | 7 + src/backend/utils/error/elog.c | 36 +- src/backend/utils/init/globals.c | 6 + src/backend/utils/resowner/resowner.c | 11 +- src/include/access/transam.h | 1 + src/include/access/twophase.h | 3 +- src/include/access/xact.h | 10 + src/include/miscadmin.h | 2 + src/include/utils/elog.h | 3 + 13 files changed, 797 insertions(+), 21 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..c401885465 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" @@ -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); 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..d760796475 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,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 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/utils/error/elog.c b/src/backend/utils/error/elog.c index 8b4720ef3a..c665269023 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -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 diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index 3bf96de256..4e751d03ba 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -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. * diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index 7be11c48ab..3b2a28bfe8 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 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); } } 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..6bcdc809e2 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" @@ -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); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 1afc4d3b5d..388dc97a50 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -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; diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index dbfd8efd26..0b227ab42f 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); @@ -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); -- 2.39.5