From 8c75ad436f75fc629b61f601ba884c8f9313c9af Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 5 Nov 2015 13:52:30 -0500
Subject: [PATCH] Fix memory leaks in PL/Python.

Previously, plpython was in the habit of allocating a lot of stuff in
TopMemoryContext, and it was very slipshod about making sure that stuff
got cleaned up; in particular, use of TopMemoryContext as fn_mcxt for
function calls represents an unfixable leak, since we generally don't
know what the called function might have allocated in fn_mcxt.  This
results in session-lifespan leakage in certain usage scenarios, as for
example in a case reported by Ed Behn back in July.

To fix, get rid of all the retail allocations in TopMemoryContext.
All long-lived allocations are now made in sub-contexts that are
associated with specific objects (either pl/python procedures, or
Python-visible objects such as cursors and plans).  We can clean these
up when the associated object is deleted.

I went so far as to get rid of PLy_malloc completely.  There were a
couple of places where it could still have been used safely, but on
the whole it was just an invitation to bad coding.

Haribabu Kommi, based on a draft patch by Heikki Linnakangas;
some further work by me
---
 src/pl/plpython/plpy_cursorobject.c  |  30 ++++--
 src/pl/plpython/plpy_cursorobject.h  |   1 +
 src/pl/plpython/plpy_exec.c          |   2 +-
 src/pl/plpython/plpy_main.c          |  39 ++++++--
 src/pl/plpython/plpy_main.h          |   3 +
 src/pl/plpython/plpy_planobject.c    |  19 ++--
 src/pl/plpython/plpy_planobject.h    |   1 +
 src/pl/plpython/plpy_procedure.c     | 118 ++++++++++-------------
 src/pl/plpython/plpy_procedure.h     |   2 +
 src/pl/plpython/plpy_spi.c           |  30 ++++--
 src/pl/plpython/plpy_subxactobject.c |  15 ++-
 src/pl/plpython/plpy_typeio.c        | 137 +++++++++++++--------------
 src/pl/plpython/plpy_typeio.h        |   6 +-
 src/pl/plpython/plpy_util.c          |  36 -------
 src/pl/plpython/plpy_util.h          |   5 -
 15 files changed, 221 insertions(+), 223 deletions(-)

diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c
index 2c458d35fdb..103571ba15c 100644
--- a/src/pl/plpython/plpy_cursorobject.c
+++ b/src/pl/plpython/plpy_cursorobject.c
@@ -8,6 +8,7 @@
 
 #include "access/xact.h"
 #include "mb/pg_wchar.h"
+#include "utils/memutils.h"
 
 #include "plpython.h"
 
@@ -111,7 +112,12 @@ PLy_cursor_query(const char *query)
 		return NULL;
 	cursor->portalname = NULL;
 	cursor->closed = false;
-	PLy_typeinfo_init(&cursor->result);
+	cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
+										 "PL/Python cursor context",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+	PLy_typeinfo_init(&cursor->result, cursor->mcxt);
 
 	oldcontext = CurrentMemoryContext;
 	oldowner = CurrentResourceOwner;
@@ -139,7 +145,7 @@ PLy_cursor_query(const char *query)
 			elog(ERROR, "SPI_cursor_open() failed: %s",
 				 SPI_result_code_string(SPI_result));
 
-		cursor->portalname = PLy_strdup(portal->name);
+		cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name);
 
 		PLy_spi_subtransaction_commit(oldcontext, oldowner);
 	}
@@ -200,7 +206,12 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
 		return NULL;
 	cursor->portalname = NULL;
 	cursor->closed = false;
-	PLy_typeinfo_init(&cursor->result);
+	cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
+										 "PL/Python cursor context",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+	PLy_typeinfo_init(&cursor->result, cursor->mcxt);
 
 	oldcontext = CurrentMemoryContext;
 	oldowner = CurrentResourceOwner;
@@ -261,7 +272,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
 			elog(ERROR, "SPI_cursor_open() failed: %s",
 				 SPI_result_code_string(SPI_result));
 
-		cursor->portalname = PLy_strdup(portal->name);
+		cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name);
 
 		PLy_spi_subtransaction_commit(oldcontext, oldowner);
 	}
@@ -315,12 +326,13 @@ PLy_cursor_dealloc(PyObject *arg)
 
 		if (PortalIsValid(portal))
 			SPI_cursor_close(portal);
+		cursor->closed = true;
+	}
+	if (cursor->mcxt)
+	{
+		MemoryContextDelete(cursor->mcxt);
+		cursor->mcxt = NULL;
 	}
-
-	PLy_free(cursor->portalname);
-	cursor->portalname = NULL;
-
-	PLy_typeinfo_dealloc(&cursor->result);
 	arg->ob_type->tp_free(arg);
 }
 
