Adjust nodeFunctionscan.c to reset transient memory context between calls

to the table function, thus preventing memory leakage accumulation across
calls.  This means that SRFs need to be careful to distinguish permanent
and local storage; adjust code and documentation accordingly.  Patch by
Joe Conway, very minor tweaks by Tom Lane.
This commit is contained in:
Tom Lane 2002-08-29 17:14:33 +00:00
parent 0201dac1c3
commit e4186762ff
10 changed files with 154 additions and 134 deletions

View File

@ -1,4 +1,4 @@
pgstattuple README 2002/08/22 Tatsuo Ishii
pgstattuple README 2002/08/29 Tatsuo Ishii
1. What is pgstattuple?
@ -40,15 +40,15 @@ free_percent -- free space in %
3. Using pgstattuple
pgstattuple may be called as a SRF (set returning function) and is
pgstattuple may be called as a table function and is
defined as follows:
CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS SETOF pgstattuple_view
CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS pgstattuple_type
AS 'MODULE_PATHNAME', 'pgstattuple'
LANGUAGE 'c' WITH (isstrict);
The argument is the table name. Note that pgstattuple never
returns more than 1 tuple.
The argument is the table name. Note that pgstattuple only returns
one row.
4. Notes

View File

@ -1,5 +1,5 @@
/*
* $Header: /cvsroot/pgsql/contrib/pgstattuple/pgstattuple.c,v 1.7 2002/08/23 08:19:49 ishii Exp $
* $Header: /cvsroot/pgsql/contrib/pgstattuple/pgstattuple.c,v 1.8 2002/08/29 17:14:31 tgl Exp $
*
* Copyright (c) 2001,2002 Tatsuo Ishii
*
@ -25,10 +25,10 @@
#include "postgres.h"
#include "fmgr.h"
#include "funcapi.h"
#include "access/heapam.h"
#include "access/transam.h"
#include "catalog/namespace.h"
#include "funcapi.h"
#include "utils/builtins.h"
@ -41,19 +41,19 @@ extern Datum pgstattuple(PG_FUNCTION_ARGS);
* returns live/dead tuples info
*
* C FUNCTION definition
* pgstattuple(TEXT) returns setof pgstattuple_view
* see pgstattuple.sql for pgstattuple_view
* pgstattuple(text) returns pgstattuple_type
* see pgstattuple.sql for pgstattuple_type
* ----------
*/
#define DUMMY_TUPLE "pgstattuple_view"
#define DUMMY_TUPLE "pgstattuple_type"
#define NCOLUMNS 9
#define NCHARS 32
Datum
pgstattuple(PG_FUNCTION_ARGS)
{
text *relname;
text *relname = PG_GETARG_TEXT_P(0);
RangeVar *relrv;
Relation rel;
HeapScanDesc scan;
@ -71,62 +71,30 @@ pgstattuple(PG_FUNCTION_ARGS)
double dead_tuple_percent;
uint64 free_space = 0; /* free/reusable space in bytes */
double free_percent; /* free/reusable space in % */
FuncCallContext *funcctx;
int call_cntr;
int max_calls;
TupleDesc tupdesc;
TupleTableSlot *slot;
AttInMetadata *attinmeta;
char **values;
int i;
Datum result;
char **values;
int i;
Datum result;
/*
* Build a tuple description for a pgstattupe_type tuple
*/
tupdesc = RelationNameGetTupleDesc(DUMMY_TUPLE);
/* stuff done only on the first call of the function */
if(SRF_IS_FIRSTCALL())
{
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/* total number of tuples to be returned */
funcctx->max_calls = 1;
/*
* Build a tuple description for a pgstattupe_view tuple
*/
tupdesc = RelationNameGetTupleDesc(DUMMY_TUPLE);
/* allocate a slot for a tuple with this tupdesc */
slot = TupleDescGetSlot(tupdesc);
/* assign slot to function context */
funcctx->slot = slot;
/*
* Generate attribute metadata needed later to produce tuples from raw
* C strings
*/
attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx->attinmeta = attinmeta;
}
/* allocate a slot for a tuple with this tupdesc */
slot = TupleDescGetSlot(tupdesc);
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
call_cntr = funcctx->call_cntr;
max_calls = funcctx->max_calls;
slot = funcctx->slot;
attinmeta = funcctx->attinmeta;
/* Are we done? */
if (call_cntr >= max_calls)
{
SRF_RETURN_DONE(funcctx);
}
/*
* Generate attribute metadata needed later to produce tuples from raw
* C strings
*/
attinmeta = TupleDescGetAttInMetadata(tupdesc);
/* open relation */
relname = PG_GETARG_TEXT_P(0);
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname,"pgstattuple"));
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname,
"pgstattuple"));
rel = heap_openrv(relrv, AccessShareLock);
nblocks = RelationGetNumberOfBlocks(rel);
@ -223,5 +191,5 @@ pgstattuple(PG_FUNCTION_ARGS)
}
pfree(values);
SRF_RETURN_NEXT(funcctx, result);
PG_RETURN_DATUM(result);
}

