v44-0001-Make-PlanState-tree-cleanup-non-recursive.patch

application/octet-stream

Filename: v44-0001-Make-PlanState-tree-cleanup-non-recursive.patch
Type: application/octet-stream
Part: 5
Message: Re: generic plans and "initial" pruning

Patch

Format: format-patch
Series: patch v44-0001
Subject: Make PlanState tree cleanup non-recursive
File+
src/backend/executor/execMain.c 19 17
src/backend/executor/execProcnode.c 26 30
src/backend/executor/execUtils.c 2 0
src/backend/executor/nodeAgg.c 1 3
src/backend/executor/nodeAppend.c 1 19
src/backend/executor/nodeBitmapAnd.c 1 22
src/backend/executor/nodeBitmapHeapscan.c 1 4
src/backend/executor/nodeBitmapOr.c 1 22
src/backend/executor/nodeForeignscan.c 1 3
src/backend/executor/nodeGather.c 1 1
src/backend/executor/nodeGatherMerge.c 1 1
src/backend/executor/nodeGroup.c 1 4
src/backend/executor/nodeHash.c 1 7
src/backend/executor/nodeHashjoin.c 1 5
src/backend/executor/nodeIncrementalSort.c 1 4
src/backend/executor/nodeLimit.c 1 1
src/backend/executor/nodeLockRows.c 1 1
src/backend/executor/nodeMaterial.c 1 4
src/backend/executor/nodeMemoize.c 1 4
src/backend/executor/nodeMergeAppend.c 1 19
src/backend/executor/nodeMergejoin.c 1 5
src/backend/executor/nodeModifyTable.c 2 5
src/backend/executor/nodeNestloop.c 1 5
src/backend/executor/nodeProjectSet.c 1 4
src/backend/executor/nodeRecursiveunion.c 1 5
src/backend/executor/nodeResult.c 1 4
src/backend/executor/nodeSetOp.c 1 1
src/backend/executor/nodeSort.c 1 4
src/backend/executor/nodeSubqueryscan.c 1 4
src/backend/executor/nodeUnique.c 1 1
src/backend/executor/nodeWindowAgg.c 1 3
src/backend/executor/README 2 2
src/include/nodes/execnodes.h 2 0
From a55bd363690bc4c28047e4b874ce80384e37c49d Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Tue, 1 Aug 2023 11:36:24 +0900
Subject: [PATCH v44 1/6] Make PlanState tree cleanup non-recursive

With this change, node type specific subroutines of ExecEndNode()
are no longer required to also clean up the child nodes of a given
node, only its own stuff.  Instead, ExecEndPlan() calls
ExecInitNode() directly for each node in the PlanState tree by
iterating over a list (EState.es_planstate_nodes) of all those nodes
built during the ExecInitNode() traversal of the tree.

This changes the order in which the nodes get cleaned up, because
they are now cleaned up in the order in which they are added into
the list which is from leaf-level up to the root, whereas with the
current recursive approach cleanup occurs from the root to the
leaves.  The change seems harmless though, because there isn't
necessarily any coupling between of the cleanup actions of parent
and child nodes.

