2525#include "nodes/nodeFuncs.h"
2626#include "parser/parse_coerce.h"
2727#include "parser/parse_func.h"
28+ #include "storage/proc.h"
2829#include "tcop/utility.h"
2930#include "utils/builtins.h"
3031#include "utils/datum.h"
3132#include "utils/lsyscache.h"
33+ #include "utils/memutils.h"
3234#include "utils/snapmgr.h"
3335#include "utils/syscache.h"
3436
@@ -74,8 +76,17 @@ typedef struct execution_state
7476 * and linked to from the fn_extra field of the FmgrInfo struct.
7577 *
7678 * Note that currently this has only the lifespan of the calling query.
77- * Someday we might want to consider caching the parse/plan results longer
78- * than that.
79+ * Someday we should rewrite this code to use plancache.c to save parse/plan
80+ * results for longer than that.
81+ *
82+ * Physically, though, the data has the lifespan of the FmgrInfo that's used
83+ * to call the function, and there are cases (particularly with indexes)
84+ * where the FmgrInfo might survive across transactions. We cannot assume
85+ * that the parse/plan trees are good for longer than the (sub)transaction in
86+ * which parsing was done, so we must mark the record with the LXID/subxid of
87+ * its creation time, and regenerate everything if that's obsolete. To avoid
88+ * memory leakage when we do have to regenerate things, all the data is kept
89+ * in a sub-context of the FmgrInfo's fn_mcxt.
7990 */
8091typedef struct
8192{
@@ -106,6 +117,12 @@ typedef struct
106117 * track of where the original query boundaries are.
107118 */
108119 List * func_state ;
120+
121+ MemoryContext fcontext ; /* memory context holding this struct and all
122+ * subsidiary data */
123+
124+ LocalTransactionId lxid ; /* lxid in which cache was made */
125+ SubTransactionId subxid ; /* subxid in which cache was made */
109126} SQLFunctionCache ;
110127
111128typedef SQLFunctionCache * SQLFunctionCachePtr ;
@@ -551,6 +568,8 @@ static void
551568init_sql_fcache (FmgrInfo * finfo , Oid collation , bool lazyEvalOK )
552569{
553570 Oid foid = finfo -> fn_oid ;
571+ MemoryContext fcontext ;
572+ MemoryContext oldcontext ;
554573 Oid rettype ;
555574 HeapTuple procedureTuple ;
556575 Form_pg_proc procedureStruct ;
@@ -562,7 +581,25 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
562581 Datum tmp ;
563582 bool isNull ;
564583
584+ /*
585+ * Create memory context that holds all the SQLFunctionCache data. It
586+ * must be a child of whatever context holds the FmgrInfo.
587+ */
588+ fcontext = AllocSetContextCreate (finfo -> fn_mcxt ,
589+ "SQL function data" ,
590+ ALLOCSET_DEFAULT_MINSIZE ,
591+ ALLOCSET_DEFAULT_INITSIZE ,
592+ ALLOCSET_DEFAULT_MAXSIZE );
593+
594+ oldcontext = MemoryContextSwitchTo (fcontext );
595+
596+ /*
597+ * Create the struct proper, link it to fcontext and fn_extra. Once this
598+ * is done, we'll be able to recover the memory after failure, even if the
599+ * FmgrInfo is long-lived.
600+ */
565601 fcache = (SQLFunctionCachePtr ) palloc0 (sizeof (SQLFunctionCache ));
602+ fcache -> fcontext = fcontext ;
566603 finfo -> fn_extra = (void * ) fcache ;
567604
568605 /*
@@ -635,6 +672,11 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
635672 * sublist, for example if the last query rewrites to DO INSTEAD NOTHING.
636673 * (It might not be unreasonable to throw an error in such a case, but
637674 * this is the historical behavior and it doesn't seem worth changing.)
675+ *
676+ * Note: since parsing and planning is done in fcontext, we will generate
677+ * a lot of cruft that lives as long as the fcache does. This is annoying
678+ * but we'll not worry about it until the module is rewritten to use
679+ * plancache.c.
638680 */
639681 raw_parsetree_list = pg_parse_query (fcache -> src );
640682
@@ -700,7 +742,13 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
700742 fcache ,
701743 lazyEvalOK );
702744
745+ /* Mark fcache with time of creation to show it's valid */
746+ fcache -> lxid = MyProc -> lxid ;
747+ fcache -> subxid = GetCurrentSubTransactionId ();
748+
703749 ReleaseSysCache (procedureTuple );
750+
751+ MemoryContextSwitchTo (oldcontext );
704752}
705753
706754/* Start up execution of one execution_state node */
@@ -923,9 +971,9 @@ postquel_get_single_result(TupleTableSlot *slot,
923971Datum
924972fmgr_sql (PG_FUNCTION_ARGS )
925973{
926- MemoryContext oldcontext ;
927974 SQLFunctionCachePtr fcache ;
928975 ErrorContextCallback sqlerrcontext ;
976+ MemoryContext oldcontext ;
929977 bool randomAccess ;
930978 bool lazyEvalOK ;
931979 bool is_first ;
@@ -936,13 +984,6 @@ fmgr_sql(PG_FUNCTION_ARGS)
936984 List * eslist ;
937985 ListCell * eslc ;
938986
939- /*
940- * Switch to context in which the fcache lives. This ensures that
941- * parsetrees, plans, etc, will have sufficient lifetime. The
942- * sub-executor is responsible for deleting per-tuple information.
943- */
944- oldcontext = MemoryContextSwitchTo (fcinfo -> flinfo -> fn_mcxt );
945-
946987 /*
947988 * Setup error traceback support for ereport()
948989 */
@@ -978,20 +1019,43 @@ fmgr_sql(PG_FUNCTION_ARGS)
9781019 }
9791020
9801021 /*
981- * Initialize fcache (build plans) if first time through.
1022+ * Initialize fcache (build plans) if first time through; or re-initialize
1023+ * if the cache is stale.
9821024 */
9831025 fcache = (SQLFunctionCachePtr ) fcinfo -> flinfo -> fn_extra ;
1026+
1027+ if (fcache != NULL )
1028+ {
1029+ if (fcache -> lxid != MyProc -> lxid ||
1030+ !SubTransactionIsActive (fcache -> subxid ))
1031+ {
1032+ /* It's stale; unlink and delete */
1033+ fcinfo -> flinfo -> fn_extra = NULL ;
1034+ MemoryContextDelete (fcache -> fcontext );
1035+ fcache = NULL ;
1036+ }
1037+ }
1038+
9841039 if (fcache == NULL )
9851040 {
9861041 init_sql_fcache (fcinfo -> flinfo , PG_GET_COLLATION (), lazyEvalOK );
9871042 fcache = (SQLFunctionCachePtr ) fcinfo -> flinfo -> fn_extra ;
9881043 }
989- eslist = fcache -> func_state ;
1044+
1045+ /*
1046+ * Switch to context in which the fcache lives. This ensures that our
1047+ * tuplestore etc will have sufficient lifetime. The sub-executor is
1048+ * responsible for deleting per-tuple information. (XXX in the case of a
1049+ * long-lived FmgrInfo, this policy represents more memory leakage, but
1050+ * it's not entirely clear where to keep stuff instead.)
1051+ */
1052+ oldcontext = MemoryContextSwitchTo (fcache -> fcontext );
9901053
9911054 /*
9921055 * Find first unfinished query in function, and note whether it's the
9931056 * first query.
9941057 */
1058+ eslist = fcache -> func_state ;
9951059 es = NULL ;
9961060 is_first = true;
9971061 foreach (eslc , eslist )
0 commit comments