Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit be86ca1

Browse files
committed
Fix memory leakage when function compilation fails.
In pl_comp.c, initially create the plpgsql function's cache context under the assumed-short-lived caller's context, and reparent it under CacheMemoryContext only upon success. This avoids a process-lifespan leak of 8kB or more if the function contains syntax errors. (This leakage has existed for a long time without many complaints, but as we move towards a possibly multi-threaded future, getting rid of process-lifespan leaks grows more important.) In funccache.c, arrange to reclaim the CachedFunction struct in case the language-specific compile callback function throws an error; previously, that resulted in an independent process-lifespan leak. This is arguably a new bug in v18, since the leakage now occurred for SQL-language functions as well as plpgsql. Also, don't fill fn_xmin/fn_tid/dcallback until after successful completion of the compile callback. This avoids a scenario where a partially-built function cache might appear already valid upon later inspection, and another scenario where dcallback might fail upon being presented with an incomplete cache entry. We would have to reach such a faulty cache entry via a pre-existing fn_extra pointer, so I'm not sure these scenarios correspond to any live bug. (The predecessor code in pl_comp.c never took any care about this, and we've heard no complaints about that.) Still, it's better to be careful. Given the lack of field complaints, I'm not very excited about back-patching any of this; but it seems still in-scope for v18. Discussion: https://postgr.es/m/999171.1748300004@sss.pgh.pa.us
1 parent c861092 commit be86ca1

File tree

2 files changed

+41
-9
lines changed

2 files changed

+41
-9
lines changed

src/backend/utils/cache/funccache.c

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ cached_function_compile(FunctionCallInfo fcinfo,
491491
CachedFunctionHashKey hashkey;
492492
bool function_valid = false;
493493
bool hashkey_valid = false;
494+
bool new_function = false;
494495

495496
/*
496497
* Lookup the pg_proc tuple by Oid; we'll need it in any case
@@ -570,13 +571,15 @@ cached_function_compile(FunctionCallInfo fcinfo,
570571

571572
/*
572573
* Create the new function struct, if not done already. The function
573-
* structs are never thrown away, so keep them in TopMemoryContext.
574+
* cache entry will be kept for the life of the backend, so put it in
575+
* TopMemoryContext.
574576
*/
575577
Assert(cacheEntrySize >= sizeof(CachedFunction));
576578
if (function == NULL)
577579
{
578580
function = (CachedFunction *)
579581
MemoryContextAllocZero(TopMemoryContext, cacheEntrySize);
582+
new_function = true;
580583
}
581584
else
582585
{
@@ -585,17 +588,36 @@ cached_function_compile(FunctionCallInfo fcinfo,
585588
}
586589

587590
/*
588-
* Fill in the CachedFunction part. fn_hashkey and use_count remain
589-
* zeroes for now.
591+
* However, if function compilation fails, we'd like not to leak the
592+
* function struct, so use a PG_TRY block to prevent that. (It's up
593+
* to the compile callback function to avoid its own internal leakage
594+
* in such cases.) Unfortunately, freeing the struct is only safe if
595+
* we just allocated it: otherwise there are probably fn_extra
596+
* pointers to it.
590597
*/
591-
function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
592-
function->fn_tid = procTup->t_self;
593-
function->dcallback = dcallback;
598+
PG_TRY();
599+
{
600+
/*
601+
* Do the hard, language-specific part.
602+
*/
603+
ccallback(fcinfo, procTup, &hashkey, function, forValidator);
604+
}
605+
PG_CATCH();
606+
{
607+
if (new_function)
608+
pfree(function);
609+
PG_RE_THROW();
610+
}
611+
PG_END_TRY();
594612

595613
/*
596-
* Do the hard, language-specific part.
614+
* Fill in the CachedFunction part. (We do this last to prevent the
615+
* function from looking valid before it's fully built.) fn_hashkey
616+
* will be set by cfunc_hashtable_insert; use_count remains zero.
597617
*/
598-
ccallback(fcinfo, procTup, &hashkey, function, forValidator);
618+
function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
619+
function->fn_tid = procTup->t_self;
620+
function->dcallback = dcallback;
599621

600622
/*
601623
* Add the completed struct to the hash table.

src/pl/plpgsql/src/pl_comp.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,13 @@ plpgsql_compile_callback(FunctionCallInfo fcinfo,
226226
/*
227227
* All the permanent output of compilation (e.g. parse tree) is kept in a
228228
* per-function memory context, so it can be reclaimed easily.
229+
*
230+
* While the func_cxt needs to be long-lived, we initially make it a child
231+
* of the assumed-short-lived caller's context, and reparent it under
232+
* CacheMemoryContext only upon success. This arrangement avoids memory
233+
* leakage during compilation of a faulty function.
229234
*/
230-
func_cxt = AllocSetContextCreate(TopMemoryContext,
235+
func_cxt = AllocSetContextCreate(CurrentMemoryContext,
231236
"PL/pgSQL function",
232237
ALLOCSET_DEFAULT_SIZES);
233238
plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
@@ -703,6 +708,11 @@ plpgsql_compile_callback(FunctionCallInfo fcinfo,
703708
if (plpgsql_DumpExecTree)
704709
plpgsql_dumptree(function);
705710

711+
/*
712+
* All is well, so make the func_cxt long-lived
713+
*/
714+
MemoryContextSetParent(func_cxt, CacheMemoryContext);
715+
706716
/*
707717
* Pop the error context stack
708718
*/

0 commit comments

Comments
 (0)