The main motivation behind this change is to allow the cases in
the future where ExecInitNode() traversal of the plan tree may
be aborted in the middle resulting in a partially initialized
PlanState tree.  Dealing with that case by making the cleanup
phase walk over a list of successfully initialized nodes seems
better / more robust than making the individual ExecEndNode()
subroutines deal with partially valid PlanState nodes.
---
 src/backend/executor/README                |  4 +-
 src/backend/executor/execMain.c            | 36 +++++++-------
 src/backend/executor/execProcnode.c        | 56 ++++++++++------------
 src/backend/executor/execUtils.c           |  2 +
 src/backend/executor/nodeAgg.c             |  4 +-
 src/backend/executor/nodeAppend.c          | 20 +-------
 src/backend/executor/nodeBitmapAnd.c       | 23 +--------
 src/backend/executor/nodeBitmapHeapscan.c  |  5 +-
 src/backend/executor/nodeBitmapOr.c        | 23 +--------
 src/backend/executor/nodeForeignscan.c     |  4 +-
 src/backend/executor/nodeGather.c          |  2 +-
 src/backend/executor/nodeGatherMerge.c     |  2 +-
 src/backend/executor/nodeGroup.c           |  5 +-
 src/backend/executor/nodeHash.c            |  8 +---
 src/backend/executor/nodeHashjoin.c        |  6 +--
 src/backend/executor/nodeIncrementalSort.c |  5 +-
 src/backend/executor/nodeLimit.c           |  2 +-
 src/backend/executor/nodeLockRows.c        |  2 +-
 src/backend/executor/nodeMaterial.c        |  5 +-
 src/backend/executor/nodeMemoize.c         |  5 +-
 src/backend/executor/nodeMergeAppend.c     | 20 +-------
 src/backend/executor/nodeMergejoin.c       |  6 +--
 src/backend/executor/nodeModifyTable.c     |  7 +--
 src/backend/executor/nodeNestloop.c        |  6 +--
 src/backend/executor/nodeProjectSet.c      |  5 +-
 src/backend/executor/nodeRecursiveunion.c  |  6 +--
 src/backend/executor/nodeResult.c          |  5 +-
 src/backend/executor/nodeSetOp.c           |  2 +-
 src/backend/executor/nodeSort.c            |  5 +-
 src/backend/executor/nodeSubqueryscan.c    |  5 +-
 src/backend/executor/nodeUnique.c          |  2 +-
 src/backend/executor/nodeWindowAgg.c       |  4 +-
 src/include/nodes/execnodes.h              |  2 +
 33 files changed, 80 insertions(+), 214 deletions(-)

diff --git a/src/backend/executor/README b/src/backend/executor/README
index 17775a49e2..67a5c1769b 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -310,13 +310,13 @@ This is a sketch of control flow for full query processing:
 		AfterTriggerEndQuery
 
 	ExecutorEnd
-		ExecEndNode --- recursively releases resources
+		ExecEndPlan --- releases plan resources
 		FreeExecutorState
 			frees per-query context and child contexts
 
 	FreeQueryDesc
 
-Per above comments, it's not really critical for ExecEndNode to free any
+Per above comments, it's not really critical for ExecEndPlan to free any
 memory; it'll all go away in FreeExecutorState anyway.  However, we do need to
 be careful to close relations, drop buffer pins, etc, so we do need to scan
 the plan state tree to find these sorts of resources.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4c5a7bbf62..235bb52ccc 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -82,7 +82,7 @@ ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 static void InitPlan(QueryDesc *queryDesc, int eflags);
 static void CheckValidRowMarkRel(Relation rel, RowMarkType markType);
 static void ExecPostprocessPlan(EState *estate);