diff --git a/src/pl/plpython/plpy_cursorobject.h b/src/pl/plpython/plpy_cursorobject.h
index 3c28f4f8e71..c73033c486b 100644
--- a/src/pl/plpython/plpy_cursorobject.h
+++ b/src/pl/plpython/plpy_cursorobject.h
@@ -14,6 +14,7 @@ typedef struct PLyCursorObject
 	char	   *portalname;
 	PLyTypeInfo result;
 	bool		closed;
+	MemoryContext mcxt;
 } PLyCursorObject;
 
 extern void PLy_cursor_init_type(void);
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 3ccebe403e4..24aed011e4b 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -852,6 +852,6 @@ PLy_abort_open_subtransactions(int save_subxact_level)
 
 		MemoryContextSwitchTo(subtransactiondata->oldcontext);
 		CurrentResourceOwner = subtransactiondata->oldowner;
-		PLy_free(subtransactiondata);
+		pfree(subtransactiondata);
 	}
 }
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 63a284e2384..3c2ebfa16af 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -277,7 +277,12 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
 	flinfo.fn_mcxt = CurrentMemoryContext;
 
 	MemSet(&proc, 0, sizeof(PLyProcedure));
-	proc.pyname = PLy_strdup("__plpython_inline_block");
+	proc.mcxt = AllocSetContextCreate(TopMemoryContext,
+									  "__plpython_inline_block",
+									  ALLOCSET_DEFAULT_MINSIZE,
+									  ALLOCSET_DEFAULT_INITSIZE,
+									  ALLOCSET_DEFAULT_MAXSIZE);
+	proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
 	proc.langid = codeblock->langOid;
 	proc.result.out.d.typoid = VOIDOID;
 
@@ -364,17 +369,32 @@ PLy_current_execution_context(void)
 	return PLy_execution_contexts;
 }
 
+MemoryContext
+PLy_get_scratch_context(PLyExecutionContext *context)
+{
+	/*
+	 * A scratch context might never be needed in a given plpython procedure,
+	 * so allocate it on first request.
+	 */
+	if (context->scratch_ctx == NULL)
+		context->scratch_ctx =
+			AllocSetContextCreate(TopTransactionContext,
+								  "PL/Python scratch context",
+								  ALLOCSET_DEFAULT_MINSIZE,
+								  ALLOCSET_DEFAULT_INITSIZE,
+								  ALLOCSET_DEFAULT_MAXSIZE);
+	return context->scratch_ctx;
+}
+
 static PLyExecutionContext *
 PLy_push_execution_context(void)
 {
-	PLyExecutionContext *context = PLy_malloc(sizeof(PLyExecutionContext));
+	PLyExecutionContext *context;
 
+	context = (PLyExecutionContext *)
+		MemoryContextAlloc(TopTransactionContext, sizeof(PLyExecutionContext));
 	context->curr_proc = NULL;
-	context->scratch_ctx = AllocSetContextCreate(TopTransactionContext,
-												 "PL/Python scratch context",
-												 ALLOCSET_DEFAULT_MINSIZE,
-												 ALLOCSET_DEFAULT_INITSIZE,
-												 ALLOCSET_DEFAULT_MAXSIZE);
+	context->scratch_ctx = NULL;
 	context->next = PLy_execution_contexts;
 	PLy_execution_contexts = context;
 	return context;
@@ -390,6 +410,7 @@ PLy_pop_execution_context(void)
 
 	PLy_execution_contexts = context->next;
 
-	MemoryContextDelete(context->scratch_ctx);
-	PLy_free(context);
+	if (context->scratch_ctx)
+		MemoryContextDelete(context->scratch_ctx);
+	pfree(context);
 }
diff --git a/src/pl/plpython/plpy_main.h b/src/pl/plpython/plpy_main.h
index b13e2c21a11..10426c43236 100644
--- a/src/pl/plpython/plpy_main.h
+++ b/src/pl/plpython/plpy_main.h
@@ -25,4 +25,7 @@ typedef struct PLyExecutionContext
 /* Get the current execution context */
 extern PLyExecutionContext *PLy_current_execution_context(void);
 
+/* Get the scratch memory context for specified execution context */
+extern MemoryContext PLy_get_scratch_context(PLyExecutionContext *context);
+
 #endif   /* PLPY_MAIN_H */
diff --git a/src/pl/plpython/plpy_planobject.c b/src/pl/plpython/plpy_planobject.c
index 8305bd68e96..a9040efb502 100644
--- a/src/pl/plpython/plpy_planobject.c
+++ b/src/pl/plpython/plpy_planobject.c
@@ -11,6 +11,7 @@
 #include "plpy_planobject.h"
 
 #include "plpy_elog.h"
+#include "utils/memutils.h"
 
 
 static void PLy_plan_dealloc(PyObject *arg);
@@ -80,6 +81,7 @@ PLy_plan_new(void)
 	ob->types = NULL;
 	ob->values = NULL;
 	ob->args = NULL;
+	ob->mcxt = NULL;
 
 	return (PyObject *) ob;
 }
