Repair oversights in the mechanism used to store compiled plpgsql functions.

The original coding failed (tried to access deallocated memory) if there were
two active call sites (fn_extra pointers) for the same function and the
function definition was updated.  Also, if an update of a recursive function
was detected upon nested entry to the function, the existing compiled version
was summarily deallocated, resulting in crash upon return to the outer
instance.  Problem observed while studying a bug report from Sergiy
Vyshnevetskiy.

Bug does not exist before 8.1 since older versions just leaked the memory of
obsoleted compiled functions, rather than trying to reclaim it.
REL8_1_STABLE
Tom Lane 19 years ago
parent 458e9169d6
commit 6c74f05cf2
  1. 105
      src/pl/plpgsql/src/pl_comp.c
  2. 17
      src/pl/plpgsql/src/pl_handler.c
  3. 4
      src/pl/plpgsql/src/plpgsql.h

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.94.2.2 2005/12/09 17:09:00 tgl Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.94.2.3 2007/01/30 22:05:25 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -121,6 +121,7 @@ static const ExceptionLabelMap exception_label_map[] = {
*/ */
static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo, static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
HeapTuple procTup, HeapTuple procTup,
PLpgSQL_function *function,
PLpgSQL_func_hashkey *hashkey, PLpgSQL_func_hashkey *hashkey,
bool forValidator); bool forValidator);
static int fetchArgInfo(HeapTuple procTup, static int fetchArgInfo(HeapTuple procTup,
@ -161,6 +162,7 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
Form_pg_proc procStruct; Form_pg_proc procStruct;
PLpgSQL_function *function; PLpgSQL_function *function;
PLpgSQL_func_hashkey hashkey; PLpgSQL_func_hashkey hashkey;
bool function_valid = false;
bool hashkey_valid = false; bool hashkey_valid = false;
/* /*
@ -179,6 +181,7 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
*/ */
function = (PLpgSQL_function *) fcinfo->flinfo->fn_extra; function = (PLpgSQL_function *) fcinfo->flinfo->fn_extra;
recheck:
if (!function) if (!function)
{ {
/* Compute hashkey using function signature and actual arg types */ /* Compute hashkey using function signature and actual arg types */
@ -192,19 +195,43 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
if (function) if (function)
{ {
/* We have a compiled function, but is it still valid? */ /* We have a compiled function, but is it still valid? */
if (!(function->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && if (function->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
function->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data))) function->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data))
function_valid = true;
else
{ {
/* Nope, drop the function and associated storage */ /*
* Nope, so remove it from hashtable and try to drop associated
* storage (if not done already).
*/
delete_function(function); delete_function(function);
/*
* If the function isn't in active use then we can overwrite the
* func struct with new data, allowing any other existing fn_extra
* pointers to make use of the new definition on their next use.
* If it is in use then just leave it alone and make a new one.
* (The active invocations will run to completion using the
* previous definition, and then the cache entry will just be
* leaked; doesn't seem worth adding code to clean it up, given
* what a corner case this is.)
*
* If we found the function struct via fn_extra then it's possible
* a replacement has already been made, so go back and recheck
* the hashtable.
*/
if (function->use_count != 0)
{
function = NULL; function = NULL;
if (!hashkey_valid)
goto recheck;
}
} }
} }
/* /*
* If the function wasn't found or was out-of-date, we have to compile it * If the function wasn't found or was out-of-date, we have to compile it
*/ */
if (!function) if (!function_valid)
{ {
/* /*
* Calculate hashkey if we didn't already; we'll need it to store the * Calculate hashkey if we didn't already; we'll need it to store the
@ -217,7 +244,8 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
/* /*
* Do the hard part. * Do the hard part.
*/ */
function = do_compile(fcinfo, procTup, &hashkey, forValidator); function = do_compile(fcinfo, procTup, function,
&hashkey, forValidator);
} }
ReleaseSysCache(procTup); ReleaseSysCache(procTup);
@ -236,6 +264,9 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
/* /*
* This is the slow part of plpgsql_compile(). * This is the slow part of plpgsql_compile().
* *
* The passed-in "function" pointer is either NULL or an already-allocated
* function struct to overwrite.
*
* While compiling a function, the CurrentMemoryContext is the * While compiling a function, the CurrentMemoryContext is the
* per-function memory context of the function we are compiling. That * per-function memory context of the function we are compiling. That
* means a palloc() will allocate storage with the same lifetime as * means a palloc() will allocate storage with the same lifetime as
@ -248,16 +279,19 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
* switch into a short-term context before calling into the * switch into a short-term context before calling into the
* backend. An appropriate context for performing short-term * backend. An appropriate context for performing short-term
* allocations is the compile_tmp_cxt. * allocations is the compile_tmp_cxt.
*
* NB: this code is not re-entrant. We assume that nothing we do here could
* result in the invocation of another plpgsql function.
*/ */
static PLpgSQL_function * static PLpgSQL_function *
do_compile(FunctionCallInfo fcinfo, do_compile(FunctionCallInfo fcinfo,
HeapTuple procTup, HeapTuple procTup,
PLpgSQL_function *function,
PLpgSQL_func_hashkey *hashkey, PLpgSQL_func_hashkey *hashkey,
bool forValidator) bool forValidator)
{ {
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
int functype = CALLED_AS_TRIGGER(fcinfo) ? T_TRIGGER : T_FUNCTION; int functype = CALLED_AS_TRIGGER(fcinfo) ? T_TRIGGER : T_FUNCTION;
PLpgSQL_function *function;
Datum prosrcdatum; Datum prosrcdatum;
bool isnull; bool isnull;
char *proc_source; char *proc_source;
@ -323,9 +357,24 @@ do_compile(FunctionCallInfo fcinfo,
plpgsql_check_syntax = forValidator; plpgsql_check_syntax = forValidator;
/* /*
* Create the new function node. We allocate the function and all of its * Create the new function struct, if not done already. The function
* compile-time storage (e.g. parse tree) in its own memory context. This * structs are never thrown away, so keep them in TopMemoryContext.
* allows us to reclaim the function's storage cleanly. */
if (function == NULL)
{
function = (PLpgSQL_function *)
MemoryContextAllocZero(TopMemoryContext, sizeof(PLpgSQL_function));
}
else
{
/* re-using a previously existing struct, so clear it out */
memset(function, 0, sizeof(PLpgSQL_function));
}
plpgsql_curr_compile = function;
/*
* All the rest of the compile-time storage (e.g. parse tree) is kept in
* its own memory context, so it can be reclaimed easily.
*/ */
func_cxt = AllocSetContextCreate(TopMemoryContext, func_cxt = AllocSetContextCreate(TopMemoryContext,
"PL/PgSQL function context", "PL/PgSQL function context",
@ -333,8 +382,6 @@ do_compile(FunctionCallInfo fcinfo,
ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE); ALLOCSET_DEFAULT_MAXSIZE);
compile_tmp_cxt = MemoryContextSwitchTo(func_cxt); compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function = palloc0(sizeof(*function));
plpgsql_curr_compile = function;
function->fn_name = pstrdup(NameStr(procStruct->proname)); function->fn_name = pstrdup(NameStr(procStruct->proname));
function->fn_oid = fcinfo->flinfo->fn_oid; function->fn_oid = fcinfo->flinfo->fn_oid;
@ -2100,19 +2147,32 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
} }
} }
/*
* delete_function - clean up as much as possible of a stale function cache
*
* We can't release the PLpgSQL_function struct itself, because of the
* possibility that there are fn_extra pointers to it. We can release
* the subsidiary storage, but only if there are no active evaluations
* in progress. Otherwise we'll just leak that storage. Since the
* case would only occur if a pg_proc update is detected during a nested
* recursive call on the function, a leak seems acceptable.
*
* Note that this can be called more than once if there are multiple fn_extra
* pointers to the same function cache. Hence be careful not to do things
* twice.
*/
static void static void
delete_function(PLpgSQL_function *func) delete_function(PLpgSQL_function *func)
{ {
/* remove function from hash table */ /* remove function from hash table (might be done already) */
plpgsql_HashTableDelete(func); plpgsql_HashTableDelete(func);
/* release the function's storage */ /* release the function's storage if safe and not done already */
if (func->use_count == 0 && func->fn_cxt)
{
MemoryContextDelete(func->fn_cxt); MemoryContextDelete(func->fn_cxt);
func->fn_cxt = NULL;
/* }
* Caller should be sure not to use passed-in pointer, as it now points to
* pfree'd storage
*/
} }
/* exported so we can call it from plpgsql_init() */ /* exported so we can call it from plpgsql_init() */
@ -2173,10 +2233,17 @@ plpgsql_HashTableDelete(PLpgSQL_function *function)
{ {
plpgsql_HashEnt *hentry; plpgsql_HashEnt *hentry;
/* do nothing if not in table */
if (function->fn_hashkey == NULL)
return;
hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable, hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
(void *) function->fn_hashkey, (void *) function->fn_hashkey,
HASH_REMOVE, HASH_REMOVE,
NULL); NULL);
if (hentry == NULL) if (hentry == NULL)
elog(WARNING, "trying to delete function that does not exist"); elog(WARNING, "trying to delete function that does not exist");
/* remove back link, which no longer points to allocated storage */
function->fn_hashkey = NULL;
} }

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.26 2005/10/15 02:49:50 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.26.2.1 2007/01/30 22:05:25 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -112,6 +112,11 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */ /* Find or compile the function */
func = plpgsql_compile(fcinfo, false); func = plpgsql_compile(fcinfo, false);
/* Mark the function as busy, so it can't be deleted from under us */
func->use_count++;
PG_TRY();
{
/* /*
* Determine if called as function or trigger and call appropriate * Determine if called as function or trigger and call appropriate
* subhandler * subhandler
@ -121,6 +126,16 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
(TriggerData *) fcinfo->context)); (TriggerData *) fcinfo->context));
else else
retval = plpgsql_exec_function(func, fcinfo); retval = plpgsql_exec_function(func, fcinfo);
}
PG_CATCH();
{
/* Decrement use-count and propagate error */
func->use_count--;
PG_RE_THROW();
}
PG_END_TRY();
func->use_count--;
/* /*
* Disconnect from SPI manager * Disconnect from SPI manager

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.65.2.2 2006/03/02 05:34:17 tgl Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.65.2.3 2007/01/30 22:05:25 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -598,6 +598,8 @@ typedef struct PLpgSQL_function
int ndatums; int ndatums;
PLpgSQL_datum **datums; PLpgSQL_datum **datums;
PLpgSQL_stmt_block *action; PLpgSQL_stmt_block *action;
unsigned long use_count;
} PLpgSQL_function; } PLpgSQL_function;

Loading…
Cancel
Save