I wrote:
> Or we could add fields recording the current transaction/subtransaction
> IDs, then throw away and regenerate the function cache entry if that
> subxact is no longer active.  This would be a bit more invasive/riskier
> than throwing a "not supported" error, but it would preserve the
> functionality.

The attached patch seems fairly reasonable for this.

                        regards, tom lane

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 9a9724574bec2874710316fa74643ae24d2d305e..e62286f9f98eccfd9a30e2e8f4908e8757b48672 100644
*** a/src/backend/access/transam/xact.c
--- b/src/backend/access/transam/xact.c
*************** GetCurrentSubTransactionId(void)
*** 570,575 ****
--- 570,596 ----
  	return s->subTransactionId;
  }
  
+ /*
+  *	SubTransactionIsActive
+  *
+  * Test if the specified subxact ID is still active.  Note caller is
+  * responsible for checking whether this ID is relevant to the current xact.
+  */
+ bool
+ SubTransactionIsActive(SubTransactionId subxid)
+ {
+ 	TransactionState s;
+ 
+ 	for (s = CurrentTransactionState; s != NULL; s = s->parent)
+ 	{
+ 		if (s->state == TRANS_ABORT)
+ 			continue;
+ 		if (s->subTransactionId == subxid)
+ 			return true;
+ 	}
+ 	return false;
+ }
+ 
  
  /*
   *	GetCurrentCommandId
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 2782b55e876078cda87bc9dac6b980340be0298d..c908f34cfe4bdd7fb0e059679d8307b2f75c8b23 100644
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 25,34 ****
--- 25,36 ----
  #include "nodes/nodeFuncs.h"
  #include "parser/parse_coerce.h"
  #include "parser/parse_func.h"
+ #include "storage/proc.h"
  #include "tcop/utility.h"
  #include "utils/builtins.h"
  #include "utils/datum.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  #include "utils/snapmgr.h"
  #include "utils/syscache.h"
  
*************** typedef struct execution_state
*** 74,81 ****
   * and linked to from the fn_extra field of the FmgrInfo struct.
   *
   * Note that currently this has only the lifespan of the calling query.
!  * Someday we might want to consider caching the parse/plan results longer
!  * than that.
   */
  typedef struct
  {
--- 76,92 ----
   * and linked to from the fn_extra field of the FmgrInfo struct.
   *
   * Note that currently this has only the lifespan of the calling query.
!  * Someday we should rewrite this code to use plancache.c to save parse/plan
!  * results for longer than that.
!  *
!  * Physically, though, the data has the lifespan of the FmgrInfo that's used
!  * to call the function, and there are cases (particularly with indexes)
!  * where the FmgrInfo might survive across transactions.  We cannot assume
!  * that the parse/plan trees are good for longer than the (sub)transaction in
!  * which parsing was done, so we must mark the record with the LXID/subxid of
!  * its creation time, and regenerate everything if that's obsolete.  To avoid
!  * memory leakage when we do have to regenerate things, all the data is kept
!  * in a sub-context of the FmgrInfo's fn_mcxt.
   */
  typedef struct
  {
*************** typedef struct
*** 106,111 ****
--- 117,128 ----
  	 * track of where the original query boundaries are.
  	 */
  	List	   *func_state;
+ 
+ 	MemoryContext fcontext;		/* memory context holding this struct and all
+ 								 * subsidiary data */
+ 
+ 	LocalTransactionId lxid;	/* lxid in which cache was made */
+ 	SubTransactionId subxid;	/* subxid in which cache was made */
  } SQLFunctionCache;
  
  typedef SQLFunctionCache *SQLFunctionCachePtr;
*************** static void
*** 551,556 ****
--- 568,575 ----
  init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
  {
  	Oid			foid = finfo->fn_oid;
+ 	MemoryContext fcontext;
+ 	MemoryContext oldcontext;
  	Oid			rettype;
  	HeapTuple	procedureTuple;
  	Form_pg_proc procedureStruct;
*************** init_sql_fcache(FmgrInfo *finfo, Oid col
*** 562,568 ****
--- 581,605 ----
  	Datum		tmp;
  	bool		isNull;
  
+ 	/*
+ 	 * Create memory context that holds all the SQLFunctionCache data.	It
+ 	 * must be a child of whatever context holds the FmgrInfo.
+ 	 */
+ 	fcontext = AllocSetContextCreate(finfo->fn_mcxt,
+ 									 "SQL function data",
+ 									 ALLOCSET_DEFAULT_MINSIZE,
+ 									 ALLOCSET_DEFAULT_INITSIZE,
+ 									 ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 	oldcontext = MemoryContextSwitchTo(fcontext);
+ 
+ 	/*
+ 	 * Create the struct proper, link it to fcontext and fn_extra.	Once this
+ 	 * is done, we'll be able to recover the memory after failure, even if the
+ 	 * FmgrInfo is long-lived.
+ 	 */
  	fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache));
+ 	fcache->fcontext = fcontext;
  	finfo->fn_extra = (void *) fcache;
  
  	/*
*************** init_sql_fcache(FmgrInfo *finfo, Oid col
*** 635,640 ****
--- 672,682 ----
  	 * sublist, for example if the last query rewrites to DO INSTEAD NOTHING.
  	 * (It might not be unreasonable to throw an error in such a case, but
  	 * this is the historical behavior and it doesn't seem worth changing.)
+ 	 *
+ 	 * Note: since parsing and planning is done in fcontext, we will generate
+ 	 * a lot of cruft that lives as long as the fcache does.  This is annoying
+ 	 * but we'll not worry about it until the module is rewritten to use
+ 	 * plancache.c.
  	 */
  	raw_parsetree_list = pg_parse_query(fcache->src);
  
*************** init_sql_fcache(FmgrInfo *finfo, Oid col
*** 700,706 ****
--- 742,754 ----
  											  fcache,
  											  lazyEvalOK);
  
+ 	/* Mark fcache with time of creation to show it's valid */
+ 	fcache->lxid = MyProc->lxid;
+ 	fcache->subxid = GetCurrentSubTransactionId();
+ 
  	ReleaseSysCache(procedureTuple);
+ 
+ 	MemoryContextSwitchTo(oldcontext);
  }
  
  /* Start up execution of one execution_state node */
*************** postquel_get_single_result(TupleTableSlo
*** 923,931 ****
  Datum
  fmgr_sql(PG_FUNCTION_ARGS)
  {
- 	MemoryContext oldcontext;
  	SQLFunctionCachePtr fcache;
  	ErrorContextCallback sqlerrcontext;
  	bool		randomAccess;
  	bool		lazyEvalOK;
  	bool		is_first;
--- 971,979 ----
  Datum
  fmgr_sql(PG_FUNCTION_ARGS)
  {
  	SQLFunctionCachePtr fcache;
  	ErrorContextCallback sqlerrcontext;
+ 	MemoryContext oldcontext;
  	bool		randomAccess;
  	bool		lazyEvalOK;
  	bool		is_first;
*************** fmgr_sql(PG_FUNCTION_ARGS)
*** 937,949 ****
  	ListCell   *eslc;
  
  	/*
- 	 * Switch to context in which the fcache lives.  This ensures that
- 	 * parsetrees, plans, etc, will have sufficient lifetime.  The
- 	 * sub-executor is responsible for deleting per-tuple information.
- 	 */
- 	oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
- 
- 	/*
  	 * Setup error traceback support for ereport()
  	 */
  	sqlerrcontext.callback = sql_exec_error_callback;
--- 985,990 ----
*************** fmgr_sql(PG_FUNCTION_ARGS)
*** 978,997 ****
  	}
  
  	/*
! 	 * Initialize fcache (build plans) if first time through.
  	 */
  	fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
  	if (fcache == NULL)
  	{
  		init_sql_fcache(fcinfo->flinfo, PG_GET_COLLATION(), lazyEvalOK);
  		fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
  	}
! 	eslist = fcache->func_state;
  
  	/*
  	 * Find first unfinished query in function, and note whether it's the
  	 * first query.
  	 */
  	es = NULL;
  	is_first = true;
  	foreach(eslc, eslist)
--- 1019,1061 ----
  	}
  
  	/*
! 	 * Initialize fcache (build plans) if first time through; or re-initialize
! 	 * if the cache is stale.
  	 */
  	fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
+ 
+ 	if (fcache != NULL)
+ 	{
+ 		if (fcache->lxid != MyProc->lxid ||
+ 			!SubTransactionIsActive(fcache->subxid))
+ 		{
+ 			/* It's stale; unlink and delete */
+ 			fcinfo->flinfo->fn_extra = NULL;
+ 			MemoryContextDelete(fcache->fcontext);
+ 			fcache = NULL;
+ 		}
+ 	}
+ 
  	if (fcache == NULL)
  	{
  		init_sql_fcache(fcinfo->flinfo, PG_GET_COLLATION(), lazyEvalOK);
  		fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
  	}
! 
! 	/*
! 	 * Switch to context in which the fcache lives.  This ensures that our
! 	 * tuplestore etc will have sufficient lifetime.  The sub-executor is
! 	 * responsible for deleting per-tuple information.	(XXX in the case of a
! 	 * long-lived FmgrInfo, this policy represents more memory leakage, but
! 	 * it's not entirely clear where to keep stuff instead.)
! 	 */
! 	oldcontext = MemoryContextSwitchTo(fcache->fcontext);
  
  	/*
  	 * Find first unfinished query in function, and note whether it's the
  	 * first query.
  	 */
+ 	eslist = fcache->func_state;
  	es = NULL;
  	is_first = true;
  	foreach(eslc, eslist)
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 09e6a6842c2039dc2b7c99cad74a6e83a6d9e9e6..835f6acbee0e10ee51e5a2295429efd5141e3b77 100644
*** a/src/include/access/xact.h
--- b/src/include/access/xact.h
*************** extern TransactionId GetCurrentTransacti
*** 215,220 ****
--- 215,221 ----
  extern TransactionId GetCurrentTransactionIdIfAny(void);
  extern TransactionId GetStableLatestTransactionId(void);
  extern SubTransactionId GetCurrentSubTransactionId(void);
+ extern bool SubTransactionIsActive(SubTransactionId subxid);
  extern CommandId GetCurrentCommandId(bool used);
  extern TimestampTz GetCurrentTransactionStartTimestamp(void);
  extern TimestampTz GetCurrentStatementStartTimestamp(void);
-- 
Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-bugs

Reply via email to