@@ -96,20 +98,15 @@ PLy_plan_dealloc(PyObject *arg)
 	PLyPlanObject *ob = (PLyPlanObject *) arg;
 
 	if (ob->plan)
+	{
 		SPI_freeplan(ob->plan);
-	if (ob->types)
-		PLy_free(ob->types);
-	if (ob->values)
-		PLy_free(ob->values);
-	if (ob->args)
+		ob->plan = NULL;
+	}
+	if (ob->mcxt)
 	{
-		int			i;
-
-		for (i = 0; i < ob->nargs; i++)
-			PLy_typeinfo_dealloc(&ob->args[i]);
-		PLy_free(ob->args);
+		MemoryContextDelete(ob->mcxt);
+		ob->mcxt = NULL;
 	}
-
 	arg->ob_type->tp_free(arg);
 }
 
diff --git a/src/pl/plpython/plpy_planobject.h b/src/pl/plpython/plpy_planobject.h
index 7a89ffc2c18..c67559266ec 100644
--- a/src/pl/plpython/plpy_planobject.h
+++ b/src/pl/plpython/plpy_planobject.h
@@ -17,6 +17,7 @@ typedef struct PLyPlanObject
 	Oid		   *types;
 	Datum	   *values;
 	PLyTypeInfo *args;
+	MemoryContext mcxt;
 } PLyPlanObject;
 
 extern void PLy_plan_init_type(void);
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index 16ff84560bb..e1f56209ef0 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -112,8 +112,9 @@ PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger)
 		else if (!PLy_procedure_valid(proc, procTup))
 		{
 			/* Found it, but it's invalid, free and reuse the cache entry */
-			PLy_procedure_delete(proc);
-			PLy_free(proc);
+			entry->proc = NULL;
+			if (proc)
+				PLy_procedure_delete(proc);
 			proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
 			entry->proc = proc;
 		}
@@ -142,11 +143,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 	char		procName[NAMEDATALEN + 256];
 	Form_pg_proc procStruct;
 	PLyProcedure *volatile proc;
-	char	   *volatile procSource = NULL;
-	Datum		prosrcdatum;
-	bool		isnull;
-	int			i,
-				rv;
+	MemoryContext cxt;
+	MemoryContext oldcxt;
+	int			rv;
 
 	procStruct = (Form_pg_proc) GETSTRUCT(procTup);
 	rv = snprintf(procName, sizeof(procName),
@@ -156,38 +155,48 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 	if (rv >= sizeof(procName) || rv < 0)
 		elog(ERROR, "procedure name would overrun buffer");
 
-	proc = PLy_malloc(sizeof(PLyProcedure));
-	proc->proname = PLy_strdup(NameStr(procStruct->proname));
-	proc->pyname = PLy_strdup(procName);
-	proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
-	proc->fn_tid = procTup->t_self;
-	/* Remember if function is STABLE/IMMUTABLE */
-	proc->fn_readonly =
-		(procStruct->provolatile != PROVOLATILE_VOLATILE);
-	PLy_typeinfo_init(&proc->result);
-	for (i = 0; i < FUNC_MAX_ARGS; i++)
-		PLy_typeinfo_init(&proc->args[i]);
-	proc->nargs = 0;
-	proc->langid = procStruct->prolang;
-	{
-		MemoryContext oldcxt;
+	cxt = AllocSetContextCreate(TopMemoryContext,
+								procName,
+								ALLOCSET_DEFAULT_MINSIZE,
+								ALLOCSET_DEFAULT_INITSIZE,
+								ALLOCSET_DEFAULT_MAXSIZE);
 
-		Datum		protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
-										  Anum_pg_proc_protrftypes, &isnull);
+	oldcxt = MemoryContextSwitchTo(cxt);
 
-		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
-		proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
-		MemoryContextSwitchTo(oldcxt);
-	}
-	proc->code = proc->statics = NULL;
-	proc->globals = NULL;
-	proc->is_setof = procStruct->proretset;
-	proc->setof = NULL;
-	proc->src = NULL;
-	proc->argnames = NULL;
+	proc = (PLyProcedure *) palloc0(sizeof(PLyProcedure));
+	proc->mcxt = cxt;
 
 	PG_TRY();
 	{
+		Datum		protrftypes_datum;
+		Datum		prosrcdatum;
+		bool		isnull;
+		char	   *procSource;
+		int			i;
+
+		proc->proname = pstrdup(NameStr(procStruct->proname));
+		proc->pyname = pstrdup(procName);
+		proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+		proc->fn_tid = procTup->t_self;
+		/* Remember if function is STABLE/IMMUTABLE */
+		proc->fn_readonly =
+			(procStruct->provolatile != PROVOLATILE_VOLATILE);
+		PLy_typeinfo_init(&proc->result, proc->mcxt);
+		for (i = 0; i < FUNC_MAX_ARGS; i++)
+			PLy_typeinfo_init(&proc->args[i], proc->mcxt);
+		proc->nargs = 0;
+		proc->langid = procStruct->prolang;
+		protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
+											Anum_pg_proc_protrftypes,
+											&isnull);
+		proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
+		proc->code = proc->statics = NULL;
+		proc->globals = NULL;
+		proc->is_setof = procStruct->proretset;
+		proc->setof = NULL;
+		proc->src = NULL;
+		proc->argnames = NULL;
+
 		/*
 		 * get information required for output conversion of the return value,
 		 * but only if this isn't a trigger.
@@ -250,8 +259,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 			Oid		   *types;
 			char	  **names,
 					   *modes;
-			int			i,
-						pos,
+			int			pos,
 						total;
 
 			/* extract argument type info from the pg_proc tuple */
