Skip to content

Commit 595d1ef

Browse files
committed
Make functions.c mostly run in a short-lived memory context.
Previously, much of this code ran with CurrentMemoryContext set to be the function's fcontext, so that we tended to leak a lot of stuff there. Commit 0dca5d6 dealt with that by releasing the fcontext at the completion of each SQL function call, but we'd like to go back to the previous approach of allowing the fcontext to be query-lifespan. To control the leakage problem, rearrange the code so that we mostly run in the memory context that fmgr_sql is called in (which we expect to be short-lived). Notably, this means that parsing/planning is all done in the short-lived context and doesn't leak cruft into fcontext. This patch also fixes the allocation of execution_state records so that we don't leak them across executions. I set that up with a re-usable array that contains at least as many execution_state structs as we need for the current querytree. The chain structure is still there, but it's not really doing much for us, and maybe somebody will be motivated to get rid of it. I'm not though. This incidentally also moves the call of BlessTupleDesc to be with the code that creates the JunkFilter. That doesn't make much difference now, but a later patch will reduce the number of times the JunkFilter gets made, and we needn't bless the results any more often than that. We still leak a fair amount in fcontext, particularly when executing utility statements, but that's material for a separate patch step; the point here is only to get rid of unintentional allocations in fcontext.
1 parent 09b07c2 commit 595d1ef

File tree

1 file changed

+95
-58
lines changed

1 file changed

+95
-58
lines changed

src/backend/executor/functions.c

Lines changed: 95 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ typedef struct
5555
*
5656
* The "next" fields chain together all the execution_state records generated
5757
* from a single original parsetree. (There will only be more than one in
58-
* case of rule expansion of the original parsetree.)
58+
* case of rule expansion of the original parsetree.) The chain structure is
59+
* quite vestigial at this point, because we allocate the records in an array
60+
* for ease of memory management. But we'll get rid of it some other day.
5961
*/
6062
typedef enum
6163
{
@@ -158,17 +160,20 @@ typedef struct SQLFunctionCache
158160

159161
/*
160162
* While executing a particular query within the function, cplan is the
161-
* CachedPlan we've obtained for that query, and eslist is a list of
163+
* CachedPlan we've obtained for that query, and eslist is a chain of
162164
* execution_state records for the individual plans within the CachedPlan.
163165
* next_query_index is the 0-based index of the next CachedPlanSource to
164166
* get a CachedPlan from.
165167
*/
166168
CachedPlan *cplan; /* Plan for current query, if any */
167169
ResourceOwner cowner; /* CachedPlan is registered with this owner */
168-
execution_state *eslist; /* execution_state records */
169170
int next_query_index; /* index of next CachedPlanSource to run */
170171

171-
/* if positive, this is the index of the query we're processing */
172+
execution_state *eslist; /* chain of execution_state records */
173+
execution_state *esarray; /* storage for eslist */
174+
int esarray_len; /* allocated length of esarray[] */
175+
176+
/* if positive, this is the 1-based index of the query we're processing */
172177
int error_query_index;
173178

174179
MemoryContext fcontext; /* memory context holding this struct and all
@@ -212,13 +217,12 @@ static void sql_delete_callback(CachedFunction *cfunc);
212217
static void sql_postrewrite_callback(List *querytree_list, void *arg);
213218
static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
214219
static bool postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache);
215-
static void postquel_end(execution_state *es);
220+
static void postquel_end(execution_state *es, SQLFunctionCachePtr fcache);
216221
static void postquel_sub_params(SQLFunctionCachePtr fcache,
217222
FunctionCallInfo fcinfo);
218223
static Datum postquel_get_single_result(TupleTableSlot *slot,
219224
FunctionCallInfo fcinfo,
220-
SQLFunctionCachePtr fcache,
221-
MemoryContext resultcontext);
225+
SQLFunctionCachePtr fcache);
222226
static void sql_compile_error_callback(void *arg);
223227
static void sql_exec_error_callback(void *arg);
224228
static void ShutdownSQLFunction(Datum arg);
@@ -658,12 +662,11 @@ init_execution_state(SQLFunctionCachePtr fcache)
658662
CachedPlanSource *plansource;
659663
execution_state *preves = NULL;
660664
execution_state *lasttages = NULL;
665+
int nstmts;
661666
ListCell *lc;
662667

663668
/*
664-
* Clean up after previous query, if there was one. Note that we just
665-
* leak the old execution_state records until end of function execution;
666-
* there aren't likely to be enough of them to matter.
669+
* Clean up after previous query, if there was one.
667670
*/
668671
if (fcache->cplan)
669672
{
@@ -704,6 +707,22 @@ init_execution_state(SQLFunctionCachePtr fcache)
704707
fcache->cowner,
705708
NULL);
706709

710+
/*
711+
* If necessary, make esarray[] bigger to hold the needed state.
712+
*/
713+
nstmts = list_length(fcache->cplan->stmt_list);
714+
if (nstmts > fcache->esarray_len)
715+
{
716+
if (fcache->esarray == NULL)
717+
fcache->esarray = (execution_state *)
718+
MemoryContextAlloc(fcache->fcontext,
719+
sizeof(execution_state) * nstmts);
720+
else
721+
fcache->esarray = repalloc_array(fcache->esarray,
722+
execution_state, nstmts);
723+
fcache->esarray_len = nstmts;
724+
}
725+
707726
/*
708727
* Build execution_state list to match the number of contained plans.
709728
*/
@@ -740,7 +759,7 @@ init_execution_state(SQLFunctionCachePtr fcache)
740759
CreateCommandName((Node *) stmt))));
741760

