/* transaction block states */
TBLOCK_BEGIN, /* starting transaction block */
TBLOCK_INPROGRESS, /* live transaction */
TBLOCK_IMPLICIT_INPROGRESS, /* live transaction after implicit BEGIN */
TBLOCK_PARALLEL_INPROGRESS, /* live transaction inside parallel worker */
TBLOCK_END, /* COMMIT received */
TBLOCK_ABORT, /* failed xact, awaiting ROLLBACK */
TBLOCK_ABORT_END, /* failed xact, ROLLBACK received */
TBLOCK_ABORT_PENDING, /* live xact, ROLLBACK received */
TBLOCK_PREPARE, /* live xact, PREPARE received */
/* subtransaction states */
TBLOCK_SUBBEGIN, /* starting a subtransaction */
TBLOCK_SUBINPROGRESS, /* live subtransaction */
TBLOCK_SUBRELEASE, /* RELEASE received */
TBLOCK_SUBCOMMIT, /* COMMIT received while TBLOCK_SUBINPROGRESS */
TBLOCK_SUBABORT, /* failed subxact, awaiting ROLLBACK */
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 */
} TBlockState;
/* *transactionstatestructure * *Note:parallelModeLevelcountsthenumberofunmatchedEnterParallelMode *callsdoneatthistransactionlevel.parallelChildXactistrueifany *uppertransactionlevelhasnonzeroparallelModeLevel.
*/ typedefstruct TransactionStateData
{
FullTransactionId fullTransactionId; /* my FullTransactionId */
SubTransactionId subTransactionId; /* my subxact ID */ char *name; /* savepoint name, if any */ int savepointLevel; /* savepoint level */
TransState state; /* low-level state */
TBlockState blockState; /* high-level state */ int nestingLevel; /* transaction nesting depth */ int gucNestLevel; /* GUC context nesting depth */
MemoryContext curTransactionContext; /* my xact-lifetime context */
ResourceOwner curTransactionOwner; /* my query resources */
MemoryContext priorContext; /* CurrentMemoryContext before xact started */
TransactionId *childXids; /* subcommitted child XIDs, in XID order */ int nChildXids; /* # of subcommitted child XIDs */ int maxChildXids; /* allocated size of childXids[] */
Oid prevUser; /* previous CurrentUserId setting */ int prevSecContext; /* previous SecurityRestrictionContext */ bool prevXactReadOnly; /* entry-time xact r/o state */ bool startedInRecovery; /* did we start in recovery? */ bool didLogXid; /* has xid been included in WAL record? */ int parallelModeLevel; /* Enter/ExitParallelMode counter */ bool parallelChildXact; /* is any parent transaction parallel? */ bool chain; /* start a new block after this one */ bool topXidLogged; /* for a subxact: is top-level XID logged? */ struct TransactionStateData *parent; /* back link to parent */
} TransactionStateData;
/* The size of SerializedTransactionState, not including the final array. */ #define SerializedTransactionStateHeaderSize \
offsetof(SerializedTransactionState, parallelCurrentXids)
for (s = CurrentTransactionState; s != NULL; s = s->parent)
{ if (s->state == TRANS_ABORT) continue; if (s->subTransactionId == subxid) returntrue;
} returnfalse;
}
/* *GetCurrentCommandId * *"used"mustbetrueifthecallerintendstousethecommandIDtomark *inserted/updated/deletedtuples.falsemeanstheIDisbeingfetched *forread-onlypurposes(ie,asasnapshotvaliditycutoff).See *CommandCounterIncrement()fordiscussion.
*/
CommandId
GetCurrentCommandId(bool used)
{ /* this is global to a transaction, not subtransaction-local */ if (used)
{ /* *ForbidsettingcurrentCommandIdUsedinaparallelworker,because *wehavenoprovisionforcommunicatingthisbacktotheleader.We *couldrelaxthisrestrictionwhencurrentCommandIdUsedwasalready *trueatthestartoftheparalleloperation.
*/ if (IsParallelWorker())
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot modify data in a parallel worker")));
/* *WewillreturntruefortheXidofthecurrentsubtransaction,anyof *itssubcommittedchildren,anyofitsparents,oranyoftheir *previouslysubcommittedchildren.However,atransactionbeingaborted *isnolonger"current",eventhoughitmaystillhaveanentryonthe *statestack.
*/ for (s = CurrentTransactionState; s != NULL; s = s->parent)
{ int low,
high;
if (s->state == TRANS_ABORT) continue; if (!FullTransactionIdIsValid(s->fullTransactionId)) continue; /* it can't have any child XIDs either */ if (TransactionIdEquals(xid, XidFromFullTransactionId(s->fullTransactionId))) returntrue; /* As the childXids array is ordered, we can use binary search */
low = 0;
high = s->nChildXids - 1; while (low <= high)
{ int middle;
TransactionId probe;
/* *CommandCounterIncrement
*/ void
CommandCounterIncrement(void)
{ /* *Ifthecurrentvalueofthecommandcounterhasn'tbeen"used"tomark *tuples,weneednotincrementit,sincethere'snoneedtodistinguish *aread-onlycommandfromothers.Thishelpspostponecommandcounter *overflow,andkeepsno-opCommandCounterIncrementoperationscheap.
*/ if (currentCommandIdUsed)
{ /* *Workerssynchronizetransactionstateatthebeginningofeach *paralleloperation,sowecan'taccountfornewcommandsafterthat *point.
*/ if (IsInParallelMode() || IsParallelWorker())
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot start commands during a parallel operation")));
currentCommandId += 1; if (currentCommandId == InvalidCommandId)
{
currentCommandId -= 1;
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("cannot have more than 2^32-2 commands in a transaction")));
}
currentCommandIdUsed = false;
/* Propagate new command ID into static snapshots */
SnapshotSetCommandId(currentCommandId);
/* *Logpendinginvalidationsforlogicaldecodingofin-progress *transactions.NormallyforDDLs,welogthisateachcommandend, *however,forcertaincaseswherewedirectlyupdatethesystemtable *withoutatransactionblock,theinvalidationsarenotloggedtillthis *time.
*/ if (XLogLogicalInfoActive())
LogLogicalInvalidations();
/* Get data needed for commit record */
nrels = smgrGetPendingDeletes(true, &rels);
nchildren = xactGetCommittedChildren(&children);
ndroppedstats = pgstat_get_transactional_drops(true, &droppedstats); if (XLogStandbyInfoActive())
nmsgs = xactGetCommittedInvalidationMessages(&invalMessages,
&RelcacheInitFileInval);
wrote_xlog = (XactLastRecEnd != 0);
/* *Ifwehaven'tbeenassignedanXIDyet,weneithercan,nordowewant *towriteaCOMMITrecord.
*/ if (!markXidCommitted)
{ /* *WeexpectthateveryRelationDropStorageisfollowedbyacatalog *update,andhenceXIDassignment,soweshouldn'tgetherewithany *pendingdeletes.Sameistruefordroppingstats. * *UsearealtestnotjustanAsserttocheckthis,sinceit'sabit *fragile.
*/ if (nrels != 0 || ndroppedstats != 0)
elog(ERROR, "cannot commit a transaction that deleted files but has no xid");
/* Can't have child XIDs either; AssignTransactionId enforces this */
Assert(nchildren == 0);
/* remember end of last commit record */
XactLastCommitEnd = XactLastRecEnd;
/* Reset XactLastRecEnd until the next transaction writes something */
XactLastRecEnd = 0;
cleanup: /* Clean up local data */ if (rels)
pfree(rels); if (ndroppedstats)
pfree(droppedstats);
/* Allocate or enlarge the parent array if necessary */ if (s->parent->maxChildXids < new_nChildXids)
{ int new_maxChildXids;
TransactionId *new_childXids;
/* Release child's array to avoid leakage */ if (s->childXids != NULL)
pfree(s->childXids); /* We must reset these to avoid double-free if fail later in commit */
s->childXids = NULL;
s->nChildXids = 0;
s->maxChildXids = 0;
}
/* *Checkthatwehaven'tabortedhalfwaythroughRecordTransactionCommit.
*/ if (TransactionIdDidCommit(xid))
elog(PANIC, "cannot abort transaction %u, it was already committed",
xid);
/* Fetch the data we need for the abort record */
nrels = smgrGetPendingDeletes(false, &rels);
nchildren = xactGetCommittedChildren(&children);
ndroppedstats = pgstat_get_transactional_drops(false, &droppedstats);
/* XXX do we really need a critical section here? */
START_CRIT_SECTION();
/* Write the ABORT record */ if (isSubXact)
xact_time = GetCurrentTimestamp(); else
{
xact_time = GetCurrentTransactionStopTimestamp();
}
/* *Ifthisxacthasstartedanyunfinishedparalleloperation,cleanup *itsworkers,warningaboutleakedresources.(Butwedon'tactually *resetparallelModeLeveltillenteringTRANS_COMMIT,abitbelow.This *keepsparallelmoderestrictionsactiveaslongaspossibleina *parallelworker.)
*/
AtEOXact_Parallel(true); if (is_parallel_worker)
{ if (s->parallelModeLevel != 1)
elog(WARNING, "parallelModeLevel is %d not 1 at end of parallel worker transaction",
s->parallelModeLevel);
} else
{ if (s->parallelModeLevel != 0)
elog(WARNING, "parallelModeLevel is %d not 0 at end of transaction",
s->parallelModeLevel);
}
/* Shut down the deferred-trigger manager */
AfterTriggerEndXact(true);
/* *Don'tallowPREPARETRANSACTIONifwe'veaccessedatemporarytablein *thistransaction.Havingthepreparedxactholdlocksonanother *backend'stemptableseemsabadidea---forinstanceitwouldprevent *thebackendfromexiting.Thereareotherproblemstoo,suchashowto *cleanupthesourcebackend'slocalbuffersandONCOMMITstateifthe *preparedxactincludesaDROPofatemptable. * *Otherobjectstypes,likefunctions,operatorsorextensions,sharethe *samerestrictionastheyshouldnotbecreated,lockedordroppedas *thiscanmessupwiththissessionorevenafollow-upsessiontrying *tousethesametemporarynamespace. * *WemustcheckthisafterexecutinganyONCOMMITactions,becausethey *mightstillaccessatemprelation. * *XXXInprinciplethiscouldberelaxedtoallowsomeusefulspecial *cases,suchasatemptablecreatedanddroppedallwithinthe *transaction.Thatseemstorequiremuchmorebookkeepingthough.
*/ if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot PREPARE a transaction that has operated on temporary objects")));
/* *Likewise,don'tallowPREPAREafterpg_export_snapshot.Thiscouldbe *supportedifweaddedcleanuplogictotwophase.c,butfornowit *doesn'tseemworththetrouble.
*/ if (XactHasExportedSnapshots())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot PREPARE a transaction that has exported snapshots")));
/* Prevent cancel/die interrupt while cleaning up */
HOLD_INTERRUPTS();
/* PREPARE acts the same as COMMIT as far as GUC is concerned */
AtEOXact_GUC(true, 1);
AtEOXact_SPI(true);
AtEOXact_Enum();
AtEOXact_on_commit_actions(true);
AtEOXact_Namespace(true, false);
AtEOXact_SMgr();
AtEOXact_Files(true);
AtEOXact_ComboCid();
AtEOXact_HashTables(true); /* don't call AtEOXact_PgStat here; we fixed pgstat state above */
AtEOXact_Snapshot(true, true); /* we treat PREPARE as ROLLBACK so far as waking workers goes */
AtEOXact_ApplyLauncher(false);
AtEOXact_LogicalRepWorkers(false);
pgstat_report_xact_timestamp(0);
/* *Wearesomewhereinatransactionblockorsubtransactionand *abouttostartanewcommand.Fornowwedonothing,but *somedaywemaydocommand-localresourceinitialization.(Note *thatanyneededCommandCounterIncrementwasdonebythe *previousCommitTransactionCommand.)
*/ case TBLOCK_INPROGRESS: case TBLOCK_IMPLICIT_INPROGRESS: case TBLOCK_SUBINPROGRESS: break;
/* *Hereweareinafailedtransactionblock(oneofthecommands *causedanabort)sowedonothingbutremainintheabort *state.EventuallywewillgetaROLLBACKcommandwhichwill *getusoutofthisstate.(Itisuptoothercodetoensure *thatnocommandsotherthanROLLBACKwillbeprocessedinthese *states.)
*/ case TBLOCK_ABORT: case TBLOCK_SUBABORT: break;
/* These cases are invalid. */ case TBLOCK_STARTED: case TBLOCK_BEGIN: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE:
elog(ERROR, "StartTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState)); break;
}
/* *Thisisthecasewhenwehavefinishedexecutingacommand *someplacewithinatransactionblock.Weincrementthecommand *counterandreturn.
*/ case TBLOCK_INPROGRESS: case TBLOCK_IMPLICIT_INPROGRESS: case TBLOCK_SUBINPROGRESS:
CommandCounterIncrement(); break;
/* *Hereweareinthemiddleofatransactionblockbutoneofthe *commandscausedanabortsowedonothingbutremaininthe *abortstate.EventuallywewillgetaROLLBACKcommand.
*/ case TBLOCK_ABORT: case TBLOCK_SUBABORT: break;
/* *TheuserissuedaRELEASEcommand,soweendthecurrent *subtransactionandreturntotheparenttransaction.Theparent *mightbeendedtoo,sorepeattillwefindanINPROGRESS *transactionorsubtransaction.
*/ case TBLOCK_SUBRELEASE: do
{
CommitSubTransaction();
s = CurrentTransactionState; /* changed by pop */
} while (s->blockState == TBLOCK_SUBRELEASE);
/* *Asabove,butit'snotdeadyet,soabortfirst.
*/ case TBLOCK_SUBABORT_PENDING:
AbortSubTransaction();
CleanupSubTransaction(); returnfalse;
/* *ThecurrentsubtransactionisthetargetofaROLLBACKTO *command.Abortandpopit,thenstartanewsubtransaction *withthesamename.
*/ case TBLOCK_SUBRESTART:
{ char *name; int savepointLevel;
/* save name and keep Cleanup from freeing it */
name = s->name;
s->name = NULL;
savepointLevel = s->savepointLevel;
AbortSubTransaction();
CleanupSubTransaction();
DefineSavepoint(NULL);
s = CurrentTransactionState; /* changed by push */
s->name = name;
s->savepointLevel = savepointLevel;
/* This is the same as TBLOCK_SUBBEGIN case */
Assert(s->blockState == TBLOCK_SUBBEGIN);
StartSubTransaction();
s->blockState = TBLOCK_SUBINPROGRESS;
} break;
/* *Sameasabove,butthesubtransactionhadalreadyfailed,sowe *don'tneedAbortSubTransaction.
*/ case TBLOCK_SUBABORT_RESTART:
{ char *name; int savepointLevel;
/* save name and keep Cleanup from freeing it */
name = s->name;
s->name = NULL;
savepointLevel = s->savepointLevel;
CleanupSubTransaction();
DefineSavepoint(NULL);
s = CurrentTransactionState; /* changed by push */
s->name = name;
s->savepointLevel = savepointLevel;
/* This is the same as TBLOCK_SUBBEGIN case */
Assert(s->blockState == TBLOCK_SUBBEGIN);
StartSubTransaction();
s->blockState = TBLOCK_SUBINPROGRESS;
} break;
}
/* Done, no more iterations required */ returntrue;
}
switch (s->blockState)
{ case TBLOCK_DEFAULT: if (s->state == TRANS_DEFAULT)
{ /* we are idle, so nothing to do */
} else
{ /* *Wecangethereafteranerrorduringtransactionstart *(statewillbeTRANS_START).Needtocleanupthe *incompletelystartedtransaction.First,adjustthe *low-levelstatetosuppresswarningmessagefrom *AbortTransaction.
*/ if (s->state == TRANS_START)
s->state = TRANS_INPROGRESS;
AbortTransaction();
CleanupTransaction();
} break;
/* *Ifwearen'tinatransactionblock,wejustdothebasicabort *&cleanuptransaction.Forthispurpose,wetreatanimplicit *transactionblockasifitwereasimplestatement.
*/ case TBLOCK_STARTED: case TBLOCK_IMPLICIT_INPROGRESS:
AbortTransaction();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT; break;
/* *Here,wearealreadyinanabortedtransactionstateandare *waitingforaROLLBACK,butforsomereasonwefailedagain!So *wejustremainintheabortstate.
*/ case TBLOCK_ABORT: case TBLOCK_SUBABORT: break;
/* *Ifwefailedwhiletryingtocreateasubtransaction,cleanup *thebrokensubtransactionandaborttheparent.Thesame *appliesifwegetafailurewhileendingasubtransaction.As *weneedtoaborttheparent,returnfalsetorequestthecaller *todothenextiteration.
*/ case TBLOCK_SUBBEGIN: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART:
AbortSubTransaction();
CleanupSubTransaction(); returnfalse;
/* *Sameasabove,excepttheAbort()wasalreadydone.
*/ case TBLOCK_SUBABORT_END: case TBLOCK_SUBABORT_RESTART:
CleanupSubTransaction(); returnfalse;
}
/* Done, no more iterations required */ returntrue;
}
/* *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)));
/* *insideafunctioncall?
*/ if (!isTopLevel)
ereport(ERROR,
(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(FATAL, "cannot prevent transaction chain");
/* All okay. Set the flag to make sure the right thing happens later. */
MyXactFlags |= XACT_FLAGS_NEEDIMMEDIATECOMMIT;
}
/* *subtransaction?
*/ if (IsSubTransaction()) return;
/* *insideafunctioncall?
*/ if (!isTopLevel) return;
ereport(throwError ? ERROR : WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */
errmsg("%s can only be used in transaction blocks",
stmtType)));
}
for (item = Xact_callbacks; item; item = next)
{ /* allow callbacks to unregister themselves when called */
next = item->next;
item->callback(event, item->arg);
}
}
/* *Alreadyatransactionblockinprogress.
*/ case TBLOCK_INPROGRESS: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_SUBINPROGRESS: case TBLOCK_ABORT: case TBLOCK_SUBABORT:
ereport(WARNING,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("there is already a transaction in progress"))); break;
/* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE:
elog(FATAL, "BeginTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState)); break;
}
}
/* Set up to commit the current transaction */
result = EndTransactionBlock(false);
/* If successful, change outer tblock state to PREPARE */ if (result)
{
s = CurrentTransactionState;
while (s->parent != NULL)
s = s->parent;
if (s->blockState == TBLOCK_END)
{ /* Save GID where PrepareTransaction can find it again */
prepareGID = MemoryContextStrdup(TopTransactionContext, gid);
s->blockState = TBLOCK_PREPARE;
} else
{ /* *ignorecasewherewearenotinatransaction; *EndTransactionBlockalreadyissuedawarning.
*/
Assert(s->blockState == TBLOCK_STARTED ||
s->blockState == TBLOCK_IMPLICIT_INPROGRESS); /* Don't send back a PREPARE result tag... */
result = false;
}
}
switch (s->blockState)
{ /* *Weareinatransactionblock,sotellCommitTransactionCommand *toCOMMIT.
*/ case TBLOCK_INPROGRESS:
s->blockState = TBLOCK_END;
result = true; break;
/* *Weareinanimplicittransactionblock.IfANDCHAINwas *specified,error.Otherwisecommit,butissueawarning *becausetherewasnoexplicitBEGINbeforethis.
*/ case TBLOCK_IMPLICIT_INPROGRESS: if (chain)
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */
errmsg("%s can only be used in transaction blocks", "COMMIT AND CHAIN"))); else
ereport(WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
errmsg("there is no transaction in progress")));
s->blockState = TBLOCK_END;
result = true; break;
/* *Weareinafailedtransactionblock.Tell *CommitTransactionCommandit'stimetoexittheblock.
*/ case TBLOCK_ABORT:
s->blockState = TBLOCK_ABORT_END; break;
/* *Weareinalivesubtransactionblock.Setuptosubcommitall *opensubtransactionsandthencommitthemaintransaction.
*/ case TBLOCK_SUBINPROGRESS: while (s->parent != NULL)
{ if (s->blockState == TBLOCK_SUBINPROGRESS)
s->blockState = TBLOCK_SUBCOMMIT; else
elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
s = s->parent;
} if (s->blockState == TBLOCK_INPROGRESS)
s->blockState = TBLOCK_END; else
elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
result = true; break;
/* *Hereweareinsideanabortedsubtransaction.TreattheCOMMIT *asROLLBACK:setuptoaborteverythingandexitthemain *transaction.
*/ case TBLOCK_SUBABORT: while (s->parent != NULL)
{ if (s->blockState == TBLOCK_SUBINPROGRESS)
s->blockState = TBLOCK_SUBABORT_PENDING; elseif (s->blockState == TBLOCK_SUBABORT)
s->blockState = TBLOCK_SUBABORT_END; else
elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
s = s->parent;
} if (s->blockState == TBLOCK_INPROGRESS)
s->blockState = TBLOCK_ABORT_PENDING; elseif (s->blockState == TBLOCK_ABORT)
s->blockState = TBLOCK_ABORT_END; else
elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState)); break;
/* *TheuserissuedCOMMITwhennotinsideatransaction.For *COMMITwithoutCHAIN,issueaWARNING,stayingin *TBLOCK_STARTEDstate.Theupcomingcallto *CommitTransactionCommand()willthenclosethetransactionand *putusbackintothedefaultstate.ForCOMMITANDCHAIN, *error.
*/ case TBLOCK_STARTED: if (chain)
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */
errmsg("%s can only be used in transaction blocks", "COMMIT AND CHAIN"))); else
ereport(WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
errmsg("there is no transaction in progress")));
result = true; break;
/* *TheuserissuedaCOMMITthatsomehowraninsideaparallel *worker.Wecan'tcopewiththat.
*/ case TBLOCK_PARALLEL_INPROGRESS:
ereport(FATAL,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot commit during a parallel operation"))); break;
/* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE:
elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState)); break;
}
/* *Weareinsideasubtransaction.Markeverythinguptotop *levelasexitable.
*/ case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBABORT: while (s->parent != NULL)
{ if (s->blockState == TBLOCK_SUBINPROGRESS)
s->blockState = TBLOCK_SUBABORT_PENDING; elseif (s->blockState == TBLOCK_SUBABORT)
s->blockState = TBLOCK_SUBABORT_END; else
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
s = s->parent;
} if (s->blockState == TBLOCK_INPROGRESS)
s->blockState = TBLOCK_ABORT_PENDING; elseif (s->blockState == TBLOCK_ABORT)
s->blockState = TBLOCK_ABORT_END; else
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState)); break;
/* *TheuserissuedABORTwhennotinsideatransaction.For *ROLLBACKwithoutCHAIN,issueaWARNINGandgotoabortstate. *TheupcomingcalltoCommitTransactionCommand()willthenput *usbackintothedefaultstate.ForROLLBACKANDCHAIN,error. * *WedothesamethingwithABORTinsideanimplicittransaction, *althoughinthiscasewemightberollingbackactualdatabase *statechanges.(It'sdebatablewhetherweshouldissuea *WARNINGinthiscase,butwehavedonesohistorically.)
*/ case TBLOCK_STARTED: case TBLOCK_IMPLICIT_INPROGRESS: if (chain)
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */
errmsg("%s can only be used in transaction blocks", "ROLLBACK AND CHAIN"))); else
ereport(WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
errmsg("there is no transaction in progress")));
s->blockState = TBLOCK_ABORT_PENDING; break;
/* *TheuserissuedanABORTthatsomehowraninsideaparallel *worker.Wecan'tcopewiththat.
*/ case TBLOCK_PARALLEL_INPROGRESS:
ereport(FATAL,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot abort during a parallel operation"))); break;
/* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE:
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState)); break;
}
/* *Workerssynchronizetransactionstateatthebeginningofeachparallel *operation,sowecan'taccountfornewsubtransactionsafterthat *point.(Notethatthischeckwillcertainlyerroroutifs->blockState *isTBLOCK_PARALLEL_INPROGRESS,sowecantreatthatasaninvalidcase *below.)
*/ if (IsInParallelMode() || IsParallelWorker())
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot define savepoints during a parallel operation")));
switch (s->blockState)
{ case TBLOCK_INPROGRESS: case TBLOCK_SUBINPROGRESS: /* Normal subtransaction start */
PushTransaction();
s = CurrentTransactionState; /* changed by push */
/* *Wedisallowsavepointcommandsinimplicittransactionblocks. *Therewouldbenogreatdifficultyinallowingthemsofaras *thismoduleisconcerned,butasavepointseemsinconsistent *withexec_simple_query'sbehaviorofabandoningthewholequery *stringuponerror.Also,thepointofanimplicittransaction *block(asopposedtoaregularone)istoautomaticallyclose *afteranerror,soit'shardtoseehowasavepointwouldfit *intothat. * *Theerrormessagesforthisarephrasedasiftherewereno *activetransactionblockatall,whichishistoricalbut *perhapscouldbeimproved.
*/ case TBLOCK_IMPLICIT_INPROGRESS:
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */
errmsg("%s can only be used in transaction blocks", "SAVEPOINT"))); break;
/* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT: case TBLOCK_SUBABORT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE:
elog(FATAL, "DefineSavepoint: unexpected state %s",
BlockStateAsString(s->blockState)); break;
}
}
/* *Workerssynchronizetransactionstateatthebeginningofeachparallel *operation,sowecan'taccountfortransactionstatechangeafterthat *point.(Notethatthischeckwillcertainlyerroroutifs->blockState *isTBLOCK_PARALLEL_INPROGRESS,sowecantreatthatasaninvalidcase *below.)
*/ if (IsInParallelMode() || IsParallelWorker())
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot release savepoints during a parallel operation")));
switch (s->blockState)
{ /* *Wecan'treleaseasavepointifthereisnosavepointdefined.
*/ case TBLOCK_INPROGRESS:
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("savepoint \"%s\" does not exist", name))); break;
case TBLOCK_IMPLICIT_INPROGRESS: /* See comment about implicit transactions in DefineSavepoint */
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */
errmsg("%s can only be used in transaction blocks", "RELEASE SAVEPOINT"))); break;
/* *Weareinanon-abortedsubtransaction.Thisistheonlyvalid *case.
*/ case TBLOCK_SUBINPROGRESS: break;
/* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT: case TBLOCK_SUBABORT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE:
elog(FATAL, "ReleaseSavepoint: unexpected state %s",
BlockStateAsString(s->blockState)); break;
}
for (target = s; PointerIsValid(target); target = target->parent)
{ if (PointerIsValid(target->name) && strcmp(target->name, name) == 0) break;
}
if (!PointerIsValid(target))
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("savepoint \"%s\" does not exist", name)));
/* disallow crossing savepoint level boundaries */ if (target->savepointLevel != s->savepointLevel)
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("savepoint \"%s\" does not exist within current savepoint level", name)));
/* *Workerssynchronizetransactionstateatthebeginningofeachparallel *operation,sowecan'taccountfortransactionstatechangeafterthat *point.(Notethatthischeckwillcertainlyerroroutifs->blockState *isTBLOCK_PARALLEL_INPROGRESS,sowecantreatthatasaninvalidcase *below.)
*/ if (IsInParallelMode() || IsParallelWorker())
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot rollback to savepoints during a parallel operation")));
switch (s->blockState)
{ /* *Wecan'trollbacktoasavepointifthereisnosavepoint *defined.
*/ case TBLOCK_INPROGRESS: case TBLOCK_ABORT:
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("savepoint \"%s\" does not exist", name))); break;
case TBLOCK_IMPLICIT_INPROGRESS: /* See comment about implicit transactions in DefineSavepoint */
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */
errmsg("%s can only be used in transaction blocks", "ROLLBACK TO SAVEPOINT"))); break;
/* *Thereisatleastonesavepoint,soproceed.
*/ case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBABORT: break;
/* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE:
elog(FATAL, "RollbackToSavepoint: unexpected state %s",
BlockStateAsString(s->blockState)); break;
}
for (target = s; PointerIsValid(target); target = target->parent)
{ if (PointerIsValid(target->name) && strcmp(target->name, name) == 0) break;
}
if (!PointerIsValid(target))
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("savepoint \"%s\" does not exist", name)));
/* disallow crossing savepoint level boundaries */ if (target->savepointLevel != s->savepointLevel)
ereport(ERROR,
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
errmsg("savepoint \"%s\" does not exist within current savepoint level", name)));
switch (s->blockState)
{ case TBLOCK_STARTED: case TBLOCK_INPROGRESS: case TBLOCK_IMPLICIT_INPROGRESS: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_END: case TBLOCK_PREPARE: case TBLOCK_SUBINPROGRESS: /* Normal subtransaction start */
PushTransaction();
s = CurrentTransactionState; /* changed by push */
/* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT: case TBLOCK_SUBABORT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART:
elog(FATAL, "BeginInternalSubTransaction: unexpected state %s",
BlockStateAsString(s->blockState)); break;
}
switch (s->blockState)
{ /* Must be in a subtransaction */ case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBABORT: break;
/* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: case TBLOCK_IMPLICIT_INPROGRESS: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_SUBBEGIN: case TBLOCK_INPROGRESS: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE:
elog(FATAL, "RollbackAndReleaseCurrentSubTransaction: unexpected state %s",
BlockStateAsString(s->blockState)); break;
}
/* *Abortthecurrentsubtransaction,ifneeded.
*/ if (s->blockState == TBLOCK_SUBINPROGRESS)
AbortSubTransaction();
/* And clean it up, too */
CleanupSubTransaction();
s = CurrentTransactionState; /* changed by pop */
Assert(s->blockState == TBLOCK_SUBINPROGRESS ||
s->blockState == TBLOCK_INPROGRESS ||
s->blockState == TBLOCK_IMPLICIT_INPROGRESS ||
s->blockState == TBLOCK_PARALLEL_INPROGRESS ||
s->blockState == TBLOCK_STARTED);
}
/* Ensure we're not running in a doomed memory context */
AtAbort_Memory();
/* *Getoutofanytransactionornestedtransaction
*/ do
{ switch (s->blockState)
{ case TBLOCK_DEFAULT: if (s->state == TRANS_DEFAULT)
{ /* Not in a transaction, do nothing */
} else
{ /* *Wecangethereafteranerrorduringtransactionstart *(statewillbeTRANS_START).Needtocleanupthe *incompletelystartedtransaction.First,adjustthe *low-levelstatetosuppresswarningmessagefrom *AbortTransaction.
*/ if (s->state == TRANS_START)
s->state = TRANS_INPROGRESS;
AbortTransaction();
CleanupTransaction();
} break; case TBLOCK_STARTED: case TBLOCK_BEGIN: case TBLOCK_INPROGRESS: case TBLOCK_IMPLICIT_INPROGRESS: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_END: case TBLOCK_ABORT_PENDING: case TBLOCK_PREPARE: /* In a transaction, so clean up */
AbortTransaction();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT; break; case TBLOCK_ABORT: case TBLOCK_ABORT_END:
/* *Inasubtransaction,socleanitupandabortparenttoo
*/ case TBLOCK_SUBBEGIN: case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART:
AbortSubTransaction();
CleanupSubTransaction();
s = CurrentTransactionState; /* changed by pop */ break;
case TBLOCK_SUBABORT: case TBLOCK_SUBABORT_END: case TBLOCK_SUBABORT_RESTART: /* As above, but AbortSubTransaction already done */ if (s->curTransactionOwner)
{ /* As in TBLOCK_ABORT, might have a live portal to zap */
AtSubAbort_Portals(s->subTransactionId,
s->parent->subTransactionId,
s->curTransactionOwner,
s->parent->curTransactionOwner);
}
CleanupSubTransaction();
s = CurrentTransactionState; /* changed by pop */ break;
}
} while (s->blockState != TBLOCK_DEFAULT);
/* Should be out of all subxacts now */
Assert(s->parent == NULL);
/* *TransactionBlockStatusCode-returnstatuscodetosendinReadyForQuery
*/ char
TransactionBlockStatusCode(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{ case TBLOCK_DEFAULT: case TBLOCK_STARTED: return'I'; /* idle --- not in transaction */ case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: case TBLOCK_INPROGRESS: case TBLOCK_IMPLICIT_INPROGRESS: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_SUBINPROGRESS: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_PREPARE: return'T'; /* in transaction */ case TBLOCK_ABORT: case TBLOCK_SUBABORT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: return'E'; /* in failed transaction */
}
/* should never get here */
elog(FATAL, "invalid transaction block state: %s",
BlockStateAsString(s->blockState)); return0; /* keep compiler quiet */
}
/* *IsSubTransaction
*/ bool
IsSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
/* *Ifthissubxacthasstartedanyunfinishedparalleloperation,cleanup *itsworkersandexitparallelmode.Warnaboutleakedresources.
*/
AtEOSubXact_Parallel(true, s->subTransactionId); if (s->parallelModeLevel != 0)
{
elog(WARNING, "parallelModeLevel is %d not 0 at end of subtransaction",
s->parallelModeLevel);
s->parallelModeLevel = 0;
}
/* Do the actual "commit", such as it is */
s->state = TRANS_COMMIT;
/* Must CCI to ensure commands of subtransaction are seen as done */
CommandCounterIncrement();
/* *WekeepsubtransactionstatenodesinTopTransactionContext.
*/
s = (TransactionState)
MemoryContextAllocZero(TopTransactionContext, sizeof(TransactionStateData));
/* *AssignasubtransactionID,watchingoutforcounterwraparound.
*/
currentSubTransactionId += 1; if (currentSubTransactionId == InvalidSubTransactionId)
{
currentSubTransactionId -= 1;
pfree(s);
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("cannot have more than 2^32-1 subtransactions in a transaction")));
}
if (s->state != TRANS_DEFAULT)
elog(WARNING, "PopTransaction while in %s state",
TransStateAsString(s->state));
if (s->parent == NULL)
elog(FATAL, "PopTransaction with no parent");
CurrentTransactionState = s->parent;
/* Let's just make sure CurTransactionContext is good */
CurTransactionContext = s->parent->curTransactionContext;
MemoryContextSwitchTo(CurTransactionContext);
for (s = CurrentTransactionState; s != NULL; s = s->parent)
{ if (FullTransactionIdIsValid(s->fullTransactionId))
nxids = add_size(nxids, 1);
nxids = add_size(nxids, s->nChildXids);
}
/* *OK,weneedtogenerateasortedlistofXIDsthatourworkersshould *viewascurrent.First,figureouthowmanythereare.
*/ for (s = CurrentTransactionState; s != NULL; s = s->parent)
{ if (FullTransactionIdIsValid(s->fullTransactionId))
nxids = add_size(nxids, 1);
nxids = add_size(nxids, s->nChildXids);
}
Assert(SerializedTransactionStateHeaderSize + nxids * sizeof(TransactionId)
<= maxsize);
/* Copy them to our scratch space. */
workspace = palloc(nxids * sizeof(TransactionId)); for (s = CurrentTransactionState; s != NULL; s = s->parent)
{ if (FullTransactionIdIsValid(s->fullTransactionId))
workspace[i++] = XidFromFullTransactionId(s->fullTransactionId); if (s->nChildXids > 0)
memcpy(&workspace[i], s->childXids,
s->nChildXids * sizeof(TransactionId));
i += s->nChildXids;
}
Assert(i == nxids);
/* *ShowTransactionState *Debugsupport
*/ staticvoid
ShowTransactionState(constchar *str)
{ /* skip work if message will definitely not be printed */ if (message_level_is_interesting(DEBUG5))
ShowTransactionStateRec(str, CurrentTransactionState);
}
/* *BlockStateAsString *Debugsupport
*/ staticconstchar *
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_IMPLICIT_INPROGRESS: return"IMPLICIT_INPROGRESS"; case TBLOCK_PARALLEL_INPROGRESS: return"PARALLEL_INPROGRESS"; case TBLOCK_END: return"END"; case TBLOCK_ABORT: return"ABORT"; case TBLOCK_ABORT_END: return"ABORT_END"; case TBLOCK_ABORT_PENDING: return"ABORT_PENDING"; case TBLOCK_PREPARE: return"PREPARE"; case TBLOCK_SUBBEGIN: return"SUBBEGIN"; case TBLOCK_SUBINPROGRESS: return"SUBINPROGRESS"; case TBLOCK_SUBRELEASE: return"SUBRELEASE"; case TBLOCK_SUBCOMMIT: return"SUBCOMMIT"; case TBLOCK_SUBABORT: return"SUBABORT"; case TBLOCK_SUBABORT_END: return"SUBABORT_END"; case TBLOCK_SUBABORT_PENDING: return"SUBABORT_PENDING"; case TBLOCK_SUBRESTART: return"SUBRESTART"; case TBLOCK_SUBABORT_RESTART: return"SUBABORT_RESTART";
} return"UNRECOGNIZED";
}
/* *TransStateAsString *Debugsupport
*/ staticconstchar *
TransStateAsString(TransState state)
{ switch (state)
{ case TRANS_DEFAULT: return"DEFAULT"; case TRANS_START: return"START"; case TRANS_INPROGRESS: return"INPROGRESS"; case TRANS_COMMIT: return"COMMIT"; case TRANS_ABORT: return"ABORT"; case TRANS_PREPARE: return"PREPARE";
} return"UNRECOGNIZED";
}
/* *xactGetCommittedChildren * *Getsthelistofcommittedchildrenofthecurrenttransaction.Thereturn *valueisthenumberofchildtransactions.*ptrissettopointtoan *arrayofTransactionIds.ThearrayisallocatedinTopTransactionContext; *thecallershould*not*pfree()it(thisisachangefrompre-8.4code!). *Iftherearenosubxacts,*ptrissettoNULL.
*/ int
xactGetCommittedChildren(TransactionId **ptr)
{
TransactionState s = CurrentTransactionState;
/*
* Log the commit record for a plain or twophase transaction commit.
*
* A 2pc commit will be emitted when twophase_xid is valid, a plain one
* otherwise.
*/
XLogRecPtr
XactLogCommitRecord(TimestampTz commit_time,
int nsubxacts, TransactionId *subxacts,
int nrels, RelFileLocator *rels,
int ndroppedstats, xl_xact_stats_item *droppedstats,
int nmsgs, SharedInvalidationMessage *msgs,
bool relcacheInval,
int xactflags, TransactionId twophase_xid,
const char *twophase_gid)
{
xl_xact_commit xlrec;
xl_xact_xinfo xl_xinfo;
xl_xact_dbinfo xl_dbinfo;
xl_xact_subxacts xl_subxacts;
xl_xact_relfilelocators xl_relfilelocators;
xl_xact_stats_items xl_dropped_stats;
xl_xact_invals xl_invals;
xl_xact_twophase xl_twophase;
xl_xact_origin xl_origin;
uint8 info;
Assert(CritSectionCount > 0);
xl_xinfo.xinfo = 0;
/* decide between a plain and 2pc commit */
if (!TransactionIdIsValid(twophase_xid))
info = XLOG_XACT_COMMIT;
else
info = XLOG_XACT_COMMIT_PREPARED;
/* First figure out and collect all the information needed */
xlrec.xact_time = commit_time;
if (relcacheInval)
xl_xinfo.xinfo |= XACT_COMPLETION_UPDATE_RELCACHE_FILE;
if (forceSyncCommit)
xl_xinfo.xinfo |= XACT_COMPLETION_FORCE_SYNC_COMMIT;
if ((xactflags & XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK))
xl_xinfo.xinfo |= XACT_XINFO_HAS_AE_LOCKS;
/*
* Check if the caller would like to ask standbys for immediate feedback
* once this commit is applied.
*/
if (synchronous_commit >= SYNCHRONOUS_COMMIT_REMOTE_APPLY)
xl_xinfo.xinfo |= XACT_COMPLETION_APPLY_FEEDBACK;
/*
* Relcache invalidations requires information about the current database
* and so does logical decoding.
*/
if (nmsgs > 0 || XLogLogicalInfoActive())
{
xl_xinfo.xinfo |= XACT_XINFO_HAS_DBINFO;
xl_dbinfo.dbId = MyDatabaseId;
xl_dbinfo.tsId = MyDatabaseTableSpace;
}
if (xl_xinfo.xinfo & XACT_XINFO_HAS_TWOPHASE)
{
XLogRegisterData(&xl_twophase, sizeof(xl_xact_twophase));
if (xl_xinfo.xinfo & XACT_XINFO_HAS_GID)
XLogRegisterData(twophase_gid, strlen(twophase_gid) + 1);
}
if (xl_xinfo.xinfo & XACT_XINFO_HAS_ORIGIN)
XLogRegisterData(&xl_origin, sizeof(xl_xact_origin));
/* we allow filtering by xacts */
XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
return XLogInsert(RM_XACT_ID, info);
}
/*
* Log the commit record for a plain or twophase transaction abort.
*
* A 2pc abort will be emitted when twophase_xid is valid, a plain one
* otherwise.
*/
XLogRecPtr
XactLogAbortRecord(TimestampTz abort_time,
int nsubxacts, TransactionId *subxacts,
int nrels, RelFileLocator *rels,
int ndroppedstats, xl_xact_stats_item *droppedstats,
int xactflags, TransactionId twophase_xid,
const char *twophase_gid)
{
xl_xact_abort xlrec;
xl_xact_xinfo xl_xinfo;
xl_xact_subxacts xl_subxacts;
xl_xact_relfilelocators xl_relfilelocators;
xl_xact_stats_items xl_dropped_stats;
xl_xact_twophase xl_twophase;
xl_xact_dbinfo xl_dbinfo;
xl_xact_origin xl_origin;
uint8 info;
Assert(CritSectionCount > 0);
xl_xinfo.xinfo = 0;
/* decide between a plain and 2pc abort */
if (!TransactionIdIsValid(twophase_xid))
info = XLOG_XACT_ABORT;
else
info = XLOG_XACT_ABORT_PREPARED;
/* First figure out and collect all the information needed */
xlrec.xact_time = abort_time;
if ((xactflags & XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK))
xl_xinfo.xinfo |= XACT_XINFO_HAS_AE_LOCKS;
/*
* Dump transaction origin information. We need this during recovery to
* update the replication origin progress.
*/
if (replorigin_session_origin != InvalidRepOriginId)
{
xl_xinfo.xinfo |= XACT_XINFO_HAS_ORIGIN;
if (xl_xinfo.xinfo & XACT_XINFO_HAS_TWOPHASE)
{
XLogRegisterData(&xl_twophase, sizeof(xl_xact_twophase));
if (xl_xinfo.xinfo & XACT_XINFO_HAS_GID)
XLogRegisterData(twophase_gid, strlen(twophase_gid) + 1);
}
if (xl_xinfo.xinfo & XACT_XINFO_HAS_ORIGIN)
XLogRegisterData(&xl_origin, sizeof(xl_xact_origin));
/* Include the replication origin */
XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
return XLogInsert(RM_XACT_ID, info);
}
/*
* Before 9.0 this was a fairly short function, but now it performs many
* actions for which the order of execution is critical.
*/
static void
xact_redo_commit(xl_xact_parsed_commit *parsed,
TransactionId xid,
XLogRecPtr lsn,
RepOriginId origin_id)
{
TransactionId max_xid;
TimestampTz commit_time;
/* Set the transaction commit timestamp and metadata */
TransactionTreeSetCommitTsData(xid, parsed->nsubxacts, parsed->subxacts,
commit_time, origin_id);
if (standbyState == STANDBY_DISABLED)
{
/*
* Mark the transaction committed in pg_xact.
*/
TransactionIdCommitTree(xid, parsed->nsubxacts, parsed->subxacts);
}
else
{
/*
* If a transaction completion record arrives that has as-yet
* unobserved subtransactions then this will not have been fully
* handled by the call to RecordKnownAssignedTransactionIds() in the
* main recovery loop in xlog.c. So we need to do bookkeeping again to
* cover that case. This is confusing and it is easy to think this
* call is irrelevant, which has happened three times in development
* already. Leave it in.
*/
RecordKnownAssignedTransactionIds(max_xid);
/*
* Mark the transaction committed in pg_xact. We use async commit
* protocol during recovery to provide information on database
* consistency for when users try to set hint bits. It is important
* that we do not set hint bits until the minRecoveryPoint is past
* this commit record. This ensures that if we crash we don't see hint
* bits set on changes made by transactions that haven't yet
* recovered. It's unlikely but it's good to be safe.
*/
TransactionIdAsyncCommitTree(xid, parsed->nsubxacts, parsed->subxacts, lsn);
/*
* We must mark clog before we update the ProcArray.
*/
ExpireTreeKnownAssignedTransactionIds(xid, parsed->nsubxacts, parsed->subxacts, max_xid);
/*
* Send any cache invalidations attached to the commit. We must
* maintain the same order of invalidation then release locks as
* occurs in CommitTransaction().
*/
ProcessCommittedInvalidationMessages(parsed->msgs, parsed->nmsgs,
XactCompletionRelcacheInitFileInval(parsed->xinfo),
parsed->dbId, parsed->tsId);
/*
* Release locks, if any. We do this for both two phase and normal one
* phase transactions. In effect we are ignoring the prepare phase and
* just going straight to lock release.
*/
if (parsed->xinfo & XACT_XINFO_HAS_AE_LOCKS)
StandbyReleaseLockTree(xid, parsed->nsubxacts, parsed->subxacts);
}
/* Make sure files supposed to be dropped are dropped */
if (parsed->nrels > 0)
{
/*
* First update minimum recovery point to cover this WAL record. Once
* a relation is deleted, there's no going back. The buffer manager
* enforces the WAL-first rule for normal updates to relation files,
* so that the minimum recovery point is always updated before the
* corresponding change in the data file is flushed to disk, but we
* have to do the same here since we're bypassing the buffer manager.
*
* Doing this before deleting the files means that if a deletion fails
* for some reason, you cannot start up the system even after restart,
* until you fix the underlying situation so that the deletion will
* succeed. Alternatively, we could update the minimum recovery point
* after deletion, but that would leave a small window where the
* WAL-first rule would be violated.
*/
XLogFlush(lsn);
/* Make sure files supposed to be dropped are dropped */
DropRelationFiles(parsed->xlocators, parsed->nrels, true);
}
if (parsed->nstats > 0)
{
/* see equivalent call for relations above */
XLogFlush(lsn);
/*
* We issue an XLogFlush() for the same reason we emit ForceSyncCommit()
* in normal operation. For example, in CREATE DATABASE, we copy all files
* from the template database, and then commit the transaction. If we
* crash after all the files have been copied but before the commit, you
* have files in the data directory without an entry in pg_database. To
* minimize the window for that, we use ForceSyncCommit() to rush the
* commit record to disk as quick as possible. We have the same window
* during recovery, and forcing an XLogFlush() (which updates
* minRecoveryPoint during recovery) helps to reduce that problem window,
* for any user that requested ForceSyncCommit().
*/
if (XactCompletionForceSyncCommit(parsed->xinfo))
XLogFlush(lsn);
/*
* If asked by the primary (because someone is waiting for a synchronous
* commit = remote_apply), we will need to ask walreceiver to send a reply
* immediately.
*/
if (XactCompletionApplyFeedback(parsed->xinfo))
XLogRequestWalReceiverReply();
}
/*
* Be careful with the order of execution, as with xact_redo_commit().
* The two functions are similar but differ in key places.
*
* Note also that an abort can be for a subtransaction and its children,
* not just for a top level abort. That means we have to consider
* topxid != xid, whereas in commit we would find topxid == xid always
* because subtransaction commit is never WAL logged.
*/
static void
xact_redo_abort(xl_xact_parsed_abort *parsed, TransactionId xid,
XLogRecPtr lsn, RepOriginId origin_id)
{
TransactionId max_xid;
Assert(TransactionIdIsValid(xid));
/* Make sure nextXid is beyond any XID mentioned in the record. */
max_xid = TransactionIdLatest(xid,
parsed->nsubxacts,
parsed->subxacts);
AdvanceNextFullTransactionIdPastXid(max_xid);
if (standbyState == STANDBY_DISABLED)
{
/* Mark the transaction aborted in pg_xact, no need for async stuff */
TransactionIdAbortTree(xid, parsed->nsubxacts, parsed->subxacts);
}
else
{
/*
* If a transaction completion record arrives that has as-yet
* unobserved subtransactions then this will not have been fully
* handled by the call to RecordKnownAssignedTransactionIds() in the
* main recovery loop in xlog.c. So we need to do bookkeeping again to
* cover that case. This is confusing and it is easy to think this
* call is irrelevant, which has happened three times in development
* already. Leave it in.
*/
RecordKnownAssignedTransactionIds(max_xid);
/* Mark the transaction aborted in pg_xact, no need for async stuff */
TransactionIdAbortTree(xid, parsed->nsubxacts, parsed->subxacts);
/*
* We must update the ProcArray after we have marked clog.
*/
ExpireTreeKnownAssignedTransactionIds(xid, parsed->nsubxacts, parsed->subxacts, max_xid);
/*
* There are no invalidation messages to send or undo.
*/
/*
* Release locks, if any. There are no invalidations to send.
*/
if (parsed->xinfo & XACT_XINFO_HAS_AE_LOCKS)
StandbyReleaseLockTree(xid, parsed->nsubxacts, parsed->subxacts);
}
/* Make sure files supposed to be dropped are dropped */
if (parsed->nrels > 0)
{
/*
* See comments about update of minimum recovery point on truncation,
* in xact_redo_commit().
*/
XLogFlush(lsn);
/* Delete TwoPhaseState gxact entry and/or 2PC file. */
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
PrepareRedoRemove(parsed.twophase_xid, false);
LWLockRelease(TwoPhaseStateLock);
}
else if (info == XLOG_XACT_PREPARE)
{
/*
* Store xid and start/end pointers of the WAL record in TwoPhaseState
* gxact entry.
*/
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
PrepareRedoAdd(XLogRecGetData(record),
record->ReadRecPtr,
record->EndRecPtr,
XLogRecGetOrigin(record));
LWLockRelease(TwoPhaseStateLock);
}
else if (info == XLOG_XACT_ASSIGNMENT)
{
xl_xact_assignment *xlrec = (xl_xact_assignment *) XLogRecGetData(record);
if (standbyState >= STANDBY_INITIALIZED)
ProcArrayApplyXidAssignment(xlrec->xtop,
xlrec->nsubxacts, xlrec->xsub);
}
else if (info == XLOG_XACT_INVALIDATIONS)
{
/*
* XXX we do ignore this for now, what matters are invalidations
* written into the commit record.
*/
}
else
elog(PANIC, "xact_redo: unknown op code %u", info);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.