@@ -271,7 +279,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 				}
 			}
 
-			proc->argnames = (char **) PLy_malloc0(sizeof(char *) * proc->nargs);
+			proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs);
 			for (i = pos = 0; i < total; i++)
 			{
 				HeapTuple	argTypeTup;
@@ -314,7 +322,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 				}
 
 				/* get argument name */
-				proc->argnames[pos] = names ? PLy_strdup(names[i]) : NULL;
+				proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
 
 				ReleaseSysCache(argTypeTup);
 
@@ -334,18 +342,16 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 		PLy_procedure_compile(proc, procSource);
 
 		pfree(procSource);
-		procSource = NULL;
 	}
 	PG_CATCH();
 	{
+		MemoryContextSwitchTo(oldcxt);
 		PLy_procedure_delete(proc);
-		if (procSource)
-			pfree(procSource);
-
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
 
+	MemoryContextSwitchTo(oldcxt);
 	return proc;
 }
 
@@ -372,7 +378,7 @@ PLy_procedure_compile(PLyProcedure *proc, const char *src)
 	 */
 	msrc = PLy_procedure_munge_source(proc->pyname, src);
 	/* Save the mangled source for later inclusion in tracebacks */
-	proc->src = PLy_strdup(msrc);
+	proc->src = MemoryContextStrdup(proc->mcxt, msrc);
 	crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
 	pfree(msrc);
 