View File

@ -1,16 +1,16 @@
DROP VIEW pgstattuple_view CASCADE;
CREATE VIEW pgstattuple_view AS
SELECT
0::BIGINT AS table_len, -- physical table length in bytes
0::BIGINT AS tuple_count, -- number of live tuples
0::BIGINT AS tuple_len, -- total tuples length in bytes
0.0::FLOAT AS tuple_percent, -- live tuples in %
0::BIGINT AS dead_tuple_count, -- number of dead tuples
0::BIGINT AS dead_tuple_len, -- total dead tuples length in bytes
0.0::FLOAT AS dead_tuple_percent, -- dead tuples in %
0::BIGINT AS free_space, -- free space in bytes
0.0::FLOAT AS free_percent; -- free space in %
DROP TYPE pgstattuple_type CASCADE;
CREATE TYPE pgstattuple_type AS (
table_len BIGINT, -- physical table length in bytes
tuple_count BIGINT, -- number of live tuples
tuple_len BIGINT, -- total tuples length in bytes
tuple_percent FLOAT, -- live tuples in %
dead_tuple_count BIGINT, -- number of dead tuples
dead_tuple_len BIGINT, -- total dead tuples length in bytes
dead_tuple_percent FLOAT, -- dead tuples in %
free_space BIGINT, -- free space in bytes
free_percent FLOAT -- free space in %
);
CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS SETOF pgstattuple_view
CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS pgstattuple_type
AS 'MODULE_PATHNAME', 'pgstattuple'
LANGUAGE 'c' WITH (isstrict);

View File

