1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-18 13:44:19 +03:00

Split some storage out to separate subcontexts of fcontext.

Put the JunkFilter and its result slot (and thence also
some subsidiary data such as the result tupledesc) into a
separate subcontext "jfcontext".  This doesn't accomplish
a lot at this point, because we make a new JunkFilter each
time through the SQL function.  However, the plan is to make
the fcontext long-lived, and that raises the possibility
that we'll need a new JunkFilter because the plan for the
result-generating query changes.  A separate context makes
it easy to free the obsoleted data when that happens.

Also, instead of always running the sub-executor in fcontext,
make a separate context for it if we're doing lazy eval of
a SRF, and otherwise just run it inside CurrentMemoryContext.
This commit is contained in:
Tom Lane 2025-04-17 12:56:21 -04:00
parent 595d1efeda
commit f45a5444ee

View File

@ -107,6 +107,8 @@ typedef struct execution_state
* which we free at completion. In non-returnsSet mode, this is just a child * which we free at completion. In non-returnsSet mode, this is just a child
* of the call-time context. In returnsSet mode, it is made a child of the * of the call-time context. In returnsSet mode, it is made a child of the
* FmgrInfo's fn_mcxt so that it will survive between fmgr_sql calls. * FmgrInfo's fn_mcxt so that it will survive between fmgr_sql calls.
* The fcontext may have subsidiary contexts jfcontext and/or subcontext,
* which have somewhat shorter lifespans.
* *
* 3. SQLFunctionLink is a tiny struct that just holds pointers to * 3. SQLFunctionLink is a tiny struct that just holds pointers to
* the SQLFunctionHashEntry and the current SQLFunctionCache (if any). * the SQLFunctionHashEntry and the current SQLFunctionCache (if any).
@ -151,6 +153,7 @@ typedef struct SQLFunctionCache
bool lazyEvalOK; /* true if lazyEval is safe */ bool lazyEvalOK; /* true if lazyEval is safe */
bool shutdown_reg; /* true if registered shutdown callback */ bool shutdown_reg; /* true if registered shutdown callback */
bool lazyEval; /* true if using lazyEval for result query */ bool lazyEval; /* true if using lazyEval for result query */
bool ownSubcontext; /* is subcontext really a separate context? */
ParamListInfo paramLI; /* Param list representing current args */ ParamListInfo paramLI; /* Param list representing current args */
@ -178,6 +181,9 @@ typedef struct SQLFunctionCache
MemoryContext fcontext; /* memory context holding this struct and all MemoryContext fcontext; /* memory context holding this struct and all
* subsidiary data */ * subsidiary data */
MemoryContext jfcontext; /* subsidiary memory context holding
* junkFilter, result slot, and related data */
MemoryContext subcontext; /* subsidiary memory context for sub-executor */
} SQLFunctionCache; } SQLFunctionCache;
typedef SQLFunctionCache *SQLFunctionCachePtr; typedef SQLFunctionCache *SQLFunctionCachePtr;
@ -617,8 +623,8 @@ init_sql_fcache(FunctionCallInfo fcinfo, bool lazyEvalOK)
*/ */
pcontext = func->returnsSet ? finfo->fn_mcxt : CurrentMemoryContext; pcontext = func->returnsSet ? finfo->fn_mcxt : CurrentMemoryContext;
fcontext = AllocSetContextCreate(pcontext, fcontext = AllocSetContextCreate(pcontext,
"SQL function execution", "SQL function cache",
ALLOCSET_DEFAULT_SIZES); ALLOCSET_SMALL_SIZES);
oldcontext = MemoryContextSwitchTo(fcontext); oldcontext = MemoryContextSwitchTo(fcontext);
@ -791,6 +797,9 @@ init_execution_state(SQLFunctionCachePtr fcache)
* nothing to coerce to. (XXX Frequently, the JunkFilter isn't doing * nothing to coerce to. (XXX Frequently, the JunkFilter isn't doing
* anything very interesting, but much of this module expects it to be * anything very interesting, but much of this module expects it to be
* there anyway.) * there anyway.)
*
* The JunkFilter, its result slot, and its tupledesc are kept in a
* subsidiary memory context so that we can free them easily when needed.
*/ */
if (fcache->func->rettype != VOIDOID) if (fcache->func->rettype != VOIDOID)
{ {
@ -798,8 +807,14 @@ init_execution_state(SQLFunctionCachePtr fcache)
List *resulttlist; List *resulttlist;
MemoryContext oldcontext; MemoryContext oldcontext;
/* The result slot and JunkFilter must be in the fcontext */ /* Create or reset the jfcontext */
oldcontext = MemoryContextSwitchTo(fcache->fcontext); if (fcache->jfcontext == NULL)
fcache->jfcontext = AllocSetContextCreate(fcache->fcontext,
"SQL function junkfilter",
ALLOCSET_SMALL_SIZES);
else
MemoryContextReset(fcache->jfcontext);
oldcontext = MemoryContextSwitchTo(fcache->jfcontext);
slot = MakeSingleTupleTableSlot(NULL, &TTSOpsMinimalTuple); slot = MakeSingleTupleTableSlot(NULL, &TTSOpsMinimalTuple);
@ -1265,14 +1280,46 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
Assert(ActiveSnapshotSet()); Assert(ActiveSnapshotSet());
/* /*
* Run the sub-executor in a child of fcontext. The sub-executor is * In lazyEval mode for a SRF, we must run the sub-executor in a child of
* responsible for deleting per-tuple information. (XXX in the case of a * fcontext, so that it can survive across multiple calls to fmgr_sql.
* long-lived FmgrInfo, this policy potentially causes memory leakage, but * (XXX in the case of a long-lived FmgrInfo, this policy potentially
* it's not very clear where we could keep stuff instead. Fortunately, * causes memory leakage, but it's not very clear where we could keep
* there are few if any cases where set-returning functions are invoked * stuff instead. Fortunately, there are few if any cases where
* via FmgrInfos that would outlive the calling query.) * set-returning functions are invoked via FmgrInfos that would outlive
* the calling query.) Otherwise, we're going to run it to completion
* before exiting fmgr_sql, so it can perfectly well run in the caller's
* context.
*/ */
oldcontext = MemoryContextSwitchTo(fcache->fcontext); if (es->lazyEval && fcache->func->returnsSet)
{
fcache->subcontext = AllocSetContextCreate(fcache->fcontext,
"SQL function execution",
ALLOCSET_DEFAULT_SIZES);
fcache->ownSubcontext = true;
}
else if (es->stmt->commandType == CMD_UTILITY)
{
/*
* The code path using a sub-executor is pretty good about cleaning up
* cruft, since the executor will make its own sub-context. We don't
* really need an additional layer of sub-context in that case.
* However, if this is a utility statement, it won't make its own
* sub-context, so it seems advisable to make one that we can free on
* completion.
*/
fcache->subcontext = AllocSetContextCreate(CurrentMemoryContext,
"SQL function execution",
ALLOCSET_DEFAULT_SIZES);
fcache->ownSubcontext = true;
}
else
{
fcache->subcontext = CurrentMemoryContext;
fcache->ownSubcontext = false;
}
/* Switch into the selected subcontext (might be a no-op) */
oldcontext = MemoryContextSwitchTo(fcache->subcontext);
/* /*
* If this query produces the function result, send its output to the * If this query produces the function result, send its output to the
@ -1335,8 +1382,8 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
bool result; bool result;
MemoryContext oldcontext; MemoryContext oldcontext;
/* Run the sub-executor in a child of fcontext */ /* Run the sub-executor in subcontext */
oldcontext = MemoryContextSwitchTo(fcache->fcontext); oldcontext = MemoryContextSwitchTo(fcache->subcontext);
if (es->qd->operation == CMD_UTILITY) if (es->qd->operation == CMD_UTILITY)
{ {
@ -1375,8 +1422,8 @@ postquel_end(execution_state *es, SQLFunctionCachePtr fcache)
{ {
MemoryContext oldcontext; MemoryContext oldcontext;
/* Run the sub-executor in a child of fcontext */ /* Run the sub-executor in subcontext */
oldcontext = MemoryContextSwitchTo(fcache->fcontext); oldcontext = MemoryContextSwitchTo(fcache->subcontext);
/* mark status done to ensure we don't do ExecutorEnd twice */ /* mark status done to ensure we don't do ExecutorEnd twice */
es->status = F_EXEC_DONE; es->status = F_EXEC_DONE;
@ -1394,6 +1441,11 @@ postquel_end(execution_state *es, SQLFunctionCachePtr fcache)
es->qd = NULL; es->qd = NULL;
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
/* Delete the subcontext, if it's actually a separate context */
if (fcache->ownSubcontext)
MemoryContextDelete(fcache->subcontext);
fcache->subcontext = NULL;
} }
/* /*