@@ -404,31 +410,10 @@ PLy_procedure_compile(PLyProcedure *proc, const char *src)
 void
 PLy_procedure_delete(PLyProcedure *proc)
 {
-	int			i;
-
 	Py_XDECREF(proc->code);
 	Py_XDECREF(proc->statics);
 	Py_XDECREF(proc->globals);
-	if (proc->proname)
-		PLy_free(proc->proname);
-	if (proc->pyname)
-		PLy_free(proc->pyname);
-	for (i = 0; i < proc->nargs; i++)
-	{
-		if (proc->args[i].is_rowtype == 1)
-		{
-			if (proc->args[i].in.r.atts)
-				PLy_free(proc->args[i].in.r.atts);
-			if (proc->args[i].out.r.atts)
-				PLy_free(proc->args[i].out.r.atts);
-		}
-		if (proc->argnames && proc->argnames[i])
-			PLy_free(proc->argnames[i]);
-	}
-	if (proc->src)
-		PLy_free(proc->src);
-	if (proc->argnames)
-		PLy_free(proc->argnames);
+	MemoryContextDelete(proc->mcxt);
 }
 
 /*
@@ -479,7 +464,8 @@ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
 	int			i;
 	bool		valid;
 
-	Assert(proc != NULL);
+	if (proc == NULL)
+		return false;
 
 	/* If the pg_proc tuple has changed, it's not valid */
 	if (!(proc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h
index 6d4b00ba7c8..9fc8db07972 100644
--- a/src/pl/plpython/plpy_procedure.h
+++ b/src/pl/plpython/plpy_procedure.h
@@ -14,6 +14,8 @@ extern void init_procedure_caches(void);
 /* cached procedure data */
 typedef struct PLyProcedure
 {
+	MemoryContext mcxt;			/* context holding this PLyProcedure and its
+								 * subsidiary data */
 	char	   *proname;		/* SQL name of procedure */
 	char	   *pyname;			/* Python name of procedure */
 	TransactionId fn_xmin;
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index d0e255f8359..58e78ecebcb 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -61,12 +61,21 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
 	if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL)
 		return NULL;
 
+	plan->mcxt = AllocSetContextCreate(TopMemoryContext,
+									   "PL/Python plan context",
+									   ALLOCSET_DEFAULT_MINSIZE,
+									   ALLOCSET_DEFAULT_INITSIZE,
+									   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcontext = MemoryContextSwitchTo(plan->mcxt);
+
 	nargs = list ? PySequence_Length(list) : 0;
 
 	plan->nargs = nargs;
-	plan->types = nargs ? PLy_malloc(sizeof(Oid) * nargs) : NULL;
-	plan->values = nargs ? PLy_malloc(sizeof(Datum) * nargs) : NULL;
-	plan->args = nargs ? PLy_malloc(sizeof(PLyTypeInfo) * nargs) : NULL;
+	plan->types = nargs ? palloc(sizeof(Oid) * nargs) : NULL;
+	plan->values = nargs ? palloc(sizeof(Datum) * nargs) : NULL;
+	plan->args = nargs ? palloc(sizeof(PLyTypeInfo) * nargs) : NULL;
+
+	MemoryContextSwitchTo(oldcontext);
 
 	oldcontext = CurrentMemoryContext;
 	oldowner = CurrentResourceOwner;
@@ -84,7 +93,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
 		 */
 		for (i = 0; i < nargs; i++)
 		{
-			PLy_typeinfo_init(&plan->args[i]);
+			PLy_typeinfo_init(&plan->args[i], plan->mcxt);
 			plan->values[i] = PointerGetDatum(NULL);
 		}
 
@@ -391,10 +400,17 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
 	{
 		PLyTypeInfo args;
 		int			i;
+		MemoryContext cxt;
 
 		Py_DECREF(result->nrows);
 		result->nrows = PyInt_FromLong(rows);
-		PLy_typeinfo_init(&args);
+
+		cxt = AllocSetContextCreate(CurrentMemoryContext,
+									"PL/Python temp context",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+		PLy_typeinfo_init(&args, cxt);
 
 		oldcontext = CurrentMemoryContext;
 		PG_TRY();
@@ -432,13 +448,13 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
 		PG_CATCH();
 		{
 			MemoryContextSwitchTo(oldcontext);
-			PLy_typeinfo_dealloc(&args);
+			MemoryContextDelete(cxt);
 			Py_DECREF(result);
 			PG_RE_THROW();
 		}
 		PG_END_TRY();
 
-		PLy_typeinfo_dealloc(&args);
+		MemoryContextDelete(cxt);
 		SPI_freetuptable(tuptable);
 	}
 
diff --git a/src/pl/plpython/plpy_subxactobject.c b/src/pl/plpython/plpy_subxactobject.c
index 2e7ec4fdab4..81fb3a3a4ab 100644
--- a/src/pl/plpython/plpy_subxactobject.c
+++ b/src/pl/plpython/plpy_subxactobject.c
@@ -8,6 +8,7 @@
 
 #include "access/xact.h"
 #include "executor/spi.h"
+#include "utils/memutils.h"
 
 #include "plpython.h"
 
@@ -132,16 +133,22 @@ PLy_subtransaction_enter(PyObject *self, PyObject *unused)
 	subxact->started = true;
 	oldcontext = CurrentMemoryContext;
 
-	subxactdata = PLy_malloc(sizeof(*subxactdata));
+	subxactdata = (PLySubtransactionData *)
+		MemoryContextAlloc(TopTransactionContext,
+						   sizeof(PLySubtransactionData));
+
 	subxactdata->oldcontext = oldcontext;
 	subxactdata->oldowner = CurrentResourceOwner;
 
 	BeginInternalSubTransaction(NULL);
-	/* Do not want to leave the previous memory context */
-	MemoryContextSwitchTo(oldcontext);
 
+	/* Be sure that cells of explicit_subtransactions list are long-lived */
+	MemoryContextSwitchTo(TopTransactionContext);
 	explicit_subtransactions = lcons(subxactdata, explicit_subtransactions);
 
+	/* Caller wants to stay in original memory context */
+	MemoryContextSwitchTo(oldcontext);
+
 	Py_INCREF(self);
 	return self;
 }
@@ -204,7 +211,7 @@ PLy_subtransaction_exit(PyObject *self, PyObject *args)
 
 	MemoryContextSwitchTo(subxactdata->oldcontext);
 	CurrentResourceOwner = subxactdata->oldowner;
-	PLy_free(subxactdata);
+	pfree(subxactdata);
 
 	/*
 	 * AtEOSubXact_SPI() should not have popped any SPI context, but just in
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index 05add6e2ce8..7ad7a4400a5 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -29,8 +29,8 @@
 
 
 /* I/O function caching */
-static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
-static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *trftypes);
+static void PLy_input_datum_func2(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
+static void PLy_output_datum_func2(PLyObToDatum *arg, MemoryContext arg_mcxt, HeapTuple typeTup, Oid langid, List *trftypes);
 
 /* conversion from Datums to Python objects */
 static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
@@ -60,11 +60,8 @@ static Datum PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject
 static Datum PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence);
 static Datum PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object);
 
-/* make allocations in the TopMemoryContext */
-static void perm_fmgr_info(Oid functionId, FmgrInfo *finfo);
-
 void
-PLy_typeinfo_init(PLyTypeInfo *arg)
+PLy_typeinfo_init(PLyTypeInfo *arg, MemoryContext mcxt)
 {
 	arg->is_rowtype = -1;
 	arg->in.r.natts = arg->out.r.natts = 0;
@@ -73,30 +70,7 @@ PLy_typeinfo_init(PLyTypeInfo *arg)
 	arg->typ_relid = InvalidOid;
 	arg->typrel_xmin = InvalidTransactionId;
 	ItemPointerSetInvalid(&arg->typrel_tid);
-}
-
-void
-PLy_typeinfo_dealloc(PLyTypeInfo *arg)
-{
-	if (arg->is_rowtype == 1)
-	{
-		int			i;
-
-		for (i = 0; i < arg->in.r.natts; i++)
-		{
-			if (arg->in.r.atts[i].elm != NULL)
-				PLy_free(arg->in.r.atts[i].elm);
-		}
-		if (arg->in.r.atts)
-			PLy_free(arg->in.r.atts);
-		for (i = 0; i < arg->out.r.natts; i++)
-		{
-			if (arg->out.r.atts[i].elm != NULL)
-				PLy_free(arg->out.r.atts[i].elm);
-		}
-		if (arg->out.r.atts)
-			PLy_free(arg->out.r.atts);
-	}
+	arg->mcxt = mcxt;
 }
 
 /*
@@ -109,7 +83,7 @@ PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langi
 	if (arg->is_rowtype > 0)
 		elog(ERROR, "PLyTypeInfo struct is initialized for Tuple");
 	arg->is_rowtype = 0;
-	PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup, langid, trftypes);
+	PLy_input_datum_func2(&(arg->in.d), arg->mcxt, typeOid, typeTup, langid, trftypes);
 }
 
 void
@@ -118,7 +92,7 @@ PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trf
 	if (arg->is_rowtype > 0)
 		elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple");
 	arg->is_rowtype = 0;
-	PLy_output_datum_func2(&(arg->out.d), typeTup, langid, trftypes);
+	PLy_output_datum_func2(&(arg->out.d), arg->mcxt, typeTup, langid, trftypes);
 }
 
 void
@@ -126,6 +100,9 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
 {
 	int			i;
 	PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+	MemoryContext oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(arg->mcxt);
 
 	if (arg->is_rowtype == 0)
 		elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
@@ -134,9 +111,9 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
 	if (arg->in.r.natts != desc->natts)
 	{
 		if (arg->in.r.atts)
-			PLy_free(arg->in.r.atts);
+			pfree(arg->in.r.atts);
 		arg->in.r.natts = desc->natts;
-		arg->in.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb));
+		arg->in.r.atts = palloc0(desc->natts * sizeof(PLyDatumToOb));
 	}
 
 	/* Can this be an unnamed tuple? If not, then an Assert would be enough */
@@ -182,7 +159,7 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
 			elog(ERROR, "cache lookup failed for type %u",
 				 desc->attrs[i]->atttypid);
 
-		PLy_input_datum_func2(&(arg->in.r.atts[i]),
+		PLy_input_datum_func2(&(arg->in.r.atts[i]), arg->mcxt,
 							  desc->attrs[i]->atttypid,
 							  typeTup,
 							  exec_ctx->curr_proc->langid,
@@ -190,6 +167,8 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
 
 		ReleaseSysCache(typeTup);
 	}
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 void
@@ -197,6 +176,9 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
 {
 	int			i;
 	PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+	MemoryContext oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(arg->mcxt);
 
 	if (arg->is_rowtype == 0)
 		elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
@@ -205,9 +187,9 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
 	if (arg->out.r.natts != desc->natts)
 	{
 		if (arg->out.r.atts)
-			PLy_free(arg->out.r.atts);
+			pfree(arg->out.r.atts);
 		arg->out.r.natts = desc->natts;
-		arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyObToDatum));
+		arg->out.r.atts = palloc0(desc->natts * sizeof(PLyObToDatum));
 	}
 
 	Assert(OidIsValid(desc->tdtypeid));
@@ -249,12 +231,14 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
 			elog(ERROR, "cache lookup failed for type %u",
 				 desc->attrs[i]->atttypid);
 
-		PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup,
+		PLy_output_datum_func2(&(arg->out.r.atts[i]), arg->mcxt, typeTup,
 							   exec_ctx->curr_proc->langid,
 							   exec_ctx->curr_proc->trftypes);
 
 		ReleaseSysCache(typeTup);
 	}
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 void
@@ -291,8 +275,8 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
 {
 	PyObject   *volatile dict;
 	PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+	MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
 	MemoryContext oldcontext = CurrentMemoryContext;
-	int			i;
 
 	if (info->is_rowtype != 1)
 		elog(ERROR, "PLyTypeInfo structure describes a datum");
@@ -303,11 +287,13 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
 
 	PG_TRY();
 	{
+		int			i;
+
 		/*
 		 * Do the work in the scratch context to avoid leaking memory from the
 		 * datatype output function calls.
 		 */
-		MemoryContextSwitchTo(exec_ctx->scratch_ctx);
+		MemoryContextSwitchTo(scratch_context);
 		for (i = 0; i < info->in.r.natts; i++)
 		{
 			char	   *key;
@@ -331,7 +317,7 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
 			}
 		}
 		MemoryContextSwitchTo(oldcontext);
-		MemoryContextReset(exec_ctx->scratch_ctx);
+		MemoryContextReset(scratch_context);
 	}
 	PG_CATCH();
 	{
@@ -370,14 +356,17 @@ PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
 }
 
 static void
-PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *trftypes)
+PLy_output_datum_func2(PLyObToDatum *arg, MemoryContext arg_mcxt, HeapTuple typeTup, Oid langid, List *trftypes)
 {
 	Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
 	Oid			element_type;
 	Oid			base_type;
 	Oid			funcid;
+	MemoryContext oldcxt;
 
-	perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
+	oldcxt = MemoryContextSwitchTo(arg_mcxt);
+
+	fmgr_info_cxt(typeStruct->typinput, &arg->typfunc, arg_mcxt);
 	arg->typoid = HeapTupleGetOid(typeTup);
 	arg->typmod = -1;
 	arg->typioparam = getTypeIOParam(typeTup);
@@ -394,7 +383,7 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *t
 	if ((funcid = get_transform_tosql(base_type, langid, trftypes)))
 	{
 		arg->func = PLyObject_ToTransform;
-		perm_fmgr_info(funcid, &arg->typtransform);
+		fmgr_info_cxt(funcid, &arg->typtransform, arg_mcxt);
 	}
 	else if (typeStruct->typtype == TYPTYPE_COMPOSITE)
 	{
@@ -422,7 +411,7 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *t
 		if (type_is_rowtype(element_type))
 			arg->func = PLyObject_ToComposite;
 
-		arg->elm = PLy_malloc0(sizeof(*arg->elm));
+		arg->elm = palloc0(sizeof(*arg->elm));
 		arg->elm->func = arg->func;
 		arg->elm->typtransform = arg->typtransform;
 		arg->func = PLySequence_ToArray;
@@ -432,20 +421,25 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *t
 		get_type_io_data(element_type, IOFunc_input,
 						 &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
 						 &arg->elm->typioparam, &funcid);
-		perm_fmgr_info(funcid, &arg->elm->typfunc);
+		fmgr_info_cxt(funcid, &arg->elm->typfunc, arg_mcxt);
 	}
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 static void
-PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes)
+PLy_input_datum_func2(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes)
 {
 	Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
 	Oid			element_type;
 	Oid			base_type;
 	Oid			funcid;
+	MemoryContext oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(arg_mcxt);
 
 	/* Get the type's conversion information */
-	perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
+	fmgr_info_cxt(typeStruct->typoutput, &arg->typfunc, arg_mcxt);
 	arg->typoid = HeapTupleGetOid(typeTup);
 	arg->typmod = -1;
 	arg->typioparam = getTypeIOParam(typeTup);
@@ -461,7 +455,7 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid lan
 	if ((funcid = get_transform_fromsql(base_type, langid, trftypes)))
 	{
 		arg->func = PLyObject_FromTransform;
-		perm_fmgr_info(funcid, &arg->typtransform);
+		fmgr_info_cxt(funcid, &arg->typtransform, arg_mcxt);
 	}
 	else
 		switch (base_type)
@@ -503,7 +497,7 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid lan
 		char		dummy_delim;
 		Oid			funcid;
 
-		arg->elm = PLy_malloc0(sizeof(*arg->elm));
+		arg->elm = palloc0(sizeof(*arg->elm));
 		arg->elm->func = arg->func;
 		arg->elm->typtransform = arg->typtransform;
 		arg->func = PLyList_FromArray;
@@ -512,8 +506,10 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid lan
 		get_type_io_data(element_type, IOFunc_output,
 						 &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
 						 &arg->elm->typioparam, &funcid);
-		perm_fmgr_info(funcid, &arg->elm->typfunc);
+		fmgr_info_cxt(funcid, &arg->elm->typfunc, arg_mcxt);
 	}
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 static PyObject *
@@ -752,13 +748,19 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
 	Datum		rv;
 	PLyTypeInfo info;
 	TupleDesc	desc;
