diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index f9d257d1a11d9129bc619a9c85f9acbc5b3e35db..f25b8570455004ba2b9fefdabe9a07b88cc573fb 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -630,6 +630,9 @@ AtSubStart_Notify(void)
 
 	upperPendingNotifies = lcons(pendingNotifies, upperPendingNotifies);
 
+	Assert(list_length(upperPendingNotifies) ==
+		   GetCurrentTransactionNestLevel() - 1);
+
 	pendingNotifies = NIL;
 
 	MemoryContextSwitchTo(old_cxt);
@@ -648,6 +651,9 @@ AtSubCommit_Notify(void)
 	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.
@@ -661,13 +667,23 @@ AtSubCommit_Notify(void)
 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);
+	}
 }
 
 /*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 7e73f6b000f9a48da9dba4d26201a0b5154d6eb6..18260e6272917cf6d379d09060fb1d2aba8e0d06 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -1669,8 +1669,11 @@ ltrmark:;
  * 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.
@@ -1742,7 +1745,6 @@ typedef struct DeferredTriggersData
 	DeferredTriggerEvent *tail_stack;
 	DeferredTriggerEvent *imm_stack;
 	DeferredTriggerState *state_stack;
-	int			numpushed;
 	int			numalloc;
 } DeferredTriggersData;
 
@@ -2165,7 +2167,6 @@ DeferredTriggerBeginXact(void)
 	deferredTriggers->imm_stack = NULL;
 	deferredTriggers->state_stack = NULL;
 	deferredTriggers->numalloc = 0;
-	deferredTriggers->numpushed = 0;
 }
 
 
@@ -2251,6 +2252,8 @@ DeferredTriggerAbortXact(void)
 void
 DeferredTriggerBeginSubXact(void)
 {
+	int			my_level = GetCurrentTransactionNestLevel();
+
 	/*
 	 * Ignore call if the transaction is in aborted state.
 	 */
@@ -2260,7 +2263,7 @@ DeferredTriggerBeginSubXact(void)
 	/*
 	 * Allocate more space in the stacks if needed.
 	 */
-	if (deferredTriggers->numpushed == deferredTriggers->numalloc)
+	while (my_level >= deferredTriggers->numalloc)
 	{
 		if (deferredTriggers->numalloc == 0)
 		{
@@ -2282,32 +2285,28 @@ DeferredTriggerBeginSubXact(void)
 		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;
 }
 
 /*
@@ -2318,6 +2317,7 @@ DeferredTriggerBeginSubXact(void)
 void
 DeferredTriggerEndSubXact(bool isCommit)
 {
+	int			my_level = GetCurrentTransactionNestLevel();
 	DeferredTriggerState state;
 
 	/*
@@ -2327,18 +2327,18 @@ DeferredTriggerEndSubXact(bool isCommit)
 		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
 	{
@@ -2346,9 +2346,9 @@ DeferredTriggerEndSubXact(bool isCommit)
 		 * 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.
@@ -2367,12 +2367,14 @@ DeferredTriggerEndSubXact(bool isCommit)
 		 * 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;
 	}
 }
 
@@ -2457,6 +2459,8 @@ DeferredTriggerStateAddItem(DeferredTriggerState state,
 void
 DeferredTriggerSetState(ConstraintsSetStmt *stmt)
 {
+	int			my_level = GetCurrentTransactionNestLevel();
+
 	/*
 	 * Ignore call if we aren't in a transaction.
 	 */
@@ -2468,10 +2472,10 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
 	 * 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);
 	}
 
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 830d45169a645d1b3f01a5452d0fdaed714a01c8..5c4db3da807b283385fedb31911a9828b8f349c5 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -8,7 +8,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -1059,8 +1059,14 @@ XidCacheRemoveRunningXids(TransactionId xid, int nxids, TransactionId *xids)
 				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--)
@@ -1071,8 +1077,9 @@ XidCacheRemoveRunningXids(TransactionId xid, int nxids, TransactionId *xids)
 			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);
 }
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index 3c85b05dee4329d40fcb5786c1344f73882a8bdf..8dd806bb0d7013b1fe3b2e7406b898bb02415922 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -80,12 +80,13 @@
  * 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"
@@ -139,6 +140,9 @@ typedef struct TransInvalidationInfo
 	/* Back link to parent transaction's info */
 	struct TransInvalidationInfo *parent;
 
+	/* Subtransaction nesting depth */
+	int		my_level;
+
 	/* head of current-command event list */
 	InvalidationListHeader CurrentCmdInvalidMsgs;
 
@@ -603,6 +607,7 @@ AtStart_Inval(void)
 	transInvalInfo = (TransInvalidationInfo *)
 		MemoryContextAllocZero(TopTransactionContext,
 							   sizeof(TransInvalidationInfo));
+	transInvalInfo->my_level = GetCurrentTransactionNestLevel();
 }
 
 /*
@@ -619,6 +624,7 @@ AtSubStart_Inval(void)
 		MemoryContextAllocZero(TopTransactionContext,
 							   sizeof(TransInvalidationInfo));
 	myInfo->parent = transInvalInfo;
+	myInfo->my_level = GetCurrentTransactionNestLevel();
 	transInvalInfo = myInfo;
 }
 
@@ -649,11 +655,11 @@ AtSubStart_Inval(void)
 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
@@ -671,8 +677,11 @@ AtEOXact_Inval(bool isCommit)
 		if (transInvalInfo->RelcacheInitFileInval)
 			RelationCacheInitFileInvalidate(false);
 	}
-	else
+	else if (transInvalInfo != NULL)
 	{
+		/* Must be at top of stack */
+		Assert(transInvalInfo->parent == NULL);
+
 		ProcessInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
 									LocalExecuteInvalidationMessage);
 	}
@@ -696,18 +705,21 @@ AtEOXact_Inval(bool isCommit)
  *
  * 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();
 
@@ -718,18 +730,27 @@ AtEOSubXact_Inval(bool isCommit)
 		/* 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);
+	}
 }
 
 /*