-static void ExecEndPlan(PlanState *planstate, EState *estate);
+static void ExecEndPlan(EState *estate);
 static void ExecutePlan(EState *estate, PlanState *planstate,
 						bool use_parallel_mode,
 						CmdType operation,
@@ -500,7 +500,7 @@ standard_ExecutorEnd(QueryDesc *queryDesc)
 	 */
 	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-	ExecEndPlan(queryDesc->planstate, estate);
+	ExecEndPlan(estate);
 
 	/* do away with our snapshots */
 	UnregisterSnapshot(estate->es_snapshot);
@@ -1499,23 +1499,21 @@ ExecPostprocessPlan(EState *estate)
  * ----------------------------------------------------------------
  */
 static void
-ExecEndPlan(PlanState *planstate, EState *estate)
+ExecEndPlan(EState *estate)
 {
 	ListCell   *l;
 
 	/*
-	 * shut down the node-type-specific query processing
+	 * Shut down the node-type-specific query processing for all nodes that
+	 * were initialized in InitPlan().  That includes the nodes in both the
+	 * main plan tree (es_plannedstmt->planTree) and those in subplans
+	 * (es_plannedstmt->subplans).
 	 */
-	ExecEndNode(planstate);
-
-	/*
-	 * for subplans too
-	 */
-	foreach(l, estate->es_subplanstates)
+	foreach(l, estate->es_planstate_nodes)
 	{
-		PlanState  *subplanstate = (PlanState *) lfirst(l);
+		PlanState  *pstate = (PlanState *) lfirst(l);
 
-		ExecEndNode(subplanstate);
+		ExecEndNode(pstate);
 	}
 
 	/*
@@ -3030,13 +3028,17 @@ EvalPlanQualEnd(EPQState *epqstate)
 
 	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-	ExecEndNode(epqstate->recheckplanstate);
-
-	foreach(l, estate->es_subplanstates)
+	/*
+	 * Shut down the node-type-specific query processing for all nodes that
+	 * were initialized in InitPlan().  That includes the nodes in both the
+	 * main plan tree (epqstate->plan) and those in subplans
+	 * (es_plannedstmt->subplans).
+	 */
+	foreach(l, estate->es_planstate_nodes)
 	{
-		PlanState  *subplanstate = (PlanState *) lfirst(l);
+		PlanState  *planstate = (PlanState *) lfirst(l);
 
-		ExecEndNode(subplanstate);
+		ExecEndNode(planstate);
 	}
 
 	/* throw away the per-estate tuple table, some node may have used it */
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 4d288bc8d4..653f74cf58 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -1,11 +1,13 @@
 /*-------------------------------------------------------------------------
  *
  * execProcnode.c
- *	 contains dispatch functions which call the appropriate "initialize",
- *	 "get a tuple", and "cleanup" routines for the given node type.
- *	 If the node has children, then it will presumably call ExecInitNode,
- *	 ExecProcNode, or ExecEndNode on its subnodes and do the appropriate
- *	 processing.
+ *	 Contains dispatch functions ExecInitNode(), ExecProcNode(), and
+ *	 ExecEndNode(), which call the appropriate "initialize", "get a tuple",
+ *	 and "cleanup" routines, respectively, for the given node type.
+ *
+ *	 While the first two process the node's children recursively, ExecEndNode()
+ *	 is only concerned with the cleaning of the node itself while the children
+ *	 are processed by the caller.
  *
  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -49,7 +51,9 @@
  *		Eventually this calls ExecInitNode() on the right and left subplans
  *		and so forth until the entire plan is initialized.  The result
  *		of ExecInitNode() is a plan state tree built with the same structure
- *		as the underlying plan tree.
+ *		as the underlying plan tree.  (The plan state nodes are also added to
+ *		a list in the same order in which they are created for the final
+ *		cleanup processing.)
  *
  *	  * Then when ExecutorRun() is called, it calls ExecutePlan() which calls
  *		ExecProcNode() repeatedly on the top node of the plan state tree.
@@ -61,14 +65,10 @@
  *		form the tuples it returns.
  *
  *	  * Eventually ExecSeqScan() stops returning tuples and the nest
- *		loop join ends.  Lastly, ExecutorEnd() calls ExecEndNode() which
- *		calls ExecEndNestLoop() which in turn calls ExecEndNode() on
- *		its subplans which result in ExecEndSeqScan().
+ *		loop join ends.  Lastly, ExecutorEnd() calls ExecEndPlan(), which
+ *		in turn calls ExecEndNode() on all the nodes that were initialized:
+ *		the two Seq Scans and the Nest Loop in this case.
  *
- *		This should show how the executor works by having
- *		ExecInitNode(), ExecProcNode() and ExecEndNode() dispatch
- *		their work to the appropriate node support routines which may
- *		in turn call these routines themselves on their subplans.
  */
 #include "postgres.h"
 
@@ -136,6 +136,9 @@ static bool ExecShutdownNode_walker(PlanState *node, void *context);
  *		  'eflags' is a bitwise OR of flag bits described in executor.h
  *
  *		Returns a PlanState node corresponding to the given Plan node.
+ *
+ *		As a side-effect, all PlanState nodes that are created are appended to
+ *		estate->es_planstate_nodes for the cleanup processing in ExecEndPlan().
  * ------------------------------------------------------------------------
  */
 PlanState *
@@ -411,6 +414,10 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 		result->instrument = InstrAlloc(1, estate->es_instrument,
 										result->async_capable);
 
+	/* And remember for the cleanup processing in ExecEndPlan(). */
+	estate->es_planstate_nodes = lappend(estate->es_planstate_nodes,
+										 result);
+
 	return result;
 }
 
@@ -545,29 +552,18 @@ MultiExecProcNode(PlanState *node)
 /* ----------------------------------------------------------------
  *		ExecEndNode
  *
- *		Recursively cleans up all the nodes in the plan rooted
- *		at 'node'.
+ *		Cleans up node
  *
- *		After this operation, the query plan will not be able to be
- *		processed any further.  This should be called only after
- *		the query plan has been fully executed.
+ *		Unlike ExecInitNode(), this does not recurse into child nodes, because
+ *		they are processed separately.  So the ExecEnd* routine for any given
+ *		node type is only responsible for cleaning up its own resources.
  * ----------------------------------------------------------------
  */
 void
 ExecEndNode(PlanState *node)
 {
-	/*
-	 * do nothing when we get to the end of a leaf on tree.
-	 */
-	if (node == NULL)
-		return;
-
-	/*
-	 * Make sure there's enough stack available. Need to check here, in
-	 * addition to ExecProcNode() (via ExecProcNodeFirst()), because it's not
-	 * guaranteed that ExecProcNode() is reached for all nodes.
-	 */
-	check_stack_depth();
+	/* We only ever get called on nodes that were actually initialized. */
+	Assert(node != NULL);
 
 	if (node->chgParam != NULL)
 	{
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index c06b228858..b567165003 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -154,6 +154,8 @@ CreateExecutorState(void)
 
 	estate->es_exprcontexts = NIL;
 
+	estate->es_planstate_nodes = NIL;
+
 	estate->es_subplanstates = NIL;
 
 	estate->es_auxmodifytables = NIL;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 468db94fe5..e9d9ab6bdd 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -4304,7 +4304,6 @@ GetAggInitVal(Datum textInitVal, Oid transtype)
 void
 ExecEndAgg(AggState *node)
 {
-	PlanState  *outerPlan;
 	int			transno;
 	int			numGroupingSets = Max(node->maxsets, 1);
 	int			setno;
@@ -4367,8 +4366,7 @@ ExecEndAgg(AggState *node)
 	/* clean up tuple table */
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
 
-	outerPlan = outerPlanState(node);
-	ExecEndNode(outerPlan);
+	/* outerPlan is closely separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 609df6b9e6..9148d7d3b1 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -376,30 +376,12 @@ ExecAppend(PlanState *pstate)
 
 /* ----------------------------------------------------------------
  *		ExecEndAppend
- *
- *		Shuts down the subscans of the append node.
- *
- *		Returns nothing of interest.
  * ----------------------------------------------------------------
  */
 void
 ExecEndAppend(AppendState *node)
 {
-	PlanState **appendplans;
-	int			nplans;
-	int			i;
-
-	/*
-	 * get information from the node
-	 */
-	appendplans = node->appendplans;
-	nplans = node->as_nplans;
-
-	/*
-	 * shut down each of the subscans
-	 */
-	for (i = 0; i < nplans; i++)
-		ExecEndNode(appendplans[i]);
+	/* Nothing to do as the nodes in appendplans are closed separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeBitmapAnd.c b/src/backend/executor/nodeBitmapAnd.c
index 4c5eb2b23b..147592f7e2 100644
--- a/src/backend/executor/nodeBitmapAnd.c
+++ b/src/backend/executor/nodeBitmapAnd.c
@@ -168,33 +168,12 @@ MultiExecBitmapAnd(BitmapAndState *node)
 
 /* ----------------------------------------------------------------
  *		ExecEndBitmapAnd
- *
- *		Shuts down the subscans of the BitmapAnd node.
- *
- *		Returns nothing of interest.
  * ----------------------------------------------------------------
  */
 void
 ExecEndBitmapAnd(BitmapAndState *node)
 {
-	PlanState **bitmapplans;
-	int			nplans;
-	int			i;
-
-	/*
-	 * get information from the node
-	 */
-	bitmapplans = node->bitmapplans;
-	nplans = node->nplans;
-
-	/*
-	 * shut down each of the subscans (that we've initialized)
-	 */
-	for (i = 0; i < nplans; i++)
-	{
-		if (bitmapplans[i])
-			ExecEndNode(bitmapplans[i]);
-	}
+	/* Nothing to do as the nodes in bitmapplans are closed separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index f35df0b8bf..d58ee4f4e1 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -667,10 +667,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 		ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
 
-	/*
-	 * close down subplans
-	 */
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 
 	/*
 	 * release bitmaps and buffers if any
diff --git a/src/backend/executor/nodeBitmapOr.c b/src/backend/executor/nodeBitmapOr.c
index 0bf8af9652..736852a0ae 100644
--- a/src/backend/executor/nodeBitmapOr.c
+++ b/src/backend/executor/nodeBitmapOr.c
@@ -186,33 +186,12 @@ MultiExecBitmapOr(BitmapOrState *node)
 
 /* ----------------------------------------------------------------
  *		ExecEndBitmapOr
- *
- *		Shuts down the subscans of the BitmapOr node.
- *
- *		Returns nothing of interest.
  * ----------------------------------------------------------------
  */
 void
 ExecEndBitmapOr(BitmapOrState *node)
 {
-	PlanState **bitmapplans;
-	int			nplans;
-	int			i;
-
-	/*
-	 * get information from the node
-	 */
-	bitmapplans = node->bitmapplans;
-	nplans = node->nplans;
-
-	/*
-	 * shut down each of the subscans (that we've initialized)
-	 */
-	for (i = 0; i < nplans; i++)
-	{
-		if (bitmapplans[i])
-			ExecEndNode(bitmapplans[i]);
-	}
+	/* Nothing to do as the nodes in bitmapplans are closed separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index c2139acca0..e6616dd718 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -309,9 +309,7 @@ ExecEndForeignScan(ForeignScanState *node)
 	else
 		node->fdwroutine->EndForeignScan(node);
 
-	/* Shut down any outer plan. */
-	if (outerPlanState(node))
-		ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 
 	/* Free the exprcontext */
 	ExecFreeExprContext(&node->ss.ps);
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 307fc10eea..f7a69f185b 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -248,7 +248,7 @@ ExecGather(PlanState *pstate)
 void
 ExecEndGather(GatherState *node)
 {
-	ExecEndNode(outerPlanState(node));	/* let children clean up first */
+	/* outerPlan is closed separately. */
 	ExecShutdownGather(node);
 	ExecFreeExprContext(&node->ps);
 	if (node->ps.ps_ResultTupleSlot)
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 9d5e1a46e9..d357ff0c47 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -288,7 +288,7 @@ ExecGatherMerge(PlanState *pstate)
 void
 ExecEndGatherMerge(GatherMergeState *node)
 {
-	ExecEndNode(outerPlanState(node));	/* let children clean up first */
+	/* outerPlan is closed separately. */
 	ExecShutdownGatherMerge(node);
 	ExecFreeExprContext(&node->ps);
 	if (node->ps.ps_ResultTupleSlot)
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 25a1618952..2badcc7e60 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -226,15 +226,12 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
 void
 ExecEndGroup(GroupState *node)
 {
-	PlanState  *outerPlan;
-
 	ExecFreeExprContext(&node->ss.ps);
 
 	/* clean up tuple table */
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
 
-	outerPlan = outerPlanState(node);
-	ExecEndNode(outerPlan);
+	/* outerPlan is closed separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 8b5c35b82b..edd2324384 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -413,18 +413,12 @@ ExecInitHash(Hash *node, EState *estate, int eflags)
 void
 ExecEndHash(HashState *node)
 {
-	PlanState  *outerPlan;
-
 	/*
 	 * free exprcontext
 	 */
 	ExecFreeExprContext(&node->ps);
 
-	/*
-	 * shut down the subplan
-	 */
-	outerPlan = outerPlanState(node);
-	ExecEndNode(outerPlan);
+	/* outerPlan is closed separately. */
 }
 
 
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 980746128b..8078d7f229 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -879,11 +879,7 @@ ExecEndHashJoin(HashJoinState *node)
 	ExecClearTuple(node->hj_OuterTupleSlot);
 	ExecClearTuple(node->hj_HashTupleSlot);
 
-	/*
-	 * clean up subtrees
-	 */
-	ExecEndNode(outerPlanState(node));
-	ExecEndNode(innerPlanState(node));
+	/* outerPlan and innerPlan are closed separately. */
 }
 
 /*
diff --git a/src/backend/executor/nodeIncrementalSort.c b/src/backend/executor/nodeIncrementalSort.c
index 7683e3341c..52b146cfb8 100644
--- a/src/backend/executor/nodeIncrementalSort.c
+++ b/src/backend/executor/nodeIncrementalSort.c
@@ -1101,10 +1101,7 @@ ExecEndIncrementalSort(IncrementalSortState *node)
 		node->prefixsort_state = NULL;
 	}
 
-	/*
-	 * Shut down the subplan.
-	 */
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 
 	SO_printf("ExecEndIncrementalSort: sort node shutdown\n");
 }
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index 425fbfc405..a75099dd73 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -535,7 +535,7 @@ void
 ExecEndLimit(LimitState *node)
 {
 	ExecFreeExprContext(&node->ps);
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 }
 
 
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index e459971d32..55de8d3d65 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -386,7 +386,7 @@ ExecEndLockRows(LockRowsState *node)
 {
 	/* We may have shut down EPQ already, but no harm in another call */
 	EvalPlanQualEnd(&node->lr_epqstate);
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 }
 
 
diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c
index 09632678b0..ef04e9a8e7 100644
--- a/src/backend/executor/nodeMaterial.c
+++ b/src/backend/executor/nodeMaterial.c
@@ -251,10 +251,7 @@ ExecEndMaterial(MaterialState *node)
 		tuplestore_end(node->tuplestorestate);
 	node->tuplestorestate = NULL;
 
-	/*
-	 * shut down the subplan
-	 */
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeMemoize.c b/src/backend/executor/nodeMemoize.c
index 4f04269e26..61578d4b5c 100644
--- a/src/backend/executor/nodeMemoize.c
+++ b/src/backend/executor/nodeMemoize.c
@@ -1100,10 +1100,7 @@ ExecEndMemoize(MemoizeState *node)
 	 */
 	ExecFreeExprContext(&node->ss.ps);
 
-	/*
-	 * shut down the subplan
-	 */
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index 21b5726e6e..8aa64944c9 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -310,30 +310,12 @@ heap_compare_slots(Datum a, Datum b, void *arg)
 
 /* ----------------------------------------------------------------
  *		ExecEndMergeAppend
- *
- *		Shuts down the subscans of the MergeAppend node.
- *
- *		Returns nothing of interest.
  * ----------------------------------------------------------------
  */
 void
 ExecEndMergeAppend(MergeAppendState *node)
 {
-	PlanState **mergeplans;
-	int			nplans;
-	int			i;
-
-	/*
-	 * get information from the node
-	 */
-	mergeplans = node->mergeplans;
-	nplans = node->ms_nplans;
-
-	/*
-	 * shut down each of the subscans
-	 */
-	for (i = 0; i < nplans; i++)
-		ExecEndNode(mergeplans[i]);
+	/* Nothing to do as the nodes in mergeplans are closed separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 00f96d045e..7b530d9088 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -1654,11 +1654,7 @@ ExecEndMergeJoin(MergeJoinState *node)
 	ExecClearTuple(node->js.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->mj_MarkedTupleSlot);
 
-	/*
-	 * shut down the subplans
-	 */
-	ExecEndNode(innerPlanState(node));
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan and innerPlan are closed separately. */
 
 	MJ1_printf("ExecEndMergeJoin: %s\n",
 			   "node processing ended");
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2a5fec8d01..bdbaa4753b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -4397,7 +4397,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 /* ----------------------------------------------------------------
  *		ExecEndModifyTable
  *
- *		Shuts down the plan.
+ *		Releases ModifyTable resources.
  *
  *		Returns nothing of interest.
  * ----------------------------------------------------------------
@@ -4461,10 +4461,7 @@ ExecEndModifyTable(ModifyTableState *node)
 	 */
 	EvalPlanQualEnd(&node->mt_epqstate);
 
-	/*
-	 * shut down subplan
-	 */
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index b3d52e69ec..5cfb50a366 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -374,11 +374,7 @@ ExecEndNestLoop(NestLoopState *node)
 	 */
 	ExecClearTuple(node->js.ps.ps_ResultTupleSlot);
 
-	/*
-	 * close down subplans
-	 */
-	ExecEndNode(outerPlanState(node));
-	ExecEndNode(innerPlanState(node));
+	/* outerPlan and innerPlan are closed separately. */
 
 	NL1_printf("ExecEndNestLoop: %s\n",
 			   "node processing ended");
diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index f6ff3dc44c..4a388220ee 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -330,10 +330,7 @@ ExecEndProjectSet(ProjectSetState *node)
 	 */
 	ExecClearTuple(node->ps.ps_ResultTupleSlot);
 
-	/*
-	 * shut down subplans
-	 */
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c
index e781003934..aee31c7139 100644
--- a/src/backend/executor/nodeRecursiveunion.c
+++ b/src/backend/executor/nodeRecursiveunion.c
@@ -281,11 +281,7 @@ ExecEndRecursiveUnion(RecursiveUnionState *node)
 	if (node->tableContext)
 		MemoryContextDelete(node->tableContext);
 
-	/*
-	 * close down subplans
-	 */
-	ExecEndNode(outerPlanState(node));
-	ExecEndNode(innerPlanState(node));
+	/* outerPlan and innerPlan are closed separately. */
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index 4219712d30..a100b144be 100644
--- a/src/backend/executor/nodeResult.c
+++ b/src/backend/executor/nodeResult.c
@@ -250,10 +250,7 @@ ExecEndResult(ResultState *node)
 	 */
 	ExecClearTuple(node->ps.ps_ResultTupleSlot);
 
-	/*
-	 * shut down subplans
-	 */
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 }
 
 void
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
index 4bc2406b89..f7db9a3415 100644
--- a/src/backend/executor/nodeSetOp.c
+++ b/src/backend/executor/nodeSetOp.c
@@ -590,7 +590,7 @@ ExecEndSetOp(SetOpState *node)
 		MemoryContextDelete(node->tableContext);
 	ExecFreeExprContext(&node->ps);
 
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 }
 
 
diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c
index c6c72c6e67..078d041c40 100644
--- a/src/backend/executor/nodeSort.c
+++ b/src/backend/executor/nodeSort.c
@@ -317,10 +317,7 @@ ExecEndSort(SortState *node)
 		tuplesort_end((Tuplesortstate *) node->tuplesortstate);
 	node->tuplesortstate = NULL;
 
-	/*
-	 * shut down the subplan
-	 */
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 
 	SO1_printf("ExecEndSort: %s\n",
 			   "sort node shutdown");
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index 42471bfc04..bc55a82fc3 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -179,10 +179,7 @@ ExecEndSubqueryScan(SubqueryScanState *node)
 		ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
 
-	/*
-	 * close down subquery
-	 */
-	ExecEndNode(node->subplan);
+	/* subplan is closed separately. */
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c
index 45035d74fa..50babacdc8 100644
--- a/src/backend/executor/nodeUnique.c
+++ b/src/backend/executor/nodeUnique.c
@@ -173,7 +173,7 @@ ExecEndUnique(UniqueState *node)
 
 	ExecFreeExprContext(&node->ps);
 
-	ExecEndNode(outerPlanState(node));
+	/* outerPlan is closed separately. */
 }
 
 
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..648cdadc32 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2681,7 +2681,6 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 void
 ExecEndWindowAgg(WindowAggState *node)
 {
-	PlanState  *outerPlan;
 	int			i;
 
 	release_partition(node);
@@ -2714,8 +2713,7 @@ ExecEndWindowAgg(WindowAggState *node)
 	pfree(node->perfunc);
 	pfree(node->peragg);
 
-	outerPlan = outerPlanState(node);
-	ExecEndNode(outerPlan);
+	/* outerPlan is closed separately. */
 }
 
 /* -----------------
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..233fb6b4f9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -671,6 +671,8 @@ typedef struct EState
 
 	List	   *es_exprcontexts;	/* List of ExprContexts within EState */
 
+	List	   *es_planstate_nodes; /* "flat" list of PlanState nodes */
+
 	List	   *es_subplanstates;	/* List of PlanState for SubPlans */
 
 	List	   *es_auxmodifytables; /* List of secondary ModifyTableStates */
-- 
2.35.3