742761
/* OK, build the execution_state for this query */
743-
newes = (execution_state *) palloc(sizeof(execution_state));
762+
newes = &fcache->esarray[foreach_current_index(lc)];
744763
if (preves)
745764
preves->next = newes;
746765
else
@@ -775,9 +794,14 @@ init_execution_state(SQLFunctionCachePtr fcache)
775794
*/
776795
if (fcache->func->rettype != VOIDOID)
777796
{
778-
TupleTableSlot *slot = MakeSingleTupleTableSlot(NULL,
779-
&TTSOpsMinimalTuple);
797+
TupleTableSlot *slot;
780798
List *resulttlist;
799+
MemoryContext oldcontext;
800+
801+
/* The result slot and JunkFilter must be in the fcontext */
802+
oldcontext = MemoryContextSwitchTo(fcache->fcontext);
803+
804+
slot = MakeSingleTupleTableSlot(NULL, &TTSOpsMinimalTuple);
781805

782806
/*
783807
* Re-fetch the (possibly modified) output tlist of the final
@@ -811,14 +835,17 @@ init_execution_state(SQLFunctionCachePtr fcache)
811835
* pointer.
812836
*/
813837
fcache->junkFilter->jf_targetList = NIL;
814-
}
815838

816-
if (fcache->func->returnsTuple)
817-
{
818839
/* Make sure output rowtype is properly blessed */
819-
BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor);
840+
if (fcache->func->returnsTuple)
841+
BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor);
842+
843+
MemoryContextSwitchTo(oldcontext);
820844
}
821-
else if (fcache->func->returnsSet && type_is_rowtype(fcache->func->rettype))
845+
846+
if (fcache->func->returnsSet &&
847+
!fcache->func->returnsTuple &&
848+
type_is_rowtype(fcache->func->rettype))
822849
{
823850
/*
824851
* Returning rowtype as if it were scalar --- materialize won't work.
@@ -1230,12 +1257,23 @@ static void
12301257
postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
12311258
{
12321259
DestReceiver *dest;
1260+
MemoryContext oldcontext;
12331261

12341262
Assert(es->qd == NULL);
12351263

12361264
/* Caller should have ensured a suitable snapshot is active */
12371265
Assert(ActiveSnapshotSet());
12381266

1267+
/*
1268+
* Run the sub-executor in a child of fcontext. The sub-executor is
1269+
* responsible for deleting per-tuple information. (XXX in the case of a
1270+
* long-lived FmgrInfo, this policy potentially causes memory leakage, but
1271+
* it's not very clear where we could keep stuff instead. Fortunately,
1272+
* there are few if any cases where set-returning functions are invoked
1273+
* via FmgrInfos that would outlive the calling query.)
1274+
*/
1275+
oldcontext = MemoryContextSwitchTo(fcache->fcontext);
1276+
12391277
/*
12401278
* If this query produces the function result, send its output to the
12411279
* tuplestore; else discard any output.
@@ -1285,6 +1323,8 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
12851323
}
12861324

12871325
es->status = F_EXEC_RUN;
1326+
1327+
MemoryContextSwitchTo(oldcontext);
12881328
}
12891329

12901330
/* Run one execution_state; either to completion or to first result row */
@@ -1293,6 +1333,10 @@ static bool
12931333
postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
12941334
{
12951335
bool result;
1336+
MemoryContext oldcontext;
1337+
1338+
/* Run the sub-executor in a child of fcontext */
1339+
oldcontext = MemoryContextSwitchTo(fcache->fcontext);
12961340

12971341
if (es->qd->operation == CMD_UTILITY)
12981342
{
@@ -1320,13 +1364,20 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
13201364
result = (count == 0 || es->qd->estate->es_processed == 0);
13211365
}
13221366

1367+
MemoryContextSwitchTo(oldcontext);
1368+
13231369
return result;
13241370
}
13251371

13261372
/* Shut down execution of one execution_state node */
13271373
static void
1328-
postquel_end(execution_state *es)
1374+
postquel_end(execution_state *es, SQLFunctionCachePtr fcache)
13291375
{
1376+
MemoryContext oldcontext;
1377+
1378+
/* Run the sub-executor in a child of fcontext */
1379+
oldcontext = MemoryContextSwitchTo(fcache->fcontext);
1380+
13301381
/* mark status done to ensure we don't do ExecutorEnd twice */
13311382
es->status = F_EXEC_DONE;
13321383

@@ -1341,9 +1392,16 @@ postquel_end(execution_state *es)
13411392

13421393
FreeQueryDesc(es->qd);
13431394
es->qd = NULL;
1395+
1396+
MemoryContextSwitchTo(oldcontext);
13441397
}
13451398

1346-
/* Build ParamListInfo array representing current arguments */
1399+
/*
1400+
* Build ParamListInfo array representing current arguments.
1401+
*
1402+
* This must be called in the fcontext so that the results have adequate
1403+
* lifespan.
1404+
*/
13471405
static void
13481406
postquel_sub_params(SQLFunctionCachePtr fcache,
13491407
FunctionCallInfo fcinfo)
@@ -1398,25 +1456,22 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
13981456
/*
13991457
* Extract the SQL function's value from a single result row. This is used
14001458
* both for scalar (non-set) functions and for each row of a lazy-eval set
1401-
* result.
1459+
* result. We expect the current memory context to be that of the caller
1460+
* of fmgr_sql.
14021461
*/
14031462
static Datum
14041463
postquel_get_single_result(TupleTableSlot *slot,
14051464
FunctionCallInfo fcinfo,
1406-
SQLFunctionCachePtr fcache,
1407-
MemoryContext resultcontext)
1465+
SQLFunctionCachePtr fcache)
14081466
{
14091467
Datum value;
1410-
MemoryContext oldcontext;
14111468

14121469
/*
14131470
* Set up to return the function value. For pass-by-reference datatypes,
1414-
* be sure to allocate the result in resultcontext, not the current memory
1415-
* context (which has query lifespan). We can't leave the data in the
1416-
* TupleTableSlot because we intend to clear the slot before returning.
1471+
* be sure to copy the result into the current context. We can't leave
1472+
* the data in the TupleTableSlot because we intend to clear the slot
1473+
* before returning.
14171474
*/
1418-
oldcontext = MemoryContextSwitchTo(resultcontext);
1419-
14201475
if (fcache->func->returnsTuple)
14211476
{
14221477
/* We must return the whole tuple as a Datum. */
@@ -1427,16 +1482,14 @@ postquel_get_single_result(TupleTableSlot *slot,
14271482
{
14281483
/*
14291484
* Returning a scalar, which we have to extract from the first column
1430-
* of the SELECT result, and then copy into result context if needed.
1485+
* of the SELECT result, and then copy into current context if needed.
14311486
*/
14321487
value = slot_getattr(slot, 1, &(fcinfo->isnull));
14331488

14341489
if (!fcinfo->isnull)
14351490
value = datumCopy(value, fcache->func->typbyval, fcache->func->typlen);
14361491
}
14371492

1438-
MemoryContextSwitchTo(oldcontext);
1439-
14401493
return value;
14411494
}
14421495

@@ -1450,7 +1503,6 @@ fmgr_sql(PG_FUNCTION_ARGS)
14501503
SQLFunctionLink *flink;
14511504
ErrorContextCallback sqlerrcontext;
14521505
MemoryContext tscontext;
1453-
MemoryContext oldcontext;
14541506
bool randomAccess;
14551507
bool lazyEvalOK;
14561508
bool pushed_snapshot;
@@ -1507,20 +1559,14 @@ fmgr_sql(PG_FUNCTION_ARGS)
15071559
* Build tuplestore to hold results, if we don't have one already. Make
15081560
* sure it's in a suitable context.
15091561
*/
1510-
oldcontext = MemoryContextSwitchTo(tscontext);
1511-
15121562
if (!fcache->tstore)
1513-
fcache->tstore = tuplestore_begin_heap(randomAccess, false, work_mem);
1563+
{
1564+
MemoryContext oldcontext;
15141565

1515-
/*
1516-
* Switch to context in which the fcache lives. The sub-executor is
1517-
* responsible for deleting per-tuple information. (XXX in the case of a
1518-
* long-lived FmgrInfo, this policy potentially causes memory leakage, but
1519-
* it's not very clear where we could keep stuff instead. Fortunately,
1520-
* there are few if any cases where set-returning functions are invoked
1521-
* via FmgrInfos that would outlive the calling query.)
1522-
*/
1523-
MemoryContextSwitchTo(fcache->fcontext);
1566+
oldcontext = MemoryContextSwitchTo(tscontext);
1567+
fcache->tstore = tuplestore_begin_heap(randomAccess, false, work_mem);
1568+
MemoryContextSwitchTo(oldcontext);
1569+
}
15241570

15251571
/*
15261572
* Find first unfinished execution_state. If none, advance to the next
@@ -1598,7 +1644,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
15981644
* don't care about fetching any more result rows.
15991645
*/
16001646
if (completed || !fcache->func->returnsSet)
1601-
postquel_end(es);
1647+
postquel_end(es, fcache);
16021648

16031649
/*
16041650
* Break from loop if we didn't shut down (implying we got a
@@ -1657,8 +1703,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
16571703
if (!tuplestore_gettupleslot(fcache->tstore, true, false, slot))
16581704
elog(ERROR, "failed to fetch lazy-eval tuple");
16591705
/* Extract the result as a datum, and copy out from the slot */
1660-
result = postquel_get_single_result(slot, fcinfo,
1661-
fcache, oldcontext);
1706+
result = postquel_get_single_result(slot, fcinfo, fcache);
16621707
/* Clear the tuplestore, but keep it for next time */
16631708
/* NB: this might delete the slot's content, but we don't care */
16641709
tuplestore_clear(fcache->tstore);
@@ -1716,12 +1761,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
17161761
fcache->tstore = NULL;
17171762
/* must copy desc because execSRF.c will free it */
17181763
if (fcache->junkFilter)
1719-
{
1720-
/* setDesc must be allocated in suitable context */
1721-
MemoryContextSwitchTo(tscontext);
17221764
rsi->setDesc = CreateTupleDescCopy(fcache->junkFilter->jf_cleanTupType);
1723-
MemoryContextSwitchTo(fcache->fcontext);
1724-
}
17251765

17261766
fcinfo->isnull = true;
17271767
result = (Datum) 0;
@@ -1746,8 +1786,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
17461786
/* Re-use the junkfilter's output slot to fetch back the tuple */
17471787
slot = fcache->junkFilter->jf_resultSlot;
17481788
if (tuplestore_gettupleslot(fcache->tstore, true, false, slot))
1749-
result = postquel_get_single_result(slot, fcinfo,
1750-
fcache, oldcontext);
1789+
result = postquel_get_single_result(slot, fcinfo, fcache);
17511790
else
17521791
{
17531792
fcinfo->isnull = true;
@@ -1770,8 +1809,6 @@ fmgr_sql(PG_FUNCTION_ARGS)
17701809
if (pushed_snapshot)
17711810
PopActiveSnapshot();
17721811

1773-
MemoryContextSwitchTo(oldcontext);
1774-
17751812
/*
17761813
* If we've gone through every command in the function, we are done.
17771814
* Release the cache to start over again on next call.
@@ -1889,7 +1926,7 @@ ShutdownSQLFunction(Datum arg)
18891926
if (!fcache->func->readonly_func)
18901927
PushActiveSnapshot(es->qd->snapshot);
18911928

1892-
postquel_end(es);
1929+
postquel_end(es, fcache);
18931930

18941931
if (!fcache->func->readonly_func)
18951932
PopActiveSnapshot();

0 commit comments

Comments
 (0)