+	MemoryContext cxt;
 
 	if (typmod != -1)
 		elog(ERROR, "received unnamed record type as input");
 
 	/* Create a dummy PLyTypeInfo */
+	cxt = AllocSetContextCreate(CurrentMemoryContext,
+								"PL/Python temp context",
+								ALLOCSET_DEFAULT_MINSIZE,
+								ALLOCSET_DEFAULT_INITSIZE,
+								ALLOCSET_DEFAULT_MAXSIZE);
 	MemSet(&info, 0, sizeof(PLyTypeInfo));
-	PLy_typeinfo_init(&info);
+	PLy_typeinfo_init(&info, cxt);
 	/* Mark it as needing output routines lookup */
 	info.is_rowtype = 2;
 
@@ -774,7 +776,7 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
 
 	ReleaseTupleDesc(desc);
 
-	PLy_typeinfo_dealloc(&info);
+	MemoryContextDelete(cxt);
 
 	return rv;
 }
@@ -916,16 +918,22 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
 	HeapTuple	typeTup;
 	PLyTypeInfo locinfo;
 	PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+	MemoryContext cxt;
 
 	/* Create a dummy PLyTypeInfo */
+	cxt = AllocSetContextCreate(CurrentMemoryContext,
+								"PL/Python temp context",
+								ALLOCSET_DEFAULT_MINSIZE,
+								ALLOCSET_DEFAULT_INITSIZE,
+								ALLOCSET_DEFAULT_MAXSIZE);
 	MemSet(&locinfo, 0, sizeof(PLyTypeInfo));
