1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-29 10:41:53 +03:00

Revise executor APIs so that all per-query state structure is built in

a per-query memory context created by CreateExecutorState --- and destroyed
by FreeExecutorState.  This provides a final solution to the longstanding
problem of memory leaked by various ExecEndNode calls.
This commit is contained in:
Tom Lane
2002-12-15 16:17:59 +00:00
parent 90b3a0b6fd
commit 5bab36e9f6
42 changed files with 806 additions and 547 deletions

View File

@ -8,13 +8,20 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.92 2002/12/13 19:45:52 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.93 2002/12/15 16:17:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* INTERFACE ROUTINES
* CreateExecutorState Create/delete executor working state
* FreeExecutorState
* CreateExprContext
* FreeExprContext
*
* ExecAssignExprContext Common code for plan node init routines.
* ExecAssignResultType
* etc
*
* ExecOpenIndices \
* ExecCloseIndices | referenced by InitPlan, EndPlan,
@ -26,7 +33,6 @@
* NOTES
* This file has traditionally been the place to stick misc.
* executor support stuff that doesn't really go anyplace else.
*
*/
#include "postgres.h"
@ -64,6 +70,7 @@ extern int NIndexTupleProcessed; /* have to be defined in the
static void ShutdownExprContext(ExprContext *econtext);
/* ----------------------------------------------------------------
* statistic functions
* ----------------------------------------------------------------
@ -124,136 +131,263 @@ DisplayTupleCount(FILE *statfp)
}
#endif
/* ----------------------------------------------------------------
* miscellaneous node-init support functions
* Executor state and memory management functions
* ----------------------------------------------------------------
*/
/* ----------------
* ExecAssignExprContext
* CreateExecutorState
*
* This initializes the ExprContext field. It is only necessary
* to do this for nodes which use ExecQual or ExecProject
* because those routines depend on econtext. Other nodes that
* don't have to evaluate expressions don't need to do this.
* Create and initialize an EState node, which is the root of
* working storage for an entire Executor invocation.
*
* Note: we assume CurrentMemoryContext is the correct per-query context.
* This should be true during plan node initialization.
* Principally, this creates the per-query memory context that will be
* used to hold all working data that lives till the end of the query.
* Note that the per-query context will become a child of the caller's
* CurrentMemoryContext.
* ----------------
*/
EState *
CreateExecutorState(void)
{
EState *estate;
MemoryContext qcontext;
MemoryContext oldcontext;
/*
* Create the per-query context for this Executor run.
*/
qcontext = AllocSetContextCreate(CurrentMemoryContext,
"ExecutorState",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/*
* Make the EState node within the per-query context. This way,
* we don't need a separate pfree() operation for it at shutdown.
*/
oldcontext = MemoryContextSwitchTo(qcontext);
estate = makeNode(EState);
/*
* Initialize all fields of the Executor State structure
*/
estate->es_direction = ForwardScanDirection;
estate->es_snapshot = SnapshotNow;
estate->es_range_table = NIL;
estate->es_result_relations = NULL;
estate->es_num_result_relations = 0;
estate->es_result_relation_info = NULL;
estate->es_junkFilter = NULL;
estate->es_into_relation_descriptor = NULL;
estate->es_param_list_info = NULL;
estate->es_param_exec_vals = NULL;
estate->es_query_cxt = qcontext;
estate->es_tupleTable = NULL;
estate->es_processed = 0;
estate->es_lastoid = InvalidOid;
estate->es_rowMark = NIL;
estate->es_instrument = false;
estate->es_exprcontexts = NIL;
estate->es_per_tuple_exprcontext = NULL;
estate->es_origPlan = NULL;
estate->es_evalPlanQual = NULL;
estate->es_evTupleNull = NULL;
estate->es_evTuple = NULL;
estate->es_useEvalPlan = false;
/*
* Return the executor state structure
*/
MemoryContextSwitchTo(oldcontext);
return estate;
}
/* ----------------
* FreeExecutorState
*
* Release an EState along with all remaining working storage.
*
* Note: this is not responsible for releasing non-memory resources,
* such as open relations or buffer pins. But it will shut down any
* still-active ExprContexts within the EState. That is sufficient
* cleanup for situations where the EState has only been used for expression
* evaluation, and not to run a complete Plan.
*
* This can be called in any memory context ... so long as it's not one
* of the ones to be freed.
* ----------------
*/
void
ExecAssignExprContext(EState *estate, PlanState *planstate)
FreeExecutorState(EState *estate)
{
ExprContext *econtext = makeNode(ExprContext);
/*
* Shut down and free any remaining ExprContexts. We do this
* explicitly to ensure that any remaining shutdown callbacks get
* called (since they might need to release resources that aren't
* simply memory within the per-query memory context).
*/
while (estate->es_exprcontexts)
{
FreeExprContext((ExprContext *) lfirst(estate->es_exprcontexts));
/* FreeExprContext removed the list link for us */
}
/*
* Free the per-query memory context, thereby releasing all working
* memory, including the EState node itself.
*/
MemoryContextDelete(estate->es_query_cxt);
}
/* ----------------
* CreateExprContext
*
* Create a context for expression evaluation within an EState.
*
* An executor run may require multiple ExprContexts (we usually make one
* for each Plan node, and a separate one for per-output-tuple processing
* such as constraint checking). Each ExprContext has its own "per-tuple"
* memory context.
*
* Note we make no assumption about the caller's memory context.
* ----------------
*/
ExprContext *
CreateExprContext(EState *estate)
{
ExprContext *econtext;
MemoryContext oldcontext;
/* Create the ExprContext node within the per-query memory context */
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
econtext = makeNode(ExprContext);
/* Initialize fields of ExprContext */
econtext->ecxt_scantuple = NULL;
econtext->ecxt_innertuple = NULL;
econtext->ecxt_outertuple = NULL;
econtext->ecxt_per_query_memory = CurrentMemoryContext;
econtext->ecxt_per_query_memory = estate->es_query_cxt;
/*
* Create working memory for expression evaluation in this context.
*/
econtext->ecxt_per_tuple_memory =
AllocSetContextCreate(CurrentMemoryContext,
"PlanExprContext",
AllocSetContextCreate(estate->es_query_cxt,
"ExprContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
econtext->ecxt_param_exec_vals = estate->es_param_exec_vals;
econtext->ecxt_param_list_info = estate->es_param_list_info;
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
econtext->domainValue_datum = (Datum) 0;
econtext->domainValue_isNull = true;
econtext->ecxt_estate = estate;
econtext->ecxt_callbacks = NULL;
planstate->ps_ExprContext = econtext;
}
/* ----------------
* MakeExprContext
*
* Build an expression context for use outside normal plan-node cases.
* A fake scan-tuple slot can be supplied (pass NULL if not needed).
* A memory context sufficiently long-lived to use as fcache context
* must be supplied as well.
* ----------------
*/
ExprContext *
MakeExprContext(TupleTableSlot *slot,
MemoryContext queryContext)
{
ExprContext *econtext = makeNode(ExprContext);
econtext->ecxt_scantuple = slot;
econtext->ecxt_innertuple = NULL;
econtext->ecxt_outertuple = NULL;
econtext->ecxt_per_query_memory = queryContext;
/*
* We make the temporary context a child of current working context,
* not of the specified queryContext. This seems reasonable but I'm
* not totally sure about it...
*
* Expression contexts made via this routine typically don't live long
* enough to get reset, so specify a minsize of 0. That avoids
* alloc'ing any memory in the common case where expr eval doesn't use
* any.
* Link the ExprContext into the EState to ensure it is shut down
* when the EState is freed. Because we use lcons(), shutdowns will
* occur in reverse order of creation, which may not be essential
* but can't hurt.
*/
econtext->ecxt_per_tuple_memory =
AllocSetContextCreate(CurrentMemoryContext,
"TempExprContext",
0,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
econtext->ecxt_param_exec_vals = NULL;
econtext->ecxt_param_list_info = NULL;
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
econtext->ecxt_callbacks = NULL;
estate->es_exprcontexts = lcons(econtext, estate->es_exprcontexts);
MemoryContextSwitchTo(oldcontext);
return econtext;
}
/*
* Free an ExprContext made by MakeExprContext, including the temporary
* context used for expression evaluation. Note this will cause any
* pass-by-reference expression result to go away!
/* ----------------
* FreeExprContext
*
* Free an expression context, including calling any remaining
* shutdown callbacks.
*
* Since we free the temporary context used for expression evaluation,
* any previously computed pass-by-reference expression result will go away!
*
* Note we make no assumption about the caller's memory context.
* ----------------
*/
void
FreeExprContext(ExprContext *econtext)
{
EState *estate;
/* Call any registered callbacks */
ShutdownExprContext(econtext);
/* And clean up the memory used */
MemoryContextDelete(econtext->ecxt_per_tuple_memory);
/* Unlink self from owning EState */
estate = econtext->ecxt_estate;
estate->es_exprcontexts = lremove(econtext, estate->es_exprcontexts);
/* And delete the ExprContext node */
pfree(econtext);
}
/*
* Build a per-output-tuple ExprContext for an EState.
*
* This is normally invoked via GetPerTupleExprContext() macro.
* This is normally invoked via GetPerTupleExprContext() macro,
* not directly.
*/
ExprContext *
MakePerTupleExprContext(EState *estate)
{
if (estate->es_per_tuple_exprcontext == NULL)
{
MemoryContext oldContext;
estate->es_per_tuple_exprcontext = CreateExprContext(estate);
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
estate->es_per_tuple_exprcontext =
MakeExprContext(NULL, estate->es_query_cxt);
MemoryContextSwitchTo(oldContext);
}
return estate->es_per_tuple_exprcontext;
}
/* ----------------------------------------------------------------
* Result slot tuple type and ProjectionInfo support
* miscellaneous node-init support functions
*
* Note: all of these are expected to be called with CurrentMemoryContext
* equal to the per-query memory context.
* ----------------------------------------------------------------
*/
/* ----------------
* ExecAssignExprContext
*
* This initializes the ps_ExprContext field. It is only necessary
* to do this for nodes which use ExecQual or ExecProject
* because those routines require an econtext. Other nodes that
* don't have to evaluate expressions don't need to do this.
* ----------------
*/
void
ExecAssignExprContext(EState *estate, PlanState *planstate)
{
planstate->ps_ExprContext = CreateExprContext(estate);
}
/* ----------------
* ExecAssignResultType
* ----------------
@ -367,35 +501,13 @@ ExecAssignProjectionInfo(PlanState *planstate)
}
/* ----------------
* ExecFreeProjectionInfo
* ----------------
*/
void
ExecFreeProjectionInfo(PlanState *planstate)
{
ProjectionInfo *projInfo;
/*
* get projection info. if NULL then this node has none so we just
* return.
*/
projInfo = planstate->ps_ProjInfo;
if (projInfo == NULL)
return;
/*
* clean up memory used.
*/
if (projInfo->pi_tupValue != NULL)
pfree(projInfo->pi_tupValue);
pfree(projInfo);
planstate->ps_ProjInfo = NULL;
}
/* ----------------
* ExecFreeExprContext
*
* A plan node's ExprContext should be freed explicitly during ExecEndNode
* because there may be shutdown callbacks to call. (Other resources made
* by the above routines, such as projection info, don't need to be freed
* explicitly because they're just memory in the per-query memory context.)
* ----------------
*/
void
@ -411,16 +523,8 @@ ExecFreeExprContext(PlanState *planstate)
if (econtext == NULL)
return;
/*
* clean up any registered callbacks
*/
ShutdownExprContext(econtext);
FreeExprContext(econtext);
/*
* clean up memory used.
*/
MemoryContextDelete(econtext->ecxt_per_tuple_memory);
pfree(econtext);
planstate->ps_ExprContext = NULL;
}
@ -612,7 +716,8 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
}
/*
* XXX should free indexInfo array here too.
* XXX should free indexInfo array here too? Currently we assume that
* such stuff will be cleaned up automatically in FreeExecutorState.
*/
}
@ -674,16 +779,31 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
for (i = 0; i < numIndices; i++)
{
IndexInfo *indexInfo;
List *predicate;
InsertIndexResult result;
if (relationDescs[i] == NULL)
continue;
indexInfo = indexInfoArray[i];
predicate = indexInfo->ii_PredicateState;
if (predicate != NIL)
/* Check for partial index */
if (indexInfo->ii_Predicate != NIL)
{
List *predicate;
/*
* If predicate state not set up yet, create it (in the
* estate's per-query context)
*/
predicate = indexInfo->ii_PredicateState;
if (predicate == NIL)
{
predicate = (List *)
ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
estate);
indexInfo->ii_PredicateState = predicate;
}
/* Skip this index-update if the predicate isn't satisfied */
if (!ExecQual(predicate, econtext, false))
continue;
@ -811,6 +931,17 @@ static void
ShutdownExprContext(ExprContext *econtext)
{
ExprContext_CB *ecxt_callback;
MemoryContext oldcontext;
/* Fast path in normal case where there's nothing to do. */
if (econtext->ecxt_callbacks == NULL)
return;
/*
* Call the callbacks in econtext's per-tuple context. This ensures
* that any memory they might leak will get cleaned up.
*/
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
/*
* Call each callback function in reverse registration order.
@ -821,4 +952,6 @@ ShutdownExprContext(ExprContext *econtext)
(*ecxt_callback->function) (ecxt_callback->arg);
pfree(ecxt_callback);
}
MemoryContextSwitchTo(oldcontext);
}