From 07172d5aff8f43cd6ce09f57a0b56a535d7eaf45 Mon Sep 17 00:00:00 2001 From: Andrew Gierth Date: Mon, 13 Aug 2018 01:45:35 +0100 Subject: [PATCH] Avoid query-lifetime memory leaks in XMLTABLE (bug #15321) Multiple calls to XMLTABLE in a query (e.g. laterally applying it to a table with an xml column, an important use-case) were leaking large amounts of memory into the per-query context, blowing up memory usage. Repair by reorganizing memory context usage in nodeTableFuncscan; use the usual per-tuple context for row-by-row evaluations instead of perValueCxt, and use the explicitly created context -- renamed from perValueCxt to perTableCxt -- for arguments and state for each individual table-generation operation. Backpatch to PG10 where this code was introduced. Original report by IRC user begriffs; analysis and patch by me. Reviewed by Tom Lane and Pavel Stehule. Discussion: https://postgr.es/m/153394403528.10284.7530399040974170549@wrigleys.postgresql.org --- src/backend/executor/nodeTableFuncscan.c | 29 ++++++++++++++++++++---- src/include/nodes/execnodes.h | 2 +- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c index fed6f2b3a5..abbf0e4d44 100644 --- a/src/backend/executor/nodeTableFuncscan.c +++ b/src/backend/executor/nodeTableFuncscan.c @@ -164,7 +164,7 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags) /* Only XMLTABLE is supported currently */ scanstate->routine = &XmlTableRoutine; - scanstate->perValueCxt = + scanstate->perTableCxt = AllocSetContextCreate(CurrentMemoryContext, "TableFunc per value context", ALLOCSET_DEFAULT_SIZES); @@ -282,6 +282,16 @@ tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext) oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); tstate->tupstore = tuplestore_begin_heap(false, false, work_mem); + /* + * Each call to fetch a new set of rows - of which there may be very many + * if XMLTABLE is being used in a lateral join - will allocate a possibly + * substantial amount of memory, so we cannot use the per-query context + * here. perTableCxt now serves the same function as "argcontext" does in + * FunctionScan - a place to store per-one-call (i.e. one result table) + * lifetime data (as opposed to per-query or per-result-tuple). + */ + MemoryContextSwitchTo(tstate->perTableCxt); + PG_TRY(); { routine->InitOpaque(tstate, @@ -313,8 +323,7 @@ tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext) } PG_END_TRY(); - /* return to original memory context, and clean up */ - MemoryContextSwitchTo(oldcxt); + /* clean up and return to original memory context */ if (tstate->opaque != NULL) { @@ -322,6 +331,9 @@ tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext) tstate->opaque = NULL; } + MemoryContextSwitchTo(oldcxt); + MemoryContextReset(tstate->perTableCxt); + return; } @@ -428,7 +440,14 @@ tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext) ordinalitycol = ((TableFuncScan *) (tstate->ss.ps.plan))->tablefunc->ordinalitycol; - oldcxt = MemoryContextSwitchTo(tstate->perValueCxt); + + /* + * We need a short-lived memory context that we can clean up each time + * around the loop, to avoid wasting space. Our default per-tuple context + * is fine for the job, since we won't have used it for anything yet in + * this tuple cycle. + */ + oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* * Keep requesting rows from the table builder until there aren't any. @@ -493,7 +512,7 @@ tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext) tuplestore_putvalues(tstate->tupstore, tupdesc, values, nulls); - MemoryContextReset(tstate->perValueCxt); + MemoryContextReset(econtext->ecxt_per_tuple_memory); } MemoryContextSwitchTo(oldcxt); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 018f50bbb7..41fa2052a2 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1580,7 +1580,7 @@ typedef struct TableFuncScanState FmgrInfo *in_functions; /* input function for each column */ Oid *typioparams; /* typioparam for each column */ int64 ordinal; /* row number to be output next */ - MemoryContext perValueCxt; /* short life context for value evaluation */ + MemoryContext perTableCxt; /* per-table context */ Tuplestorestate *tupstore; /* output tuple store */ } TableFuncScanState; -- 2.40.0