-	PLy_typeinfo_init(&locinfo);
+	PLy_typeinfo_init(&locinfo, cxt);
 
 	typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid));
 	if (!HeapTupleIsValid(typeTup))
 		elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid);
 
-	PLy_output_datum_func2(&locinfo.out.d, typeTup,
+	PLy_output_datum_func2(&locinfo.out.d, locinfo.mcxt, typeTup,
 						   exec_ctx->curr_proc->langid,
 						   exec_ctx->curr_proc->trftypes);
 
@@ -933,7 +941,7 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
 
 	result = PLyObject_ToDatum(&locinfo.out.d, desc->tdtypmod, string);
 
-	PLy_typeinfo_dealloc(&locinfo);
+	MemoryContextDelete(cxt);
 
 	return result;
 }
@@ -1177,20 +1185,3 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
 
 	return result;
 }
-
-/*
- * This routine is a crock, and so is everyplace that calls it.  The problem
- * is that the cached form of plpython functions/queries is allocated permanently
- * (mostly via malloc()) and never released until backend exit.  Subsidiary
- * data structures such as fmgr info records therefore must live forever
- * as well.  A better implementation would store all this stuff in a per-
- * function memory context that could be reclaimed at need.  In the meantime,
- * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever
- * it might allocate, and whatever the eventual function might allocate using
- * fn_mcxt, will live forever too.
- */
-static void
-perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
-{
-	fmgr_info_cxt(functionId, finfo, TopMemoryContext);
-}
diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h
index b01151b0fc0..29fff61dc56 100644
--- a/src/pl/plpython/plpy_typeio.h
+++ b/src/pl/plpython/plpy_typeio.h
@@ -88,10 +88,12 @@ typedef struct PLyTypeInfo
 	Oid			typ_relid;
 	TransactionId typrel_xmin;
 	ItemPointerData typrel_tid;
+
+	/* context for subsidiary data (doesn't belong to this struct though) */
+	MemoryContext mcxt;
 } PLyTypeInfo;
 
-extern void PLy_typeinfo_init(PLyTypeInfo *arg);
-extern void PLy_typeinfo_dealloc(PLyTypeInfo *arg);
+extern void PLy_typeinfo_init(PLyTypeInfo *arg, MemoryContext mcxt);
 
 extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
 extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes);
diff --git a/src/pl/plpython/plpy_util.c b/src/pl/plpython/plpy_util.c
index b6b92557678..f2d59491376 100644
--- a/src/pl/plpython/plpy_util.c
+++ b/src/pl/plpython/plpy_util.c
@@ -17,42 +17,6 @@
 #include "plpy_elog.h"
 
 
-void *
-PLy_malloc(size_t bytes)
-{
-	/* We need our allocations to be long-lived, so use TopMemoryContext */
-	return MemoryContextAlloc(TopMemoryContext, bytes);
-}
-
-void *
-PLy_malloc0(size_t bytes)
-{
-	void	   *ptr = PLy_malloc(bytes);
-
-	MemSet(ptr, 0, bytes);
-	return ptr;
-}
-
-char *
-PLy_strdup(const char *str)
-{
-	char	   *result;
-	size_t		len;
-
-	len = strlen(str) + 1;
-	result = PLy_malloc(len);
-	memcpy(result, str, len);
-
-	return result;
-}
-
-/* define this away */
-void
-PLy_free(void *ptr)
-{
-	pfree(ptr);
-}
-
 /*
  * Convert a Python unicode object to a Python string/bytes object in
  * PostgreSQL server encoding.  Reference ownership is passed to the
diff --git a/src/pl/plpython/plpy_util.h b/src/pl/plpython/plpy_util.h
index 4c29f9aea3c..66c5ccf8ac3 100644
--- a/src/pl/plpython/plpy_util.h
+++ b/src/pl/plpython/plpy_util.h
@@ -6,11 +6,6 @@
 #ifndef PLPY_UTIL_H
 #define PLPY_UTIL_H
 
-extern void *PLy_malloc(size_t bytes);
-extern void *PLy_malloc0(size_t bytes);
-extern char *PLy_strdup(const char *str);
-extern void PLy_free(void *ptr);
-
 extern PyObject *PLyUnicode_Bytes(PyObject *unicode);
 extern char *PLyUnicode_AsString(PyObject *unicode);
 
-- 
GitLab