diff options
Diffstat (limited to 'src/backend/access/transam/xact.c')
-rw-r--r-- | src/backend/access/transam/xact.c | 1501 |
1 files changed, 1293 insertions, 208 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 2ae0fc5b21d..fcf5b374453 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.168 2004/06/03 02:08:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.169 2004/07/01 00:49:42 tgl Exp $ * * NOTES * Transaction aborts can now occur two ways: @@ -148,6 +148,7 @@ #include "access/hash.h" #include "access/nbtree.h" #include "access/rtree.h" +#include "access/subtrans.h" #include "access/xact.h" #include "catalog/heap.h" #include "catalog/index.h" @@ -190,20 +191,53 @@ static void CommitTransaction(void); static void RecordTransactionAbort(void); static void StartTransaction(void); +static void RecordSubTransactionCommit(void); +static void StartSubTransaction(void); +static void CommitSubTransaction(void); +static void AbortSubTransaction(void); +static void CleanupSubTransaction(void); +static void PushTransaction(void); +static void PopTransaction(void); + +static void AtSubAbort_Locks(void); +static void AtSubAbort_Memory(void); +static void AtSubCleanup_Memory(void); +static void AtSubCommit_Memory(void); +static void AtSubStart_Memory(void); + +static void ShowTransactionState(const char *str); +static void ShowTransactionStateRec(TransactionState state); +static const char *BlockStateAsString(TBlockState blockState); +static const char *TransStateAsString(TransState state); + /* - * global variables holding the current transaction state. + * CurrentTransactionState always points to the current transaction state + * block. It will point to TopTransactionStateData when not in a + * transaction at all, or when in a top-level transaction. */ -static TransactionStateData CurrentTransactionStateData = { +static TransactionStateData TopTransactionStateData = { 0, /* transaction id */ FirstCommandId, /* command id */ - 0, /* scan command id */ - 0x0, /* start time */ TRANS_DEFAULT, /* transaction state */ - TBLOCK_DEFAULT /* transaction block state from the client + TBLOCK_DEFAULT, /* transaction block state from the client * perspective */ + 0, /* nesting level */ + NULL, /* cur transaction context */ + NIL, /* subcommitted child Xids */ + 0, /* entry-time current userid */ + NULL /* link to parent state block */ }; -static TransactionState CurrentTransactionState = &CurrentTransactionStateData; +static TransactionState CurrentTransactionState = &TopTransactionStateData; + +/* + * These vars hold the value of now(), ie, the transaction start time. + * This does not change as we enter and exit subtransactions, so we don't + * keep it inside the TransactionState stack. + */ +static AbsoluteTime xactStartTime; /* integer part */ +static int xactStartTimeUsec; /* microsecond part */ + /* * User-tweakable parameters @@ -282,7 +316,8 @@ IsAbortedTransactionBlockState(void) { TransactionState s = CurrentTransactionState; - if (s->blockState == TBLOCK_ABORT) + if (s->blockState == TBLOCK_ABORT || + s->blockState == TBLOCK_SUBABORT) return true; return false; @@ -290,6 +325,19 @@ IsAbortedTransactionBlockState(void) /* + * GetTopTransactionId + * + * Get the ID of the main transaction, even if we are currently inside + * a subtransaction. + */ +TransactionId +GetTopTransactionId(void) +{ + return TopTransactionStateData.transactionIdData; +} + + +/* * GetCurrentTransactionId */ TransactionId @@ -319,9 +367,7 @@ GetCurrentCommandId(void) AbsoluteTime GetCurrentTransactionStartTime(void) { - TransactionState s = CurrentTransactionState; - - return s->startTime; + return xactStartTime; } @@ -331,11 +377,23 @@ GetCurrentTransactionStartTime(void) AbsoluteTime GetCurrentTransactionStartTimeUsec(int *msec) { - TransactionState s = CurrentTransactionState; + *msec = xactStartTimeUsec; + return xactStartTime; +} + - *msec = s->startTimeUsec; +/* + * GetCurrentTransactionNestLevel + * + * Note: this will return zero when not inside any transaction, one when + * inside a top-level transaction, etc. + */ +int +GetCurrentTransactionNestLevel(void) +{ + TransactionState s = CurrentTransactionState; - return s->startTime; + return s->nestingLevel; } @@ -358,19 +416,27 @@ TransactionIdIsCurrentTransactionId(TransactionId xid) return false; } - return TransactionIdEquals(xid, s->transactionIdData); -} + /* + * 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. + */ + while (s != NULL) + { + ListCell *cell; + if (TransactionIdEquals(xid, s->transactionIdData)) + return true; + foreach(cell, s->childXids) + { + if (TransactionIdEquals(xid, lfirst_int(cell))) + return true; + } -/* - * CommandIdIsCurrentCommandId - */ -bool -CommandIdIsCurrentCommandId(CommandId cid) -{ - TransactionState s = CurrentTransactionState; + s = s->parent; + } - return (cid == s->commandId); + return false; } @@ -437,13 +503,15 @@ AtStart_Locks(void) static void AtStart_Memory(void) { + TransactionState s = CurrentTransactionState; + /* * We shouldn't have a transaction context already. */ Assert(TopTransactionContext == NULL); /* - * Create a toplevel context for the transaction, and make it active. + * Create a toplevel context for the transaction. */ TopTransactionContext = AllocSetContextCreate(TopMemoryContext, @@ -452,9 +520,47 @@ AtStart_Memory(void) ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - MemoryContextSwitchTo(TopTransactionContext); + /* + * In a top-level transaction, CurTransactionContext is the same as + * TopTransactionContext. + */ + CurTransactionContext = TopTransactionContext; + s->curTransactionContext = CurTransactionContext; + + /* Make the CurTransactionContext active. */ + MemoryContextSwitchTo(CurTransactionContext); } +/* ---------------------------------------------------------------- + * StartSubTransaction stuff + * ---------------------------------------------------------------- + */ + +/* + * AtSubStart_Memory + */ +static void +AtSubStart_Memory(void) +{ + TransactionState s = CurrentTransactionState; + + Assert(CurTransactionContext != NULL); + + /* + * Create a CurTransactionContext, which will be used to hold data that + * survives subtransaction commit but disappears on subtransaction abort. + * We make it a child of the immediate parent's CurTransactionContext. + */ + CurTransactionContext = AllocSetContextCreate(CurTransactionContext, + "CurTransactionContext", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + s->curTransactionContext = CurTransactionContext; + + /* Make the CurTransactionContext active. */ + MemoryContextSwitchTo(CurTransactionContext); +} /* ---------------------------------------------------------------- * CommitTransaction stuff @@ -467,13 +573,25 @@ AtStart_Memory(void) void RecordTransactionCommit(void) { + int nrels; + RelFileNode *rptr; + int nchildren; + TransactionId *children; + + /* Get data needed for commit record */ + nrels = smgrGetPendingDeletes(true, &rptr); + nchildren = xactGetCommittedChildren(&children, false); + /* - * If we made neither any XLOG entries nor any temp-rel updates, we - * can omit recording the transaction commit at all. + * If we made neither any XLOG entries nor any temp-rel updates, + * and have no files to be deleted, we can omit recording the transaction + * commit at all. (This test includes the effects of subtransactions, + * so the presence of committed subxacts need not alone force a write.) */ - if (MyXactMadeXLogEntry || MyXactMadeTempRelUpdate) + if (MyXactMadeXLogEntry || MyXactMadeTempRelUpdate || nrels > 0) { TransactionId xid = GetCurrentTransactionId(); + bool madeTCentries; XLogRecPtr recptr; /* Tell bufmgr and smgr to prepare for commit */ @@ -482,40 +600,46 @@ RecordTransactionCommit(void) START_CRIT_SECTION(); /* - * We only need to log the commit in xlog if the transaction made - * any transaction-controlled XLOG entries. (Otherwise, its XID - * appears nowhere in permanent storage, so no one else will ever - * care if it committed.) + * We only need to log the commit in XLOG if the transaction made + * any transaction-controlled XLOG entries or will delete files. + * (If it made no transaction-controlled XLOG entries, its XID + * appears nowhere in permanent storage, so no one else will ever care + * if it committed.) */ - if (MyLastRecPtr.xrecoff != 0) + madeTCentries = (MyLastRecPtr.xrecoff != 0); + if (madeTCentries || nrels > 0) { - /* Need to emit a commit record */ - XLogRecData rdata[2]; + XLogRecData rdata[3]; + int lastrdata = 0; xl_xact_commit xlrec; - int nrels; - RelFileNode *rptr; - - nrels = smgrGetPendingDeletes(true, &rptr); xlrec.xtime = time(NULL); + xlrec.nrels = nrels; + xlrec.nsubxacts = nchildren; rdata[0].buffer = InvalidBuffer; rdata[0].data = (char *) (&xlrec); rdata[0].len = MinSizeOfXactCommit; + /* dump rels to delete */ if (nrels > 0) { rdata[0].next = &(rdata[1]); rdata[1].buffer = InvalidBuffer; rdata[1].data = (char *) rptr; rdata[1].len = nrels * sizeof(RelFileNode); - rdata[1].next = NULL; + lastrdata = 1; } - else - rdata[0].next = NULL; + /* dump committed child Xids */ + if (nchildren > 0) + { + rdata[lastrdata].next = &(rdata[2]); + rdata[2].buffer = InvalidBuffer; + rdata[2].data = (char *) children; + rdata[2].len = nchildren * sizeof(TransactionId); + lastrdata = 2; + } + rdata[lastrdata].next = NULL; recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT, rdata); - - if (rptr) - pfree(rptr); } else { @@ -529,6 +653,9 @@ RecordTransactionCommit(void) * example, if we reported a nextval() result to the client, this * ensures that any XLOG record generated by nextval will hit the * disk before we report the transaction committed. + * + * Note: if we generated a commit record above, MyXactMadeXLogEntry + * will certainly be set now. */ if (MyXactMadeXLogEntry) { @@ -560,8 +687,12 @@ RecordTransactionCommit(void) * is okay because no one else will ever care whether we * committed. */ - if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate) + if (madeTCentries || MyXactMadeTempRelUpdate) + { TransactionIdCommit(xid); + /* to avoid race conditions, the parent must commit first */ + TransactionIdCommitTree(nchildren, children); + } END_CRIT_SECTION(); } @@ -573,6 +704,12 @@ RecordTransactionCommit(void) /* Show myself as out of the transaction in PGPROC array */ MyProc->logRec.xrecoff = 0; + + /* And clean up local data */ + if (rptr) + pfree(rptr); + if (children) + pfree(children); } @@ -590,7 +727,7 @@ AtCommit_Cache(void) /* * Make catalog changes visible to all backends. */ - AtEOXactInvalidationMessages(true); + AtEOXact_Inval(true); } /* @@ -602,7 +739,7 @@ AtCommit_LocalCache(void) /* * Make catalog changes visible to me for the next command. */ - CommandEndInvalidationMessages(true); + CommandEndInvalidationMessages(); } /* @@ -616,7 +753,7 @@ AtCommit_Locks(void) * * Then you're up a creek! -mer 5/24/92 */ - ProcReleaseLocks(true); + ProcReleaseLocks(ReleaseAllExceptSession, 0, NULL); } /* @@ -638,6 +775,88 @@ AtCommit_Memory(void) Assert(TopTransactionContext != NULL); MemoryContextDelete(TopTransactionContext); TopTransactionContext = NULL; + CurTransactionContext = NULL; + CurrentTransactionState->curTransactionContext = NULL; +} + +/* ---------------------------------------------------------------- + * CommitSubTransaction stuff + * ---------------------------------------------------------------- + */ + +/* + * AtSubCommit_Memory + * + * We do not throw away the child's CurTransactionContext, since the data + * it contains will be needed at upper commit. + */ +static void +AtSubCommit_Memory(void) +{ + TransactionState s = CurrentTransactionState; + + Assert(s->parent != NULL); + + /* Return to parent transaction level's memory context. */ + CurTransactionContext = s->parent->curTransactionContext; + MemoryContextSwitchTo(CurTransactionContext); +} + +/* + * AtSubCommit_childXids + * + * Pass my own XID and my child XIDs up to my parent as committed children. + */ +static void +AtSubCommit_childXids(void) +{ + TransactionState s = CurrentTransactionState; + MemoryContext old_cxt; + + Assert(s->parent != NULL); + + old_cxt = MemoryContextSwitchTo(s->parent->curTransactionContext); + + s->parent->childXids = list_concat(s->parent->childXids, s->childXids); + s->childXids = NIL; /* ensure list not doubly referenced */ + + s->parent->childXids = lappend_int(s->parent->childXids, + s->transactionIdData); + + MemoryContextSwitchTo(old_cxt); +} + +/* + * RecordSubTransactionCommit + */ +static void +RecordSubTransactionCommit(void) +{ + /* + * We do not log the subcommit in XLOG; it doesn't matter until + * the top-level transaction commits. + * + * We must mark the subtransaction subcommitted in clog if its XID + * appears either in permanent rels or in local temporary rels. We + * test this by seeing if we made transaction-controlled entries + * *OR* local-rel tuple updates. (The test here actually covers the + * entire transaction tree so far, so it may mark subtransactions that + * don't really need it, but it's probably not worth being tenser. + * Note that if a prior subtransaction dirtied these variables, then + * RecordTransactionCommit will have to do the full pushup anyway...) + */ + if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate) + { + TransactionId xid = GetCurrentTransactionId(); + + /* XXX does this really need to be a critical section? */ + START_CRIT_SECTION(); + + /* Record subtransaction subcommit */ + TransactionIdSubCommit(xid); + + END_CRIT_SECTION(); + } } /* ---------------------------------------------------------------- @@ -651,14 +870,24 @@ AtCommit_Memory(void) static void RecordTransactionAbort(void) { + int nrels; + RelFileNode *rptr; + int nchildren; + TransactionId *children; + + /* Get data needed for abort record */ + nrels = smgrGetPendingDeletes(false, &rptr); + nchildren = xactGetCommittedChildren(&children, false); + /* * If we made neither any transaction-controlled XLOG entries nor any - * temp-rel updates, we can omit recording the transaction abort at - * all. No one will ever care that it aborted. + * temp-rel updates, and are not going to delete any files, we can omit + * recording the transaction abort at all. No one will ever care that + * it aborted. (These tests cover our whole transaction tree.) */ - if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate) + if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate || nrels > 0) { - TransactionId xid = GetCurrentTransactionId(); + TransactionId xid = GetCurrentTransactionId(); /* * Catch the scenario where we aborted partway through @@ -671,50 +900,64 @@ RecordTransactionAbort(void) /* * We only need to log the abort in XLOG if the transaction made - * any transaction-controlled XLOG entries. (Otherwise, its XID - * appears nowhere in permanent storage, so no one else will ever - * care if it committed.) We do not flush XLOG to disk unless - * deleting files, since the default assumption after a crash - * would be that we aborted, anyway. + * any transaction-controlled XLOG entries or will delete files. + * (If it made no transaction-controlled XLOG entries, its XID + * appears nowhere in permanent storage, so no one else will ever care + * if it committed.) + * + * We do not flush XLOG to disk unless deleting files, since the + * default assumption after a crash would be that we aborted, anyway. */ - if (MyLastRecPtr.xrecoff != 0) + if (MyLastRecPtr.xrecoff != 0 || nrels > 0) { - XLogRecData rdata[2]; + XLogRecData rdata[3]; + int lastrdata = 0; xl_xact_abort xlrec; - int nrels; - RelFileNode *rptr; XLogRecPtr recptr; - nrels = smgrGetPendingDeletes(false, &rptr); - xlrec.xtime = time(NULL); + xlrec.nrels = nrels; + xlrec.nsubxacts = nchildren; rdata[0].buffer = InvalidBuffer; rdata[0].data = (char *) (&xlrec); rdata[0].len = MinSizeOfXactAbort; + /* dump rels to delete */ if (nrels > 0) { rdata[0].next = &(rdata[1]); rdata[1].buffer = InvalidBuffer; rdata[1].data = (char *) rptr; rdata[1].len = nrels * sizeof(RelFileNode); - rdata[1].next = NULL; + lastrdata = 1; } - else - rdata[0].next = NULL; + /* dump committed child Xids */ + if (nchildren > 0) + { + rdata[lastrdata].next = &(rdata[2]); + rdata[2].buffer = InvalidBuffer; + rdata[2].data = (char *) children; + rdata[2].len = nchildren * sizeof(TransactionId); + lastrdata = 2; + } + rdata[lastrdata].next = NULL; recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, rdata); + /* Must flush if we are deleting files... */ if (nrels > 0) XLogFlush(recptr); - - if (rptr) - pfree(rptr); } /* * Mark the transaction aborted in clog. This is not absolutely * necessary but we may as well do it while we are here. + * + * The ordering here isn't critical but it seems best to mark the + * parent last. That reduces the chance that concurrent + * TransactionIdDidAbort calls will decide they need to do redundant + * work. */ + TransactionIdAbortTree(nchildren, children); TransactionIdAbort(xid); END_CRIT_SECTION(); @@ -727,6 +970,12 @@ RecordTransactionAbort(void) /* Show myself as out of the transaction in PGPROC array */ MyProc->logRec.xrecoff = 0; + + /* And clean up local data */ + if (rptr) + pfree(rptr); + if (children) + pfree(children); } /* @@ -736,7 +985,7 @@ static void AtAbort_Cache(void) { AtEOXact_RelationCache(false); - AtEOXactInvalidationMessages(false); + AtEOXact_Inval(false); } /* @@ -750,7 +999,7 @@ AtAbort_Locks(void) * * Then you're up a creek without a paddle! -mer */ - ProcReleaseLocks(false); + ProcReleaseLocks(ReleaseAll, 0, NULL); } @@ -779,6 +1028,127 @@ AtAbort_Memory(void) MemoryContextSwitchTo(TopMemoryContext); } +/* + * AtSubAbort_Locks + */ +static void +AtSubAbort_Locks(void) +{ + int nxids; + TransactionId *xids; + + nxids = xactGetCommittedChildren(&xids, true); + + ProcReleaseLocks(ReleaseGivenXids, nxids, xids); + + pfree(xids); +} + + +/* + * AtSubAbort_Memory + */ +static void +AtSubAbort_Memory(void) +{ + Assert(TopTransactionContext != NULL); + + MemoryContextSwitchTo(TopTransactionContext); +} + +/* + * RecordSubTransactionAbort + */ +static void +RecordSubTransactionAbort(void) +{ + int nrels; + RelFileNode *rptr; + int nchildren; + TransactionId *children; + + /* Get data needed for abort record */ + nrels = smgrGetPendingDeletes(false, &rptr); + nchildren = xactGetCommittedChildren(&children, false); + + /* + * If we made neither any transaction-controlled XLOG entries nor any + * temp-rel updates, and are not going to delete any files, we can omit + * recording the transaction abort at all. No one will ever care that + * it aborted. (These tests cover our whole transaction tree, and + * therefore may mark subxacts that don't really need it, but it's + * probably not worth being tenser.) + * + * In this case we needn't worry about marking subcommitted children as + * aborted, because they didn't mark themselves as subcommitted in the + * first place; see the optimization in RecordSubTransactionCommit. + */ + if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate || nrels > 0) + { + TransactionId xid = GetCurrentTransactionId(); + + START_CRIT_SECTION(); + + /* + * We only need to log the abort in XLOG if the transaction made + * any transaction-controlled XLOG entries or will delete files. + */ + if (MyLastRecPtr.xrecoff != 0 || nrels > 0) + { + XLogRecData rdata[3]; + int lastrdata = 0; + xl_xact_abort xlrec; + XLogRecPtr recptr; + + xlrec.xtime = time(NULL); + xlrec.nrels = nrels; + xlrec.nsubxacts = nchildren; + rdata[0].buffer = InvalidBuffer; + rdata[0].data = (char *) (&xlrec); + rdata[0].len = MinSizeOfXactAbort; + /* dump rels to delete */ + if (nrels > 0) + { + rdata[0].next = &(rdata[1]); + rdata[1].buffer = InvalidBuffer; + rdata[1].data = (char *) rptr; + rdata[1].len = nrels * sizeof(RelFileNode); + lastrdata = 1; + } + /* dump committed child Xids */ + if (nchildren > 0) + { + rdata[lastrdata].next = &(rdata[2]); + rdata[2].buffer = InvalidBuffer; + rdata[2].data = (char *) children; + rdata[2].len = nchildren * sizeof(TransactionId); + lastrdata = 2; + } + rdata[lastrdata].next = NULL; + + recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, rdata); + + /* Must flush if we are deleting files... */ + if (nrels > 0) + XLogFlush(recptr); + } + + /* + * Mark the transaction aborted in clog. This is not absolutely + * necessary but we may as well do it while we are here. + */ + TransactionIdAbortTree(nchildren, children); + TransactionIdAbort(xid); + + END_CRIT_SECTION(); + } + + /* And clean up local data */ + if (rptr) + pfree(rptr); + if (children) + pfree(children); +} /* ---------------------------------------------------------------- * CleanupTransaction stuff @@ -798,16 +1168,47 @@ AtCleanup_Memory(void) */ MemoryContextSwitchTo(TopMemoryContext); + Assert(CurrentTransactionState->parent == NULL); + /* * Release all transaction-local memory. */ if (TopTransactionContext != NULL) MemoryContextDelete(TopTransactionContext); TopTransactionContext = NULL; + CurTransactionContext = NULL; + CurrentTransactionState->curTransactionContext = NULL; } /* ---------------------------------------------------------------- + * CleanupSubTransaction stuff + * ---------------------------------------------------------------- + */ + +/* + * AtSubCleanup_Memory + */ +static void +AtSubCleanup_Memory(void) +{ + TransactionState s = CurrentTransactionState; + + Assert(s->parent != NULL); + + /* Make sure we're not in an about-to-be-deleted context */ + MemoryContextSwitchTo(s->parent->curTransactionContext); + CurTransactionContext = s->parent->curTransactionContext; + + /* + * Delete the subxact local memory contexts. Its CurTransactionContext + * can go too (note this also kills CurTransactionContexts from any + * children of the subxact). + */ + MemoryContextDelete(s->curTransactionContext); +} + +/* ---------------------------------------------------------------- * interface routines * ---------------------------------------------------------------- */ @@ -842,20 +1243,34 @@ StartTransaction(void) /* * generate a new transaction id */ - s->transactionIdData = GetNewTransactionId(); + s->transactionIdData = GetNewTransactionId(false); XactLockTableInsert(s->transactionIdData); /* + * set now() + */ + xactStartTime = GetCurrentAbsoluteTimeUsec(&(xactStartTimeUsec)); + + /* * initialize current transaction state fields */ s->commandId = FirstCommandId; - s->startTime = GetCurrentAbsoluteTimeUsec(&(s->startTimeUsec)); + s->nestingLevel = 1; + s->childXids = NIL; + + /* + * You might expect to see "s->currentUser = GetUserId();" here, but + * you won't because it doesn't work during startup; the userid isn't + * set yet during a backend's first transaction start. We only use + * the currentUser field in sub-transaction state structs. + */ /* * initialize the various transaction subsystems */ AtStart_Memory(); + AtStart_Inval(); AtStart_Cache(); AtStart_Locks(); @@ -870,6 +1285,7 @@ StartTransaction(void) */ s->state = TRANS_INPROGRESS; + ShowTransactionState("StartTransaction"); } /* @@ -880,11 +1296,14 @@ CommitTransaction(void) { TransactionState s = CurrentTransactionState; + ShowTransactionState("CommitTransaction"); + /* * check the current transaction state */ if (s->state != TRANS_INPROGRESS) elog(WARNING, "CommitTransaction and not in in-progress state"); + Assert(s->parent == NULL); /* * Tell the trigger manager that this transaction is about to be @@ -970,19 +1389,22 @@ CommitTransaction(void) AtCommit_Locks(); CallEOXactCallbacks(true); - AtEOXact_GUC(true); + AtEOXact_GUC(true, false); AtEOXact_SPI(true); AtEOXact_gist(); AtEOXact_hash(); AtEOXact_nbtree(); AtEOXact_rtree(); - AtEOXact_on_commit_actions(true); + AtEOXact_on_commit_actions(true, s->transactionIdData); AtEOXact_Namespace(true); AtEOXact_CatCache(true); AtEOXact_Files(); pgstat_count_xact_commit(); AtCommit_Memory(); + s->nestingLevel = 0; + s->childXids = NIL; + /* * done with commit processing, set current transaction state back to * default @@ -1026,6 +1448,7 @@ AbortTransaction(void) */ if (s->state != TRANS_INPROGRESS) elog(WARNING, "AbortTransaction and not in in-progress state"); + Assert(s->parent == NULL); /* * set the current transaction state information appropriately during @@ -1037,7 +1460,14 @@ AbortTransaction(void) AtAbort_Memory(); /* - * Reset user id which might have been changed transiently + * Reset user id which might have been changed transiently. We cannot + * use s->currentUser, but must get the session userid from miscinit.c. + * + * (Note: it is not necessary to restore session authorization here + * because that can only be changed via GUC, and GUC will take care of + * rolling it back if need be. However, an error within a SECURITY + * DEFINER function could send control here with the wrong current + * userid.) */ SetUserId(GetSessionUserId()); @@ -1080,13 +1510,13 @@ AbortTransaction(void) AtAbort_Locks(); CallEOXactCallbacks(false); - AtEOXact_GUC(false); + AtEOXact_GUC(false, false); AtEOXact_SPI(false); AtEOXact_gist(); AtEOXact_hash(); AtEOXact_nbtree(); AtEOXact_rtree(); - AtEOXact_on_commit_actions(false); + AtEOXact_on_commit_actions(false, s->transactionIdData); AtEOXact_Namespace(false); AtEOXact_CatCache(false); AtEOXact_Files(); @@ -1119,6 +1549,9 @@ CleanupTransaction(void) AtCleanup_Portals(); /* now safe to release portal memory */ AtCleanup_Memory(); /* and transaction memory */ + s->nestingLevel = 0; + s->childXids = NIL; + /* * done with abort processing, set current transaction state back to * default @@ -1146,45 +1579,13 @@ StartTransactionCommand(void) break; /* - * We should never experience this -- it means the STARTED state - * was not changed in the previous CommitTransactionCommand. - */ - case TBLOCK_STARTED: - elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_STARTED"); - break; - - /* - * We should never experience this -- if we do it means the - * BEGIN state was not changed in the previous - * CommitTransactionCommand(). If we get it, we print a - * warning and change to the in-progress state. - */ - case TBLOCK_BEGIN: - elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_BEGIN"); - s->blockState = TBLOCK_INPROGRESS; - break; - - /* * This is the case when are somewhere in a transaction block * and about to start a new command. For now we do nothing * but someday we may do command-local resource * initialization. */ case TBLOCK_INPROGRESS: - break; - - /* - * As with BEGIN, we should never experience this if we do it - * means the END state was not changed in the previous - * CommitTransactionCommand(). If we get it, we print a - * warning, commit the transaction, start a new transaction - * and change to the default state. - */ - case TBLOCK_END: - elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_END"); - CommitTransaction(); - StartTransaction(); - s->blockState = TBLOCK_DEFAULT; + case TBLOCK_SUBINPROGRESS: break; /* @@ -1194,26 +1595,30 @@ StartTransactionCommand(void) * TRANSACTION" which will set things straight. */ case TBLOCK_ABORT: + case TBLOCK_SUBABORT: break; - /* - * This means we somehow aborted and the last call to - * CommitTransactionCommand() didn't clear the state so we - * remain in the ENDABORT state and maybe next time we get to - * CommitTransactionCommand() the state will get reset to - * default. - */ + /* These cases are invalid. */ + case TBLOCK_STARTED: + case TBLOCK_BEGIN: + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + case TBLOCK_END: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_ENDABORT: - elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_ENDABORT"); + elog(FATAL, "StartTransactionCommand: unexpected state %s", + BlockStateAsString(s->blockState)); break; } /* - * We must switch to TopTransactionContext before returning. This is + * We must switch to CurTransactionContext before returning. This is * already done if we called StartTransaction, otherwise not. */ - Assert(TopTransactionContext != NULL); - MemoryContextSwitchTo(TopTransactionContext); + Assert(CurTransactionContext != NULL); + MemoryContextSwitchTo(CurTransactionContext); } /* @@ -1232,7 +1637,7 @@ CommitTransactionCommand(void) * appropiately. */ case TBLOCK_DEFAULT: - elog(WARNING, "CommitTransactionCommand: unexpected TBLOCK_DEFAULT"); + elog(FATAL, "CommitTransactionCommand: unexpected TBLOCK_DEFAULT"); break; /* @@ -1291,6 +1696,71 @@ CommitTransactionCommand(void) CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; break; + + /* + * We were just issued a BEGIN inside a transaction block. + * Start a subtransaction. + */ + case TBLOCK_SUBBEGIN: + StartSubTransaction(); + s->blockState = TBLOCK_SUBINPROGRESS; + break; + + /* + * We were issued a BEGIN inside an aborted transaction block. + * Start a subtransaction, and put it in aborted state. + */ + case TBLOCK_SUBBEGINABORT: + StartSubTransaction(); + AbortSubTransaction(); + s->blockState = TBLOCK_SUBABORT; + break; + + /* + * Inside a subtransaction, increment the command counter. + */ + case TBLOCK_SUBINPROGRESS: + CommandCounterIncrement(); + break; + + /* + * We where issued a COMMIT command, so we end the current + * subtransaction and return to the parent transaction. + */ + case TBLOCK_SUBEND: + CommitSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + break; + + /* + * If we are in an aborted subtransaction, do nothing. + */ + case TBLOCK_SUBABORT: + break; + + /* + * We are ending a subtransaction that aborted nicely, + * so the parent can be allowed to live. + */ + case TBLOCK_SUBENDABORT_OK: + CleanupSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + break; + + /* + * We are ending a subtransaction that aborted in a unclean + * way (e.g. the user issued COMMIT in an aborted subtrasaction.) + * Abort the subtransaction, and abort the parent too. + */ + case TBLOCK_SUBENDABORT_ERROR: + CleanupSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + Assert(s->blockState != TBLOCK_SUBENDABORT_ERROR); + AbortCurrentTransaction(); + break; } } @@ -1362,6 +1832,7 @@ AbortCurrentTransaction(void) * state. */ case TBLOCK_ABORT: + case TBLOCK_SUBABORT: break; /* @@ -1374,6 +1845,53 @@ AbortCurrentTransaction(void) CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; break; + + /* + * If we are just starting a subtransaction, put it + * in aborted state. + */ + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + StartSubTransaction(); + AbortSubTransaction(); + s->blockState = TBLOCK_SUBABORT; + break; + + case TBLOCK_SUBINPROGRESS: + AbortSubTransaction(); + s->blockState = TBLOCK_SUBABORT; + break; + + /* + * If we are aborting an ending transaction, + * we have to abort the parent transaction too. + */ + case TBLOCK_SUBEND: + AbortSubTransaction(); + CleanupSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + Assert(s->blockState != TBLOCK_SUBEND && + s->blockState != TBLOCK_SUBENDABORT_OK && + s->blockState != TBLOCK_SUBENDABORT_ERROR); + AbortCurrentTransaction(); + break; + + /* + * Same as above, except the Abort() was already done. + */ + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + CleanupSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + Assert(s->blockState != TBLOCK_SUBEND && + s->blockState != TBLOCK_SUBENDABORT_OK && + s->blockState != TBLOCK_SUBENDABORT_ERROR); + AbortCurrentTransaction(); + break; } } @@ -1387,7 +1905,7 @@ AbortCurrentTransaction(void) * If we have already started a transaction block, issue an error; also issue * an error if we appear to be running inside a user-defined function (which * could issue more commands and possibly cause a failure after the statement - * completes). + * completes). Subtransactions are verboten too. * * stmtNode: pointer to parameter block for statement; this is used in * a very klugy way to determine whether we are inside a function. @@ -1407,6 +1925,16 @@ PreventTransactionChain(void *stmtNode, const char *stmtType) stmtType))); /* + * subtransaction? + */ + if (IsSubTransaction()) + ereport(ERROR, + (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), + /* translator: %s represents an SQL statement name */ + errmsg("%s cannot run inside a subtransaction", + stmtType))); + + /* * Are we inside a function call? If the statement's parameter block * was allocated in QueryContext, assume it is an interactive command. * Otherwise assume it is coming from a function. @@ -1416,10 +1944,11 @@ PreventTransactionChain(void *stmtNode, const char *stmtType) (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */ errmsg("%s cannot be executed from a function", stmtType))); + /* If we got past IsTransactionBlock test, should be in default state */ if (CurrentTransactionState->blockState != TBLOCK_DEFAULT && CurrentTransactionState->blockState != TBLOCK_STARTED) - elog(ERROR, "cannot prevent transaction chain"); + elog(FATAL, "cannot prevent transaction chain"); /* all okay */ } @@ -1433,8 +1962,8 @@ PreventTransactionChain(void *stmtNode, const char *stmtType) * * If we appear to be running inside a user-defined function, we do not * issue an error, since the function could issue more commands that make - * use of the current statement's results. Thus this is an inverse for - * PreventTransactionChain. + * use of the current statement's results. Likewise subtransactions. + * Thus this is an inverse for PreventTransactionChain. * * stmtNode: pointer to parameter block for statement; this is used in * a very klugy way to determine whether we are inside a function. @@ -1450,6 +1979,12 @@ RequireTransactionChain(void *stmtNode, const char *stmtType) return; /* + * subtransaction? + */ + if (IsSubTransaction()) + return; + + /* * Are we inside a function call? If the statement's parameter block * was allocated in QueryContext, assume it is an interactive command. * Otherwise assume it is coming from a function. @@ -1483,6 +2018,9 @@ IsInTransactionChain(void *stmtNode) if (IsTransactionBlock()) return true; + if (IsSubTransaction()) + return true; + if (!MemoryContextContains(QueryContext, stmtNode)) return true; @@ -1571,26 +2109,40 @@ BeginTransactionBlock(void) s->blockState = TBLOCK_BEGIN; break; - /* Already a transaction block in progress. */ + /* + * Already a transaction block in progress. + * Start a subtransaction. + */ case TBLOCK_INPROGRESS: - ereport(WARNING, - (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), - errmsg("there is already a transaction in progress"))); + case TBLOCK_SUBINPROGRESS: + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + s->blockState = TBLOCK_SUBBEGIN; + break; /* - * This shouldn't happen, because a transaction in aborted state - * will not be allowed to call BeginTransactionBlock. + * An aborted transaction block should be allowed to start + * a subtransaction, but it must put it in aborted state. */ case TBLOCK_ABORT: - elog(WARNING, "BeginTransactionBlock: unexpected TBLOCK_ABORT"); + case TBLOCK_SUBABORT: + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + s->blockState = TBLOCK_SUBBEGINABORT; break; /* These cases are invalid. Reject them altogether. */ case TBLOCK_DEFAULT: case TBLOCK_BEGIN: + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: case TBLOCK_ENDABORT: case TBLOCK_END: - elog(FATAL, "BeginTransactionBlock: not in a user-allowed state!"); + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBEND: + elog(FATAL, "BeginTransactionBlock: unexpected state %s", + BlockStateAsString(s->blockState)); break; } } @@ -1615,6 +2167,15 @@ EndTransactionBlock(void) break; /* + * here we are in a subtransaction block. Signal + * CommitTransactionCommand() to end it and return to the + * parent transaction. + */ + case TBLOCK_SUBINPROGRESS: + s->blockState = TBLOCK_SUBEND; + break; + + /* * here, we are in a transaction block which aborted and since the * AbortTransaction() was already done, we do whatever is needed * and change to the special "END ABORT" state. The upcoming @@ -1625,12 +2186,21 @@ EndTransactionBlock(void) s->blockState = TBLOCK_ENDABORT; break; + /* + * here we are in an aborted subtransaction. Signal + * CommitTransactionCommand() to clean up and return to the + * parent transaction. + */ + case TBLOCK_SUBABORT: + s->blockState = TBLOCK_SUBENDABORT_ERROR; + break; + case TBLOCK_STARTED: /* - * here, the user issued COMMIT when not inside a transaction. Issue a - * WARNING and go to abort state. The upcoming call to - * CommitTransactionCommand() will then put us back into the default - * state. + * here, the user issued COMMIT when not inside a + * transaction. Issue a WARNING and go to abort state. The + * upcoming call to CommitTransactionCommand() will then put us + * back into the default state. */ ereport(WARNING, (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), @@ -1644,7 +2214,13 @@ EndTransactionBlock(void) case TBLOCK_BEGIN: case TBLOCK_ENDABORT: case TBLOCK_END: - elog(FATAL, "EndTransactionBlock and not in a user-allowed state"); + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + elog(FATAL, "EndTransactionBlock: unexpected state %s", + BlockStateAsString(s->blockState)); break; } } @@ -1657,42 +2233,68 @@ UserAbortTransactionBlock(void) { TransactionState s = CurrentTransactionState; - /* - * if the transaction has already been automatically aborted with an - * error, and the user subsequently types 'abort', allow it. (the - * behavior is the same as if they had typed 'end'.) - */ - if (s->blockState == TBLOCK_ABORT) - { - s->blockState = TBLOCK_ENDABORT; - return; - } - - if (s->blockState == TBLOCK_INPROGRESS) - { + switch (s->blockState) { /* - * here we were inside a transaction block and we got an abort - * command from the user, so we move to the ENDABORT state and - * do abort processing so we will end up in the default state - * after the upcoming CommitTransactionCommand(). + * here we are inside a failed transaction block and we got an abort + * command from the user. Abort processing is already done, we just + * need to move to the ENDABORT state so we will end up in the default + * state after the upcoming CommitTransactionCommand(). */ - s->blockState = TBLOCK_ABORT; - AbortTransaction(); - s->blockState = TBLOCK_ENDABORT; - return; + case TBLOCK_ABORT: + s->blockState = TBLOCK_ENDABORT; + break; + + /* Ditto, for a subtransaction. */ + case TBLOCK_SUBABORT: + s->blockState = TBLOCK_SUBENDABORT_OK; + break; + + /* + * here we are inside a transaction block and we got an abort + * command from the user, so we move to the ENDABORT state and + * do abort processing so we will end up in the default state + * after the upcoming CommitTransactionCommand(). + */ + case TBLOCK_INPROGRESS: + AbortTransaction(); + s->blockState = TBLOCK_ENDABORT; + break; + + /* Ditto, for a subtransaction. */ + case TBLOCK_SUBINPROGRESS: + AbortSubTransaction(); + s->blockState = TBLOCK_SUBENDABORT_OK; + break; + + /* + * here, the user issued ABORT when not inside a + * transaction. Issue a WARNING and go to abort state. The + * upcoming call to CommitTransactionCommand() will then put us + * back into the default state. + */ + case TBLOCK_STARTED: + ereport(WARNING, + (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), + errmsg("there is no transaction in progress"))); + AbortTransaction(); + s->blockState = TBLOCK_ENDABORT; + break; + + /* these cases are invalid. */ + case TBLOCK_DEFAULT: + case TBLOCK_BEGIN: + case TBLOCK_END: + case TBLOCK_ENDABORT: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + elog(FATAL, "UserAbortTransactionBlock: unexpected state %s", + BlockStateAsString(s->blockState)); + break; } - /* - * here, the user issued ABORT when not inside a transaction. Issue a - * WARNING and go to abort state. The upcoming call to - * CommitTransactionCommand() will then put us back into the default - * state. - */ - ereport(WARNING, - (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), - errmsg("there is no transaction in progress"))); - AbortTransaction(); - s->blockState = TBLOCK_ENDABORT; } /* @@ -1708,32 +2310,58 @@ AbortOutOfAnyTransaction(void) TransactionState s = CurrentTransactionState; /* - * Get out of any transaction + * Get out of any transaction or nested transaction */ - switch (s->blockState) - { - case TBLOCK_DEFAULT: - /* Not in a transaction, do nothing */ - break; - case TBLOCK_STARTED: - case TBLOCK_BEGIN: - case TBLOCK_INPROGRESS: - case TBLOCK_END: - /* In a transaction, so clean up */ - AbortTransaction(); - CleanupTransaction(); - break; - case TBLOCK_ABORT: - case TBLOCK_ENDABORT: - /* AbortTransaction already done, still need Cleanup */ - CleanupTransaction(); - break; - } + do { + switch (s->blockState) + { + case TBLOCK_DEFAULT: + /* Not in a transaction, do nothing */ + break; + case TBLOCK_STARTED: + case TBLOCK_BEGIN: + case TBLOCK_INPROGRESS: + case TBLOCK_END: + /* In a transaction, so clean up */ + AbortTransaction(); + CleanupTransaction(); + s->blockState = TBLOCK_DEFAULT; + break; + case TBLOCK_ABORT: + case TBLOCK_ENDABORT: + /* AbortTransaction already done, still need Cleanup */ + CleanupTransaction(); + s->blockState = TBLOCK_DEFAULT; + break; + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + /* + * Just starting a new transaction -- return to parent. + * FIXME -- Is this correct? + */ + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + break; + case TBLOCK_SUBINPROGRESS: + case TBLOCK_SUBEND: + /* In a subtransaction, so clean it up and abort parent too */ + AbortSubTransaction(); + CleanupSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + break; + case TBLOCK_SUBABORT: + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + CleanupSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + break; + } + } while (s->blockState != TBLOCK_DEFAULT); - /* - * Now reset the transaction state - */ - s->blockState = TBLOCK_DEFAULT; + /* Should be out of all subxacts now */ + Assert(s->parent == NULL); } /* @@ -1784,18 +2412,436 @@ TransactionBlockStatusCode(void) case TBLOCK_BEGIN: case TBLOCK_INPROGRESS: case TBLOCK_END: + case TBLOCK_SUBINPROGRESS: + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBEND: return 'T'; /* in transaction */ case TBLOCK_ABORT: case TBLOCK_ENDABORT: + case TBLOCK_SUBABORT: + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBBEGINABORT: return 'E'; /* in failed transaction */ } /* should never get here */ - elog(ERROR, "invalid transaction block state: %d", - (int) s->blockState); + elog(FATAL, "invalid transaction block state: %s", + BlockStateAsString(s->blockState)); return 0; /* keep compiler quiet */ } +/* + * IsSubTransaction + */ +bool +IsSubTransaction(void) +{ + TransactionState s = CurrentTransactionState; + + switch (s->blockState) { + case TBLOCK_DEFAULT: + case TBLOCK_STARTED: + case TBLOCK_BEGIN: + case TBLOCK_INPROGRESS: + case TBLOCK_END: + case TBLOCK_ABORT: + case TBLOCK_ENDABORT: + return false; + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + case TBLOCK_SUBINPROGRESS: + case TBLOCK_SUBABORT: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + return true; + } + + /* should never get here */ + elog(FATAL, "invalid transaction block state: %s", + BlockStateAsString(s->blockState)); + return false; /* keep compiler quiet */ +} + +/* + * StartSubTransaction + */ +static void +StartSubTransaction(void) +{ + TransactionState s = CurrentTransactionState; + + if (s->state != TRANS_DEFAULT) + elog(WARNING, "StartSubTransaction and not in default state"); + + s->state = TRANS_START; + + /* + * Generate a new Xid and record it in pg_subtrans. + */ + s->transactionIdData = GetNewTransactionId(true); + + SubTransSetParent(s->transactionIdData, s->parent->transactionIdData); + + /* + * Finish setup of other transaction state fields. + */ + s->currentUser = GetUserId(); + + /* Initialize the various transaction subsystems */ + AtSubStart_Memory(); + AtSubStart_Inval(); + AtSubStart_RelationCache(); + AtSubStart_CatCache(); + AtSubStart_Buffers(); + AtSubStart_smgr(); + AtSubStart_Notify(); + DeferredTriggerBeginSubXact(); + + s->state = TRANS_INPROGRESS; + + ShowTransactionState("StartSubTransaction"); +} + +/* + * CommitSubTransaction + */ +static void +CommitSubTransaction(void) +{ + TransactionState s = CurrentTransactionState; + + ShowTransactionState("CommitSubTransaction"); + + if (s->state != TRANS_INPROGRESS) + elog(WARNING, "CommitSubTransaction and not in in-progress state"); + + /* Pre-commit processing */ + AtSubCommit_Portals(s->parent->transactionIdData); + DeferredTriggerEndSubXact(true); + + /* Mark subtransaction as subcommitted */ + CommandCounterIncrement(); + RecordSubTransactionCommit(); + AtSubCommit_childXids(); + + /* Post-commit cleanup */ + AtSubCommit_smgr(); + + AtSubEOXact_Inval(true); + AtEOSubXact_SPI(true, s->transactionIdData); + AtSubCommit_Notify(); + AtEOXact_GUC(true, true); + AtEOSubXact_gist(s->transactionIdData); + AtEOSubXact_hash(s->transactionIdData); + AtEOSubXact_rtree(s->transactionIdData); + AtEOSubXact_on_commit_actions(true, s->transactionIdData, + s->parent->transactionIdData); + + AtEOSubXact_CatCache(true); + AtEOSubXact_RelationCache(true); + AtEOSubXact_Buffers(true); + AtSubCommit_Memory(); + + s->state = TRANS_DEFAULT; +} + +/* + * AbortSubTransaction + */ +static void +AbortSubTransaction(void) +{ + TransactionState s = CurrentTransactionState; + + ShowTransactionState("AbortSubTransaction"); + + HOLD_INTERRUPTS(); + + s->state = TRANS_ABORT; + + /* + * Release any LW locks we might be holding as quickly as possible. + * (Regular locks, however, must be held till we finish aborting.) + * Releasing LW locks is critical since we might try to grab them + * again while cleaning up! + * + * FIXME This may be incorrect --- Are there some locks we should keep? + * Buffer locks, for example? I don't think so but I'm not sure. + */ + LWLockReleaseAll(); + + AbortBufferIO(); + UnlockBuffers(); + + LockWaitCancel(); + + AtSubAbort_Memory(); + + /* + * do abort processing + */ + + RecordSubTransactionAbort(); + + /* Post-abort cleanup */ + AtSubAbort_smgr(); + + DeferredTriggerEndSubXact(false); + AtSubAbort_Portals(); + AtSubEOXact_Inval(false); + AtSubAbort_Locks(); + AtEOSubXact_SPI(false, s->transactionIdData); + AtSubAbort_Notify(); + AtEOXact_GUC(false, true); + AtEOSubXact_gist(s->transactionIdData); + AtEOSubXact_hash(s->transactionIdData); + AtEOSubXact_rtree(s->transactionIdData); + AtEOSubXact_on_commit_actions(false, s->transactionIdData, + s->parent->transactionIdData); + AtEOSubXact_RelationCache(false); + AtEOSubXact_CatCache(false); + AtEOSubXact_Buffers(false); + + /* + * Reset user id which might have been changed transiently. Here we + * want to restore to the userid that was current at subxact entry. + * (As in AbortTransaction, we need not worry about the session userid.) + * + * Must do this after AtEOXact_GUC to handle the case where we entered + * the subxact inside a SECURITY DEFINER function (hence current and + * session userids were different) and then session auth was changed + * inside the subxact. GUC will reset both current and session userids + * to the entry-time session userid. This is right in every other + * scenario so it seems simplest to let GUC do that and fix it here. + */ + SetUserId(s->currentUser); + + CommandCounterIncrement(); + + RESUME_INTERRUPTS(); +} + +/* + * CleanupSubTransaction + */ +static void +CleanupSubTransaction(void) +{ + TransactionState s = CurrentTransactionState; + + ShowTransactionState("CleanupSubTransaction"); + + if (s->state != TRANS_ABORT) + elog(WARNING, "CleanupSubTransaction and not in aborted state"); + + AtSubCleanup_Portals(); + AtSubCleanup_Memory(); + + s->state = TRANS_DEFAULT; +} + +/* + * PushTransaction + * Set up transaction state for a subtransaction + */ +static void +PushTransaction(void) +{ + TransactionState p = CurrentTransactionState; + TransactionState s; + + /* + * We keep subtransaction state nodes in TopTransactionContext. + */ + s = (TransactionState) + MemoryContextAllocZero(TopTransactionContext, + sizeof(TransactionStateData)); + s->parent = p; + s->nestingLevel = p->nestingLevel + 1; + s->state = TRANS_DEFAULT; + s->blockState = TBLOCK_SUBBEGIN; + + /* Command IDs count in a continuous sequence through subtransactions */ + s->commandId = p->commandId; + + /* + * Copy down some other data so that we will have valid state until + * StartSubTransaction runs. + */ + s->transactionIdData = p->transactionIdData; + s->curTransactionContext = p->curTransactionContext; + + CurrentTransactionState = s; +} + +/* + * PopTransaction + * Pop back to parent transaction state + */ +static void +PopTransaction(void) +{ + TransactionState s = CurrentTransactionState; + + if (s->state != TRANS_DEFAULT) + elog(WARNING, "PopTransaction and not in default state"); + + if (s->parent == NULL) + elog(FATAL, "PopTransaction with no parent"); + + /* Command IDs count in a continuous sequence through subtransactions */ + s->parent->commandId = s->commandId; + + CurrentTransactionState = s->parent; + + /* Let's just make sure CurTransactionContext is good */ + CurTransactionContext = s->parent->curTransactionContext; + MemoryContextSwitchTo(CurTransactionContext); + + /* Free the old child structure */ + pfree(s); +} + +/* + * ShowTransactionState + * Debug support + */ +static void +ShowTransactionState(const char *str) +{ + /* skip work if message will definitely not be printed */ + if (log_min_messages <= DEBUG2 || client_min_messages <= DEBUG2) + { + elog(DEBUG2, "%s", str); + ShowTransactionStateRec(CurrentTransactionState); + } +} + +/* + * ShowTransactionStateRec + * Recursive subroutine for ShowTransactionState + */ +static void +ShowTransactionStateRec(TransactionState s) +{ + if (s->parent) + ShowTransactionStateRec(s->parent); + + /* use ereport to suppress computation if msg will not be printed */ + ereport(DEBUG2, + (errmsg_internal("blockState: %13s; state: %7s, xid/cid: %u/%02u, nestlvl: %d, children: %s", + BlockStateAsString(s->blockState), + TransStateAsString(s->state), + (unsigned int) s->transactionIdData, + (unsigned int) s->commandId, + s->nestingLevel, + nodeToString(s->childXids)))); +} + +/* + * BlockStateAsString + * Debug support + */ +static const char * +BlockStateAsString(TBlockState blockState) +{ + switch (blockState) { + case TBLOCK_DEFAULT: + return "DEFAULT"; + case TBLOCK_STARTED: + return "STARTED"; + case TBLOCK_BEGIN: + return "BEGIN"; + case TBLOCK_INPROGRESS: + return "INPROGRESS"; + case TBLOCK_END: + return "END"; + case TBLOCK_ABORT: + return "ABORT"; + case TBLOCK_ENDABORT: + return "ENDABORT"; + case TBLOCK_SUBBEGIN: + return "SUB BEGIN"; + case TBLOCK_SUBBEGINABORT: + return "SUB BEGIN AB"; + case TBLOCK_SUBINPROGRESS: + return "SUB INPROGRS"; + case TBLOCK_SUBEND: + return "SUB END"; + case TBLOCK_SUBABORT: + return "SUB ABORT"; + case TBLOCK_SUBENDABORT_OK: + return "SUB ENDAB OK"; + case TBLOCK_SUBENDABORT_ERROR: + return "SUB ENDAB ERR"; + } + return "UNRECOGNIZED"; +} + +/* + * TransStateAsString + * Debug support + */ +static const char * +TransStateAsString(TransState state) +{ + switch (state) { + case TRANS_DEFAULT: + return "DEFAULT"; + case TRANS_START: + return "START"; + case TRANS_COMMIT: + return "COMMIT"; + case TRANS_ABORT: + return "ABORT"; + case TRANS_INPROGRESS: + return "INPROGR"; + } + return "UNRECOGNIZED"; +} + +/* + * xactGetCommittedChildren + * + * Gets the list of committed children of the current transaction. The return + * value is the number of child transactions. *children is set to point to a + * palloc'd array of TransactionIds. If there are no subxacts, *children is + * set to NULL. + * + * If metoo is true, include the current TransactionId. + */ +int +xactGetCommittedChildren(TransactionId **ptr, bool metoo) +{ + TransactionState s = CurrentTransactionState; + int nchildren; + TransactionId *children; + ListCell *p; + + nchildren = list_length(s->childXids); + if (metoo) + nchildren++; + if (nchildren == 0) + { + *ptr = NULL; + return 0; + } + + children = (TransactionId *) palloc(nchildren * sizeof(TransactionId)); + *ptr = children; + + foreach(p, s->childXids) + { + TransactionId child = lfirst_int(p); + *children++ = (TransactionId)child; + } + if (metoo) + *children = s->transactionIdData; + + return nchildren; +} /* * XLOG support routines @@ -1809,13 +2855,14 @@ xact_redo(XLogRecPtr lsn, XLogRecord *record) if (info == XLOG_XACT_COMMIT) { xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record); - int nfiles; int i; TransactionIdCommit(record->xl_xid); + /* Mark committed subtransactions as committed */ + TransactionIdCommitTree(xlrec->nsubxacts, + (TransactionId *) &(xlrec->xnodes[xlrec->nrels])); /* Make sure files supposed to be dropped are dropped */ - nfiles = (record->xl_len - MinSizeOfXactCommit) / sizeof(RelFileNode); - for (i = 0; i < nfiles; i++) + for (i = 0; i < xlrec->nrels; i++) { XLogCloseRelation(xlrec->xnodes[i]); smgrdounlink(smgropen(xlrec->xnodes[i]), false, true); @@ -1824,13 +2871,14 @@ xact_redo(XLogRecPtr lsn, XLogRecord *record) else if (info == XLOG_XACT_ABORT) { xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record); - int nfiles; int i; TransactionIdAbort(record->xl_xid); + /* mark subtransactions as aborted */ + TransactionIdAbortTree(xlrec->nsubxacts, + (TransactionId *) &(xlrec->xnodes[xlrec->nrels])); /* Make sure files supposed to be dropped are dropped */ - nfiles = (record->xl_len - MinSizeOfXactAbort) / sizeof(RelFileNode); - for (i = 0; i < nfiles; i++) + for (i = 0; i < xlrec->nrels; i++) { XLogCloseRelation(xlrec->xnodes[i]); smgrdounlink(smgropen(xlrec->xnodes[i]), false, true); @@ -1855,6 +2903,7 @@ void xact_desc(char *buf, uint8 xl_info, char *rec) { uint8 info = xl_info & ~XLR_INFO_MASK; + int i; if (info == XLOG_XACT_COMMIT) { @@ -1864,7 +2913,25 @@ xact_desc(char *buf, uint8 xl_info, char *rec) sprintf(buf + strlen(buf), "commit: %04u-%02u-%02u %02u:%02u:%02u", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - /* XXX can't show RelFileNodes for lack of access to record length */ + if (xlrec->nrels > 0) + { + sprintf(buf + strlen(buf), "; rels:"); + for (i = 0; i < xlrec->nrels; i++) + { + RelFileNode rnode = xlrec->xnodes[i]; + sprintf(buf + strlen(buf), " %u/%u/%u", + rnode.spcNode, rnode.dbNode, rnode.relNode); + } + } + if (xlrec->nsubxacts > 0) + { + TransactionId *xacts = (TransactionId *) + &xlrec->xnodes[xlrec->nrels]; + + sprintf(buf + strlen(buf), "; subxacts:"); + for (i = 0; i < xlrec->nsubxacts; i++) + sprintf(buf + strlen(buf), " %u", xacts[i]); + } } else if (info == XLOG_XACT_ABORT) { @@ -1874,7 +2941,25 @@ xact_desc(char *buf, uint8 xl_info, char *rec) sprintf(buf + strlen(buf), "abort: %04u-%02u-%02u %02u:%02u:%02u", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - /* XXX can't show RelFileNodes for lack of access to record length */ + if (xlrec->nrels > 0) + { + sprintf(buf + strlen(buf), "; rels:"); + for (i = 0; i < xlrec->nrels; i++) + { + RelFileNode rnode = xlrec->xnodes[i]; + sprintf(buf + strlen(buf), " %u/%u/%u", + rnode.spcNode, rnode.dbNode, rnode.relNode); + } + } + if (xlrec->nsubxacts > 0) + { + TransactionId *xacts = (TransactionId *) + &xlrec->xnodes[xlrec->nrels]; + + sprintf(buf + strlen(buf), "; subxacts:"); + for (i = 0; i < xlrec->nsubxacts; i++) + sprintf(buf + strlen(buf), " %u", xacts[i]); + } } else strcat(buf, "UNKNOWN"); |