*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.109 2007/01/05 22:20:02 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.110 2007/01/30 22:05:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
HeapTuple procTup,
+ PLpgSQL_function *function,
PLpgSQL_func_hashkey *hashkey,
bool forValidator);
static PLpgSQL_row *build_row_from_class(Oid classOid);
Form_pg_proc procStruct;
PLpgSQL_function *function;
PLpgSQL_func_hashkey hashkey;
+ bool function_valid = false;
bool hashkey_valid = false;
/*
*/
function = (PLpgSQL_function *) fcinfo->flinfo->fn_extra;
+recheck:
if (!function)
{
/* Compute hashkey using function signature and actual arg types */
if (function)
{
/* We have a compiled function, but is it still valid? */
- if (!(function->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
- function->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data)))
+ if (function->fn_xmin == HeapTupleHeaderGetXmin(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);
- function = NULL;
+ /*
+ * 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;
+ if (!hashkey_valid)
+ goto recheck;
+ }
}
}
/*
* 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
/*
* Do the hard part.
*/
- function = do_compile(fcinfo, procTup, &hashkey, forValidator);
+ function = do_compile(fcinfo, procTup, function,
+ &hashkey, forValidator);
}
ReleaseSysCache(procTup);
/*
* 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
* per-function memory context of the function we are compiling. That
* means a palloc() will allocate storage with the same lifetime as
* switch into a short-term context before calling into the
* backend. An appropriate context for performing short-term
* 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 *
do_compile(FunctionCallInfo fcinfo,
HeapTuple procTup,
+ PLpgSQL_function *function,
PLpgSQL_func_hashkey *hashkey,
bool forValidator)
{
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
int functype = CALLED_AS_TRIGGER(fcinfo) ? T_TRIGGER : T_FUNCTION;
- PLpgSQL_function *function;
Datum prosrcdatum;
bool isnull;
char *proc_source;
plpgsql_check_syntax = forValidator;
/*
- * Create the new function node. We allocate the function and all of its
- * compile-time storage (e.g. parse tree) in its own memory context. This
- * allows us to reclaim the function's storage cleanly.
+ * Create the new function struct, if not done already. The function
+ * structs are never thrown away, so keep them in TopMemoryContext.
+ */
+ 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,
"PL/PgSQL function context",
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
- function = palloc0(sizeof(*function));
- plpgsql_curr_compile = function;
function->fn_name = pstrdup(NameStr(procStruct->proname));
function->fn_oid = fcinfo->flinfo->fn_oid;
}
}
+/*
+ * 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
delete_function(PLpgSQL_function *func)
{
- /* remove function from hash table */
+ /* remove function from hash table (might be done already) */
plpgsql_HashTableDelete(func);
- /* release the function's storage */
- MemoryContextDelete(func->fn_cxt);
-
- /*
- * Caller should be sure not to use passed-in pointer, as it now points to
- * pfree'd storage
- */
+ /* release the function's storage if safe and not done already */
+ if (func->use_count == 0 && func->fn_cxt)
+ {
+ MemoryContextDelete(func->fn_cxt);
+ func->fn_cxt = NULL;
+ }
}
/* exported so we can call it from plpgsql_init() */
{
plpgsql_HashEnt *hentry;
+ /* do nothing if not in table */
+ if (function->fn_hashkey == NULL)
+ return;
+
hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
(void *) function->fn_hashkey,
HASH_REMOVE,
NULL);
if (hentry == NULL)
elog(WARNING, "trying to delete function that does not exist");
+
+ /* remove back link, which no longer points to allocated storage */
+ function->fn_hashkey = NULL;
}