/*
* transaction state structure
+ *
+ * Note: parallelModeLevel counts the number of unmatched EnterParallelMode
+ * calls done at this transaction level. parallelChildXact is true if any
+ * upper transaction level has nonzero parallelModeLevel.
*/
typedef struct TransactionStateData
{
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 */
* operation, so we can't account for new XIDs at this point.
*/
if (IsInParallelMode() || IsParallelWorker())
- elog(ERROR, "cannot assign XIDs during a parallel operation");
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+ errmsg("cannot assign XIDs during a parallel operation")));
/*
* Ensure parent(s) have XIDs, so that a child always has an XID later
* could relax this restriction when currentCommandIdUsed was already
* true at the start of the parallel operation.
*/
- Assert(!IsParallelWorker());
+ if (IsParallelWorker())
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+ errmsg("cannot modify data in a parallel worker")));
+
currentCommandIdUsed = true;
}
return currentCommandId;
TransactionState s = CurrentTransactionState;
Assert(s->parallelModeLevel > 0);
- Assert(s->parallelModeLevel > 1 || !ParallelContextActive());
+ Assert(s->parallelModeLevel > 1 || s->parallelChildXact ||
+ !ParallelContextActive());
--s->parallelModeLevel;
}
* match across all workers. Mere caches usually don't require such a
* restriction. State modified in a strict push/pop fashion, such as the
* active snapshot stack, is often fine.
+ *
+ * We say we are in parallel mode if we are in a subxact of a transaction
+ * that's initiated a parallel operation; for most purposes that context
+ * has all the same restrictions.
*/
bool
IsInParallelMode(void)
{
- return CurrentTransactionState->parallelModeLevel != 0;
+ TransactionState s = CurrentTransactionState;
+
+ return s->parallelModeLevel != 0 || s->parallelChildXact;
}
/*
* point.
*/
if (IsInParallelMode() || IsParallelWorker())
- elog(ERROR, "cannot start commands during a parallel operation");
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+ errmsg("cannot start commands during a parallel operation")));
currentCommandId += 1;
if (currentCommandId == InvalidCommandId)
CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT
: XACT_EVENT_PRE_COMMIT);
- /* If we might have parallel workers, clean them up now. */
- if (IsInParallelMode())
- AtEOXact_Parallel(true);
+ /*
+ * If this xact has started any unfinished parallel operation, clean up
+ * its workers, warning about leaked resources. (But we don't actually
+ * reset parallelModeLevel till entering TRANS_COMMIT, a bit below. This
+ * keeps parallel mode restrictions active as long as possible in a
+ * parallel worker.)
+ */
+ 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);
*/
s->state = TRANS_COMMIT;
s->parallelModeLevel = 0;
+ s->parallelChildXact = false; /* should be false already */
/* Disable transaction timeout */
if (TransactionTimeout > 0)
/* Reset snapshot export state. */
SnapBuildResetExportedSnapshotState();
- /* If in parallel mode, clean up workers and exit parallel mode. */
- if (IsInParallelMode())
- {
- AtEOXact_Parallel(false);
- s->parallelModeLevel = 0;
- }
+ /*
+ * If this xact has started any unfinished parallel operation, clean up
+ * its workers and exit parallel mode. Don't warn about leaked resources.
+ */
+ AtEOXact_Parallel(false);
+ s->parallelModeLevel = 0;
+ s->parallelChildXact = false; /* should be false already */
/*
* do abort processing
s->nChildXids = 0;
s->maxChildXids = 0;
s->parallelModeLevel = 0;
+ s->parallelChildXact = false;
XactTopFullTransactionId = InvalidFullTransactionId;
nParallelCurrentXids = 0;
* is TBLOCK_PARALLEL_INPROGRESS, so we can treat that as an invalid case
* below.)
*/
- if (IsInParallelMode())
+ if (IsInParallelMode() || IsParallelWorker())
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot define savepoints during a parallel operation")));
* is TBLOCK_PARALLEL_INPROGRESS, so we can treat that as an invalid case
* below.)
*/
- if (IsInParallelMode())
+ if (IsInParallelMode() || IsParallelWorker())
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot release savepoints during a parallel operation")));
* is TBLOCK_PARALLEL_INPROGRESS, so we can treat that as an invalid case
* below.)
*/
- if (IsInParallelMode())
+ if (IsInParallelMode() || IsParallelWorker())
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot rollback to savepoints during a parallel operation")));
/*
* BeginInternalSubTransaction
* This is the same as DefineSavepoint except it allows TBLOCK_STARTED,
- * TBLOCK_IMPLICIT_INPROGRESS, TBLOCK_END, and TBLOCK_PREPARE states,
- * and therefore it can safely be used in functions that might be called
- * when not inside a BEGIN block or when running deferred triggers at
- * COMMIT/PREPARE time. Also, it automatically does
- * CommitTransactionCommand/StartTransactionCommand instead of expecting
- * the caller to do it.
+ * TBLOCK_IMPLICIT_INPROGRESS, TBLOCK_PARALLEL_INPROGRESS, TBLOCK_END,
+ * and TBLOCK_PREPARE states, and therefore it can safely be used in
+ * functions that might be called when not inside a BEGIN block or when
+ * running deferred triggers at COMMIT/PREPARE time. Also, it
+ * automatically does CommitTransactionCommand/StartTransactionCommand
+ * instead of expecting the caller to do it.
*/
void
BeginInternalSubTransaction(const char *name)
{
TransactionState s = CurrentTransactionState;
+ bool save_ExitOnAnyError = ExitOnAnyError;
/*
- * Workers synchronize transaction state at the beginning of each parallel
- * operation, so we can't account for new subtransactions after that
- * point. We might be able to make an exception for the type of
- * subtransaction established by this function, which is typically used in
- * contexts where we're going to release or roll back the subtransaction
- * before proceeding further, so that no enduring change to the
- * transaction state occurs. For now, however, we prohibit this case along
- * with all the others.
- */
- if (IsInParallelMode())
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
- errmsg("cannot start subtransactions during a parallel operation")));
+ * Errors within this function are improbable, but if one does happen we
+ * force a FATAL exit. Callers generally aren't prepared to handle losing
+ * control, and moreover our transaction state is probably corrupted if we
+ * fail partway through; so an ordinary ERROR longjmp isn't okay.
+ */
+ ExitOnAnyError = true;
+
+ /*
+ * We do not check for parallel mode here. It's permissible to start and
+ * end "internal" subtransactions while in parallel mode, so long as no
+ * new XIDs or command IDs are assigned. Enforcement of that occurs in
+ * AssignTransactionId() and CommandCounterIncrement().
+ */
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:
/* These cases are invalid. */
case TBLOCK_DEFAULT:
case TBLOCK_BEGIN:
- case TBLOCK_PARALLEL_INPROGRESS:
case TBLOCK_SUBBEGIN:
case TBLOCK_SUBRELEASE:
case TBLOCK_SUBCOMMIT:
CommitTransactionCommand();
StartTransactionCommand();
+
+ ExitOnAnyError = save_ExitOnAnyError;
}
/*
TransactionState s = CurrentTransactionState;
/*
- * Workers synchronize transaction state at the beginning of each parallel
- * operation, so we can't account for commit of subtransactions after that
- * point. This should not happen anyway. Code calling this would
- * typically have called BeginInternalSubTransaction() first, failing
- * there.
+ * We do not check for parallel mode here. It's permissible to start and
+ * end "internal" subtransactions while in parallel mode, so long as no
+ * new XIDs or command IDs are assigned.
*/
- if (IsInParallelMode())
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
- errmsg("cannot commit subtransactions during a parallel operation")));
if (s->blockState != TBLOCK_SUBINPROGRESS)
elog(ERROR, "ReleaseCurrentSubTransaction: unexpected state %s",
TransactionState s = CurrentTransactionState;
/*
- * Unlike ReleaseCurrentSubTransaction(), this is nominally permitted
- * during parallel operations. That's because we may be in the leader,
- * recovering from an error thrown while we were in parallel mode. We
- * won't reach here in a worker, because BeginInternalSubTransaction()
- * will have failed.
+ * We do not check for parallel mode here. It's permissible to start and
+ * end "internal" subtransactions while in parallel mode, so long as no
+ * new XIDs or command IDs are assigned.
*/
switch (s->blockState)
Assert(s->blockState == TBLOCK_SUBINPROGRESS ||
s->blockState == TBLOCK_INPROGRESS ||
s->blockState == TBLOCK_IMPLICIT_INPROGRESS ||
+ s->blockState == TBLOCK_PARALLEL_INPROGRESS ||
s->blockState == TBLOCK_STARTED);
}
CallSubXactCallbacks(SUBXACT_EVENT_PRE_COMMIT_SUB, s->subTransactionId,
s->parent->subTransactionId);
- /* If in parallel mode, clean up workers and exit parallel mode. */
- if (IsInParallelMode())
+ /*
+ * If this subxact has started any unfinished parallel operation, clean up
+ * its workers and exit parallel mode. Warn about leaked resources.
+ */
+ AtEOSubXact_Parallel(true, s->subTransactionId);
+ if (s->parallelModeLevel != 0)
{
- AtEOSubXact_Parallel(true, s->subTransactionId);
+ elog(WARNING, "parallelModeLevel is %d not 0 at end of subtransaction",
+ s->parallelModeLevel);
s->parallelModeLevel = 0;
}
* exports are not supported in subtransactions.
*/
- /* Exit from parallel mode, if necessary. */
- if (IsInParallelMode())
- {
- AtEOSubXact_Parallel(false, s->subTransactionId);
- s->parallelModeLevel = 0;
- }
+ /*
+ * If this subxact has started any unfinished parallel operation, clean up
+ * its workers and exit parallel mode. Don't warn about leaked resources.
+ */
+ AtEOSubXact_Parallel(false, s->subTransactionId);
+ s->parallelModeLevel = 0;
/*
* We can skip all this stuff if the subxact failed before creating a
s->prevXactReadOnly = XactReadOnly;
s->startedInRecovery = p->startedInRecovery;
s->parallelModeLevel = 0;
+ s->parallelChildXact = (p->parallelModeLevel != 0 || p->parallelChildXact);
s->topXidLogged = false;
CurrentTransactionState = s;