* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/async.c,v 1.115 2004/08/29 05:06:41 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/async.c,v 1.116 2004/09/06 23:32:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
upperPendingNotifies = lcons(pendingNotifies, upperPendingNotifies);
+ Assert(list_length(upperPendingNotifies) ==
+ GetCurrentTransactionNestLevel() - 1);
+
pendingNotifies = NIL;
MemoryContextSwitchTo(old_cxt);
parentPendingNotifies = (List *) linitial(upperPendingNotifies);
upperPendingNotifies = list_delete_first(upperPendingNotifies);
+ Assert(list_length(upperPendingNotifies) ==
+ GetCurrentTransactionNestLevel() - 2);
+
/*
* We could try to eliminate duplicates here, but it seems not
* worthwhile.
void
AtSubAbort_Notify(void)
{
+ int my_level = GetCurrentTransactionNestLevel();
+
/*
* All we have to do is pop the stack --- the notifies made in this
* subxact are no longer interesting, and the space will be freed when
* CurTransactionContext is recycled.
+ *
+ * This routine could be called more than once at a given nesting level
+ * if there is trouble during subxact abort. Avoid dumping core by
+ * using GetCurrentTransactionNestLevel as the indicator of how far
+ * we need to prune the list.
*/
- pendingNotifies = (List *) linitial(upperPendingNotifies);
- upperPendingNotifies = list_delete_first(upperPendingNotifies);
+ while (list_length(upperPendingNotifies) > my_level - 2)
+ {
+ pendingNotifies = (List *) linitial(upperPendingNotifies);
+ upperPendingNotifies = list_delete_first(upperPendingNotifies);
+ }
}
/*
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.168 2004/08/29 05:06:41 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.169 2004/09/06 23:32:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* state data; each subtransaction level that modifies that state first
* saves a copy, which we use to restore the state if we abort.
*
- * numpushed and numalloc keep control of allocation and storage in the above
- * stacks. numpushed is essentially the current subtransaction nesting depth.
+ * We use GetCurrentTransactionNestLevel() to determine the correct array
+ * index in these stacks. numalloc is the number of allocated entries in
+ * each stack. (By not keeping our own stack pointer, we can avoid trouble
+ * in cases where errors during subxact abort cause multiple invocations
+ * of DeferredTriggerEndSubXact() at the same nesting depth.)
*
* XXX We need to be able to save the per-event data in a file if it grows too
* large.
DeferredTriggerEvent *tail_stack;
DeferredTriggerEvent *imm_stack;
DeferredTriggerState *state_stack;
- int numpushed;
int numalloc;
} DeferredTriggersData;
deferredTriggers->imm_stack = NULL;
deferredTriggers->state_stack = NULL;
deferredTriggers->numalloc = 0;
- deferredTriggers->numpushed = 0;
}
void
DeferredTriggerBeginSubXact(void)
{
+ int my_level = GetCurrentTransactionNestLevel();
+
/*
* Ignore call if the transaction is in aborted state.
*/
/*
* Allocate more space in the stacks if needed.
*/
- if (deferredTriggers->numpushed == deferredTriggers->numalloc)
+ while (my_level >= deferredTriggers->numalloc)
{
if (deferredTriggers->numalloc == 0)
{
else
{
/* repalloc will keep the stacks in the same context */
- deferredTriggers->numalloc *= 2;
+ int new_alloc = deferredTriggers->numalloc * 2;
deferredTriggers->tail_stack = (DeferredTriggerEvent *)
repalloc(deferredTriggers->tail_stack,
- deferredTriggers->numalloc * sizeof(DeferredTriggerEvent));
+ new_alloc * sizeof(DeferredTriggerEvent));
deferredTriggers->imm_stack = (DeferredTriggerEvent *)
repalloc(deferredTriggers->imm_stack,
- deferredTriggers->numalloc * sizeof(DeferredTriggerEvent));
+ new_alloc * sizeof(DeferredTriggerEvent));
deferredTriggers->state_stack = (DeferredTriggerState *)
repalloc(deferredTriggers->state_stack,
- deferredTriggers->numalloc * sizeof(DeferredTriggerState));
+ new_alloc * sizeof(DeferredTriggerState));
+ deferredTriggers->numalloc = new_alloc;
}
}
/*
- * Push the current list position into the stack and reset the
- * pointer.
+ * Push the current information into the stack.
*/
- deferredTriggers->tail_stack[deferredTriggers->numpushed] =
- deferredTriggers->tail_thisxact;
- deferredTriggers->imm_stack[deferredTriggers->numpushed] =
- deferredTriggers->events_imm;
+ deferredTriggers->tail_stack[my_level] = deferredTriggers->tail_thisxact;
+ deferredTriggers->imm_stack[my_level] = deferredTriggers->events_imm;
/* State is not saved until/unless changed */
- deferredTriggers->state_stack[deferredTriggers->numpushed] = NULL;
-
- deferredTriggers->numpushed++;
+ deferredTriggers->state_stack[my_level] = NULL;
}
/*
void
DeferredTriggerEndSubXact(bool isCommit)
{
+ int my_level = GetCurrentTransactionNestLevel();
DeferredTriggerState state;
/*
return;
/*
- * Move back the "top of the stack."
+ * Pop the prior state if needed.
*/
- Assert(deferredTriggers->numpushed > 0);
-
- deferredTriggers->numpushed--;
+ Assert(my_level < deferredTriggers->numalloc);
if (isCommit)
{
/* If we saved a prior state, we don't need it anymore */
- state = deferredTriggers->state_stack[deferredTriggers->numpushed];
+ state = deferredTriggers->state_stack[my_level];
if (state != NULL)
pfree(state);
+ /* this avoids double pfree if error later: */
+ deferredTriggers->state_stack[my_level] = NULL;
}
else
{
* Aborting --- restore the pointers from the stacks.
*/
deferredTriggers->tail_thisxact =
- deferredTriggers->tail_stack[deferredTriggers->numpushed];
+ deferredTriggers->tail_stack[my_level];
deferredTriggers->events_imm =
- deferredTriggers->imm_stack[deferredTriggers->numpushed];
+ deferredTriggers->imm_stack[my_level];
/*
* Cleanup the head and the tail of the list.
* Restore the trigger state. If the saved state is NULL, then
* this subxact didn't save it, so it doesn't need restoring.
*/
- state = deferredTriggers->state_stack[deferredTriggers->numpushed];
+ state = deferredTriggers->state_stack[my_level];
if (state != NULL)
{
pfree(deferredTriggers->state);
deferredTriggers->state = state;
}
+ /* this avoids double pfree if error later: */
+ deferredTriggers->state_stack[my_level] = NULL;
}
}
void
DeferredTriggerSetState(ConstraintsSetStmt *stmt)
{
+ int my_level = GetCurrentTransactionNestLevel();
+
/*
* Ignore call if we aren't in a transaction.
*/
* already, save it so it can be restored if the subtransaction
* aborts.
*/
- if (deferredTriggers->numpushed > 0 &&
- deferredTriggers->state_stack[deferredTriggers->numpushed - 1] == NULL)
+ if (my_level > 1 &&
+ deferredTriggers->state_stack[my_level] == NULL)
{
- deferredTriggers->state_stack[deferredTriggers->numpushed - 1] =
+ deferredTriggers->state_stack[my_level] =
DeferredTriggerStateCopy(deferredTriggers->state);
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/ipc/sinval.c,v 1.72 2004/08/29 05:06:48 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/ipc/sinval.c,v 1.73 2004/09/06 23:33:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
break;
}
}
- /* We should have found it, unless the cache has overflowed */
- Assert(j >= 0 || MyProc->subxids.overflowed);
+ /*
+ * Ordinarily we should have found it, unless the cache has overflowed.
+ * However it's also possible for this routine to be invoked multiple
+ * times for the same subtransaction, in case of an error during
+ * AbortSubTransaction. So instead of Assert, emit a debug warning.
+ */
+ if (j < 0 && !MyProc->subxids.overflowed)
+ elog(WARNING, "did not find subXID %u in MyProc", anxid);
}
for (j = MyProc->subxids.nxids - 1; j >= 0; j--)
break;
}
}
- /* We should have found it, unless the cache has overflowed */
- Assert(j >= 0 || MyProc->subxids.overflowed);
+ /* Ordinarily we should have found it, unless the cache has overflowed */
+ if (j < 0 && !MyProc->subxids.overflowed)
+ elog(WARNING, "did not find subXID %u in MyProc", xid);
LWLockRelease(SInvalLock);
}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.66 2004/08/29 05:06:50 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.67 2004/09/06 23:33:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "access/xact.h"
#include "catalog/catalog.h"
#include "miscadmin.h"
#include "storage/sinval.h"
/* Back link to parent transaction's info */
struct TransInvalidationInfo *parent;
+ /* Subtransaction nesting depth */
+ int my_level;
+
/* head of current-command event list */
InvalidationListHeader CurrentCmdInvalidMsgs;
transInvalInfo = (TransInvalidationInfo *)
MemoryContextAllocZero(TopTransactionContext,
sizeof(TransInvalidationInfo));
+ transInvalInfo->my_level = GetCurrentTransactionNestLevel();
}
/*
MemoryContextAllocZero(TopTransactionContext,
sizeof(TransInvalidationInfo));
myInfo->parent = transInvalInfo;
+ myInfo->my_level = GetCurrentTransactionNestLevel();
transInvalInfo = myInfo;
}
void
AtEOXact_Inval(bool isCommit)
{
- /* Must be at top of stack */
- Assert(transInvalInfo != NULL && transInvalInfo->parent == NULL);
-
if (isCommit)
{
+ /* Must be at top of stack */
+ Assert(transInvalInfo != NULL && transInvalInfo->parent == NULL);
+
/*
* Relcache init file invalidation requires processing both before
* and after we send the SI messages. However, we need not do
if (transInvalInfo->RelcacheInitFileInval)
RelationCacheInitFileInvalidate(false);
}
- else
+ else if (transInvalInfo != NULL)
{
+ /* Must be at top of stack */
+ Assert(transInvalInfo->parent == NULL);
+
ProcessInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
LocalExecuteInvalidationMessage);
}
*
* In any case, pop the transaction stack. We need not physically free memory
* here, since CurTransactionContext is about to be emptied anyway
- * (if aborting).
+ * (if aborting). Beware of the possibility of aborting the same nesting
+ * level twice, though.
*/
void
AtEOSubXact_Inval(bool isCommit)
{
+ int my_level = GetCurrentTransactionNestLevel();
TransInvalidationInfo *myInfo = transInvalInfo;
- /* Must be at non-top of stack */
- Assert(myInfo != NULL && myInfo->parent != NULL);
-
if (isCommit)
{
+ /* Must be at non-top of stack */
+ Assert(myInfo != NULL && myInfo->parent != NULL);
+ Assert(myInfo->my_level == my_level);
+
/* If CurrentCmdInvalidMsgs still has anything, fix it */
CommandEndInvalidationMessages();
/* Pending relcache inval becomes parent's problem too */
if (myInfo->RelcacheInitFileInval)
myInfo->parent->RelcacheInitFileInval = true;
+
+ /* Pop the transaction state stack */
+ transInvalInfo = myInfo->parent;
+
+ /* Need not free anything else explicitly */
+ pfree(myInfo);
}
- else
+ else if (myInfo != NULL && myInfo->my_level == my_level)
{
+ /* Must be at non-top of stack */
+ Assert(myInfo->parent != NULL);
+
ProcessInvalidationMessages(&myInfo->PriorCmdInvalidMsgs,
LocalExecuteInvalidationMessage);
- }
- /* Pop the transaction state stack */
- transInvalInfo = myInfo->parent;
+ /* Pop the transaction state stack */
+ transInvalInfo = myInfo->parent;
- /* Need not free anything else explicitly */
- pfree(myInfo);
+ /* Need not free anything else explicitly */
+ pfree(myInfo);
+ }
}
/*