@ -87,6 +87,7 @@ normal_rand(PG_FUNCTION_ARGS)
float8 stddev;
float8 carry_val;
bool use_carry;
MemoryContext oldcontext;
/* stuff done only on the first call of the function */
if(SRF_IS_FIRSTCALL())
@ -94,6 +95,9 @@ normal_rand(PG_FUNCTION_ARGS)
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/* switch to memory context appropriate for multiple function calls */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* total number of tuples to be returned */
funcctx->max_calls = PG_GETARG_UINT32(0);
@ -119,6 +123,8 @@ normal_rand(PG_FUNCTION_ARGS)
* purpose it doesn't matter, just cast it as an unsigned value
*/
srandom(PG_GETARG_UINT32(3));
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
@ -260,10 +266,11 @@ crosstab(PG_FUNCTION_ARGS)
AttInMetadata *attinmeta;
SPITupleTable *spi_tuptable = NULL;
TupleDesc spi_tupdesc;
char *lastrowid;
char *lastrowid = NULL;
crosstab_fctx *fctx;
int i;
int num_categories;
MemoryContext oldcontext;
/* stuff done only on the first call of the function */
if(SRF_IS_FIRSTCALL())
@ -275,13 +282,12 @@ crosstab(PG_FUNCTION_ARGS)
TupleDesc tupdesc = NULL;
int ret;
int proc;
MemoryContext oldcontext;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/* SPI switches context on us, so save it first */
oldcontext = CurrentMemoryContext;
/* switch to memory context appropriate for multiple function calls */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* Connect to SPI manager */
if ((ret = SPI_connect()) < 0)
@ -317,8 +323,8 @@ crosstab(PG_FUNCTION_ARGS)
SRF_RETURN_DONE(funcctx);
}
/* back to the original memory context */
MemoryContextSwitchTo(oldcontext);
/* SPI switches context on us, so reset it */
MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* get the typeid that represents our return type */
functypeid = get_func_rettype(funcid);
@ -381,6 +387,8 @@ crosstab(PG_FUNCTION_ARGS)
/* total number of tuples to be returned */
funcctx->max_calls = proc;
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
@ -432,7 +440,7 @@ crosstab(PG_FUNCTION_ARGS)
for (i = 0; i < num_categories; i++)
{
HeapTuple spi_tuple;
char *rowid;
char *rowid = NULL;
/* see if we've gone too far already */
if (call_cntr >= max_calls)
@ -496,7 +504,13 @@ crosstab(PG_FUNCTION_ARGS)
xpfree(fctx->lastrowid);
if (values[0] != NULL)
{
/* switch to memory context appropriate for multiple function calls */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
lastrowid = fctx->lastrowid = pstrdup(values[0]);
MemoryContextSwitchTo(oldcontext);
}
if (!allnulls)
{

View File

@ -1,5 +1,5 @@
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.57 2002/08/29 00:17:02 tgl Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.58 2002/08/29 17:14:32 tgl Exp $
-->
<chapter id="xfunc">
@ -1670,13 +1670,14 @@ typedef struct
AttInMetadata *attinmeta;
/*
* memory context used to initialize structure
* memory context used for structures which must live for multiple calls
*
* fmctx is set by SRF_FIRSTCALL_INIT() for you, and used by
* SRF_RETURN_DONE() for cleanup. It is primarily for internal use
* by the API.
* multi_call_memory_ctx is set by SRF_FIRSTCALL_INIT() for you, and used
* by SRF_RETURN_DONE() for cleanup. It is the most appropriate memory
* context for any memory that is to be re-used across multiple calls
* of the SRF.
*/
MemoryContext fmctx;
MemoryContext multi_call_memory_ctx;
} FuncCallContext;
</programlisting>
@ -1714,27 +1715,43 @@ SRF_RETURN_DONE(funcctx)
to clean up and end the SRF.
</para>
<para>
The palloc memory context that is current when the SRF is called is
a transient context that will be cleared between calls. This means
that you do not need to be careful about pfree'ing everything
you palloc; it will go away anyway. However, if you want to allocate
any data structures to live across calls, you need to put them somewhere
else. The memory context referenced by
<structfield>multi_call_memory_ctx</> is a suitable location for any
data that needs to survive until the SRF is finished running. In most
cases, this means that you should switch into
<structfield>multi_call_memory_ctx</> while doing the first-call setup.
</para>
<para>
A complete pseudo-code example looks like the following:
<programlisting>
Datum
my_Set_Returning_Function(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
Datum result;
FuncCallContext *funcctx;
Datum result;
MemoryContext oldcontext;
[user defined declarations]
if (SRF_IS_FIRSTCALL())
{
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* one-time setup code appears here: */
[user defined code]
funcctx = SRF_FIRSTCALL_INIT();
[if returning composite]
[build TupleDesc, and perhaps AttInMetadata]
[obtain slot]
funcctx-&gt;slot = slot;
[endif returning composite]
[user defined code]
MemoryContextSwitchTo(oldcontext);
}
/* each-time setup code appears here: */
@ -1777,8 +1794,13 @@ testpassbyval(PG_FUNCTION_ARGS)
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
funcctx = SRF_FIRSTCALL_INIT();
/* switch to memory context appropriate for multiple function calls */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* total number of tuples to be returned */
funcctx-&gt;max_calls = PG_GETARG_UINT32(0);
@ -1800,6 +1822,8 @@ testpassbyval(PG_FUNCTION_ARGS)
*/
attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx-&gt;attinmeta = attinmeta;
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
@ -1836,7 +1860,7 @@ testpassbyval(PG_FUNCTION_ARGS)
/* make the tuple into a datum */
result = TupleGetDatum(slot, tuple);
/* Clean up */
/* Clean up (this is not actually necessary) */
pfree(values[0]);
pfree(values[1]);
pfree(values[2]);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.6 2002/08/29 00:17:04 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.7 2002/08/29 17:14:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -428,6 +428,12 @@ function_getonetuple(FunctionScanState *scanstate,
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
/*
* reset per-tuple memory context before each call of the function.
* This cleans up any local memory the function may leak when called.
*/
ResetExprContext(econtext);
/*
* get the next Datum from the function
*/

View File

@ -5,7 +5,7 @@
* Copyright (c) 2002, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/lockfuncs.c,v 1.3 2002/08/29 00:17:05 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/lockfuncs.c,v 1.4 2002/08/29 17:14:33 tgl Exp $
*/
#include "postgres.h"
@ -24,15 +24,20 @@ static int next_lock(int locks[]);
Datum
pg_lock_status(PG_FUNCTION_ARGS)
{
FuncCallContext *funccxt;
LockData *lockData;
FuncCallContext *funcctx;
LockData *lockData;
MemoryContext oldcontext;
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcxt;
TupleDesc tupdesc;
funccxt = SRF_FIRSTCALL_INIT();
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/* switch to memory context appropriate for multiple function calls */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
tupdesc = CreateTemplateTupleDesc(5, WITHOUTOID);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relation",
OIDOID, -1, 0, false);
@ -45,10 +50,8 @@ pg_lock_status(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "isgranted",
BOOLOID, -1, 0, false);
funccxt->slot = TupleDescGetSlot(tupdesc);
funccxt->attinmeta = TupleDescGetAttInMetadata(tupdesc);
oldcxt = MemoryContextSwitchTo(funccxt->fmctx);
funcctx->slot = TupleDescGetSlot(tupdesc);
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
/*
* Preload all the locking information that we will eventually format
@ -56,15 +59,15 @@ pg_lock_status(PG_FUNCTION_ARGS)
* MemoryContext is reset when the SRF finishes, we don't need to
* free it ourselves.
*/
funccxt->user_fctx = (LockData *) palloc(sizeof(LockData));
funcctx->user_fctx = (LockData *) palloc(sizeof(LockData));
GetLockStatusData(funccxt->user_fctx);
GetLockStatusData(funcctx->user_fctx);
MemoryContextSwitchTo(oldcxt);
MemoryContextSwitchTo(oldcontext);
}
funccxt = SRF_PERCALL_SETUP();
lockData = (LockData *) funccxt->user_fctx;
funcctx = SRF_PERCALL_SETUP();
lockData = (LockData *) funcctx->user_fctx;
while (lockData->currIdx < lockData->nelements)
{
@ -82,7 +85,7 @@ pg_lock_status(PG_FUNCTION_ARGS)
holder = &(lockData->holders[currIdx]);
lock = &(lockData->locks[currIdx]);
proc = &(lockData->procs[currIdx]);
num_attrs = funccxt->attinmeta->tupdesc->natts;
num_attrs = funcctx->attinmeta->tupdesc->natts;
values = (char **) palloc(sizeof(*values) * num_attrs);
@ -133,12 +136,12 @@ pg_lock_status(PG_FUNCTION_ARGS)
strncpy(values[3], GetLockmodeName(mode), 32);
tuple = BuildTupleFromCStrings(funccxt->attinmeta, values);
result = TupleGetDatum(funccxt->slot, tuple);
SRF_RETURN_NEXT(funccxt, result);
tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
result = TupleGetDatum(funcctx->slot, tuple);
SRF_RETURN_NEXT(funcctx, result);
}
SRF_RETURN_DONE(funccxt);
SRF_RETURN_DONE(funcctx);
}
static LOCKMODE

View File

@ -7,7 +7,7 @@
* Copyright (c) 2002, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/fmgr/funcapi.c,v 1.3 2002/08/29 00:17:05 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/fmgr/funcapi.c,v 1.4 2002/08/29 17:14:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -39,16 +39,12 @@ init_MultiFuncCall(PG_FUNCTION_ARGS)
{
/*
* First call
*
* Allocate suitably long-lived space and zero it
*/
MemoryContext oldcontext;
/* switch to the appropriate memory context */
oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
/*
* allocate space and zero it
*/
retval = (FuncCallContext *) palloc(sizeof(FuncCallContext));
retval = (FuncCallContext *)
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
sizeof(FuncCallContext));
MemSet(retval, 0, sizeof(FuncCallContext));
/*
@ -59,15 +55,12 @@ init_MultiFuncCall(PG_FUNCTION_ARGS)
retval->slot = NULL;
retval->user_fctx = NULL;
retval->attinmeta = NULL;
retval->fmctx = fcinfo->flinfo->fn_mcxt;
retval->multi_call_memory_ctx = fcinfo->flinfo->fn_mcxt;
/*
* save the pointer for cross-call use
*/
fcinfo->flinfo->fn_extra = retval;
/* back to the original memory context */
MemoryContextSwitchTo(oldcontext);
}
else /* second and subsequent calls */
{

View File

@ -5,7 +5,7 @@
* command, configuration file, and command line options.
* See src/backend/utils/misc/README for more information.
*
* $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.85 2002/08/29 00:17:05 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.86 2002/08/29 17:14:33 tgl Exp $
*
* Copyright 2000 by PostgreSQL Global Development Group
* Written by Peter Eisentraut <peter_e@gmx.net>.
@ -2421,6 +2421,7 @@ show_all_settings(PG_FUNCTION_ARGS)
int max_calls;
TupleTableSlot *slot;
AttInMetadata *attinmeta;
MemoryContext oldcontext;
/* stuff done only on the first call of the function */
if(SRF_IS_FIRSTCALL())
@ -2428,6 +2429,9 @@ show_all_settings(PG_FUNCTION_ARGS)
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/* switch to memory context appropriate for multiple function calls */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* need a tuple descriptor representing two TEXT columns */
tupdesc = CreateTemplateTupleDesc(2, WITHOUTOID);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
@ -2450,6 +2454,8 @@ show_all_settings(PG_FUNCTION_ARGS)
/* total number of tuples to be returned */
funcctx->max_calls = GetNumConfigOptions();
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */

View File

@ -9,7 +9,7 @@
*
* Copyright (c) 2002, PostgreSQL Global Development Group
*
* $Id: funcapi.h,v 1.5 2002/08/29 00:17:06 tgl Exp $
* $Id: funcapi.h,v 1.6 2002/08/29 17:14:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -101,13 +101,14 @@ typedef struct FuncCallContext
AttInMetadata *attinmeta;
/*
* memory context used to initialize structure
* memory context used for structures which must live for multiple calls
*
* fmctx is set by SRF_FIRSTCALL_INIT() for you, and used by
* SRF_RETURN_DONE() for cleanup. It is primarily for internal use
* by the API.
* multi_call_memory_ctx is set by SRF_FIRSTCALL_INIT() for you, and used
* by SRF_RETURN_DONE() for cleanup. It is the most appropriate memory
* context for any memory that is to be re-used across multiple calls
* of the SRF.
*/
MemoryContext fmctx;
MemoryContext multi_call_memory_ctx;
} FuncCallContext;
@ -160,17 +161,22 @@ extern HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
* {
* FuncCallContext *funcctx;
* Datum result;
* MemoryContext oldcontext;
* <user defined declarations>
*
* if(SRF_IS_FIRSTCALL())
* if (SRF_IS_FIRSTCALL())
* {
* <user defined code>
* funcctx = SRF_FIRSTCALL_INIT();
* // switch context when allocating stuff to be used in later calls
* oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
* <user defined code>
* <if returning composite>
* <obtain slot>
* funcctx->slot = slot;
* <endif returning composite>
* <user defined code>
* // return to original context when allocating transient memory
* MemoryContextSwitchTo(oldcontext);
* }
* <user defined code>
* funcctx = SRF_PERCALL_SETUP();