diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 41f2390fb8..7cc220bca0 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -447,14 +447,37 @@ MemoryContextDelete(MemoryContext context)
 void
 MemoryContextDeleteChildren(MemoryContext context)
 {
+	MemoryContext cur,
+				next;
+
 	Assert(MemoryContextIsValid(context));
 
 	/*
-	 * MemoryContextDelete will delink the child from me, so just iterate as
-	 * long as there is a child.
+	 * We iterate rather than recursing, so that cleaning up a deep nest of
+	 * contexts doesn't require unbounded stack space.  (This avoids possible
+	 * failure during transaction cleanup, which would be bad.)  This works
+	 * because by the time we traverse back up to a parent context, it will
+	 * have no remaining children and will be seen as deletable.
 	 */
-	while (context->firstchild != NULL)
-		MemoryContextDelete(context->firstchild);
+	cur = context->firstchild;
+	while (cur != NULL)
+	{
+		/* Descend until we find a childless context */
+		if (cur->firstchild != NULL)
+		{
+			cur = cur->firstchild;
+			continue;
+		}
+		/* We can delete cur, but first remember the next deletion target */
+		if (cur->nextchild != NULL)
+			next = cur->nextchild;
+		else if (cur->parent != context)
+			next = cur->parent;
+		else
+			next = NULL;
+		MemoryContextDelete(cur);
+		cur = next;
+	}
 }
 
 /*
