From 257cccbe5ea9f20988ddaa6654a6b1ccf1925620 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 16 Sep 2004 20:17:49 +0000
Subject: [PATCH] Add some marginal tweaks to eliminate memory leakages
 associated with subtransactions.  Trivial subxacts (such as a plpgsql
 exception block containing no database access) now demonstrably leak zero
 bytes.

---
 src/backend/access/transam/xact.c | 57 +++++++++++++++++++++++++++----
 src/backend/executor/spi.c        | 23 +++++++++++--
 src/backend/utils/mmgr/aset.c     | 24 ++++++++++++-
 src/backend/utils/mmgr/mcxt.c     | 24 +++++++++++--
 src/include/nodes/memnodes.h      |  3 +-
 src/include/utils/memutils.h      |  3 +-
 6 files changed, 118 insertions(+), 16 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index fe99ba15f36..17db7dd78d5 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.189 2004/09/16 16:58:26 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.190 2004/09/16 20:17:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -861,9 +861,6 @@ AtCommit_Memory(void)
 
 /*
  * AtSubCommit_Memory
- *
- * We do not throw away the child's CurTransactionContext, since the data
- * it contains will be needed at upper commit.
  */
 static void
 AtSubCommit_Memory(void)
@@ -875,6 +872,18 @@ AtSubCommit_Memory(void)
 	/* Return to parent transaction level's memory context. */
 	CurTransactionContext = s->parent->curTransactionContext;
 	MemoryContextSwitchTo(CurTransactionContext);
+
+	/*
+	 * Ordinarily we cannot throw away the child's CurTransactionContext,
+	 * since the data it contains will be needed at upper commit.  However,
+	 * if there isn't actually anything in it, we can throw it away.  This
+	 * avoids a small memory leak in the common case of "trivial" subxacts.
+	 */
+	if (MemoryContextIsEmpty(s->curTransactionContext))
+	{
+		MemoryContextDelete(s->curTransactionContext);
+		s->curTransactionContext = NULL;
+	}
 }
 
 /*
@@ -890,13 +899,27 @@ AtSubCommit_childXids(void)
 
 	Assert(s->parent != NULL);
 
-	old_cxt = MemoryContextSwitchTo(s->parent->curTransactionContext);
+	/*
+	 * We keep the child-XID lists in TopTransactionContext; this avoids
+	 * setting up child-transaction contexts for what might be just a few
+	 * bytes of grandchild XIDs.
+	 */
+	old_cxt = MemoryContextSwitchTo(TopTransactionContext);
 
 	s->parent->childXids = lappend_xid(s->parent->childXids,
 									   s->transactionId);
 
-	s->parent->childXids = list_concat(s->parent->childXids, s->childXids);
-	s->childXids = NIL;			/* ensure list not doubly referenced */
+	if (s->childXids != NIL)
+	{
+		s->parent->childXids = list_concat(s->parent->childXids,
+										   s->childXids);
+		/*
+		 * list_concat doesn't free the list header for the second list;
+		 * do so here to avoid memory leakage (kluge)
+		 */
+		pfree(s->childXids);
+		s->childXids = NIL;
+	}
 
 	MemoryContextSwitchTo(old_cxt);
 }
@@ -1092,6 +1115,23 @@ AtSubAbort_Memory(void)
 	MemoryContextSwitchTo(TopTransactionContext);
 }
 
+/*
+ * AtSubAbort_childXids
+ */
+static void
+AtSubAbort_childXids(void)
+{
+	TransactionState s = CurrentTransactionState;
+
+	/*
+	 * We keep the child-XID lists in TopTransactionContext (see
+	 * AtSubCommit_childXids).  This means we'd better free the list
+	 * explicitly at abort to avoid leakage.
+	 */
+	list_free(s->childXids);
+	s->childXids = NIL;
+}
+
 /*
  * RecordSubTransactionAbort
  */
@@ -3317,7 +3357,10 @@ AbortSubTransaction(void)
 
 		/* Advertise the fact that we aborted in pg_clog. */
 		if (TransactionIdIsValid(s->transactionId))
+		{
 			RecordSubTransactionAbort();
+			AtSubAbort_childXids();
+		}
 
 		/* Post-abort cleanup */
 		CallSubXactCallbacks(SUBXACT_EVENT_ABORT_SUB, s->subTransactionId,
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index ab26a91c1aa..3845b94eb92 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.128 2004/09/16 16:58:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.129 2004/09/16 20:17:20 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -104,6 +104,8 @@ SPI_connect(void)
 	_SPI_current = &(_SPI_stack[_SPI_connected]);
 	_SPI_current->processed = 0;
 	_SPI_current->tuptable = NULL;
+	_SPI_current->procCxt = NULL; /* in case we fail to create 'em */
+	_SPI_current->execCxt = NULL;
 	_SPI_current->connectSubid = GetCurrentSubTransactionId();
 
 	/*
@@ -144,7 +146,9 @@ SPI_finish(void)
 
 	/* Release memory used in procedure call */
 	MemoryContextDelete(_SPI_current->execCxt);
+	_SPI_current->execCxt = NULL;
 	MemoryContextDelete(_SPI_current->procCxt);
+	_SPI_current->procCxt = NULL;
 
 	/*
 	 * Reset result variables, especially SPI_tuptable which is probably
@@ -214,11 +218,24 @@ AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid)
 
 		found = true;
 
+		/*
+		 * Release procedure memory explicitly (see note in SPI_connect)
+		 */
+		if (connection->execCxt)
+		{
+			MemoryContextDelete(connection->execCxt);
+			connection->execCxt = NULL;
+		}
+		if (connection->procCxt)
+		{
+			MemoryContextDelete(connection->procCxt);
+			connection->procCxt = NULL;
+		}
+
 		/*
 		 * Pop the stack entry and reset global variables.	Unlike
 		 * SPI_finish(), we don't risk switching to memory contexts that
-		 * might be already gone, or deleting memory contexts that have
-		 * been or will be thrown away anyway.
+		 * might be already gone.
 		 */
 		_SPI_connected--;
 		_SPI_curid = _SPI_connected;
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 79da5fe0175..b25d870059a 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/mmgr/aset.c,v 1.57 2004/08/29 05:06:51 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/mmgr/aset.c,v 1.58 2004/09/16 20:17:33 tgl Exp $
  *
  * NOTE:
  *	This is a new (Feb. 05, 1999) implementation of the allocation set
@@ -205,6 +205,7 @@ static void AllocSetInit(MemoryContext context);
 static void AllocSetReset(MemoryContext context);
 static void AllocSetDelete(MemoryContext context);
 static Size AllocSetGetChunkSpace(MemoryContext context, void *pointer);
+static bool AllocSetIsEmpty(MemoryContext context);
 static void AllocSetStats(MemoryContext context);
 
 #ifdef MEMORY_CONTEXT_CHECKING
@@ -222,6 +223,7 @@ static MemoryContextMethods AllocSetMethods = {
 	AllocSetReset,
 	AllocSetDelete,
 	AllocSetGetChunkSpace,
+	AllocSetIsEmpty,
 	AllocSetStats
 #ifdef MEMORY_CONTEXT_CHECKING
 	,AllocSetCheck
@@ -991,6 +993,26 @@ AllocSetGetChunkSpace(MemoryContext context, void *pointer)
 	return chunk->size + ALLOC_CHUNKHDRSZ;
 }
 
+/*
+ * AllocSetIsEmpty
+ *		Is an allocset empty of any allocated space?
+ */
+static bool
+AllocSetIsEmpty(MemoryContext context)
+{
+	AllocSet	set = (AllocSet) context;
+
+	/*
+	 * For now, we say "empty" only if the context never contained any
+	 * space at all.  We could examine the freelists to determine if all
+	 * space has been freed, but it's not really worth the trouble for
+	 * present uses of this functionality.
+	 */
+	if (set->blocks == NULL)
+		return true;
+	return false;
+}
+
 /*
  * AllocSetStats
  *		Displays stats about memory consumption of an allocset.
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index f8e0af4f8b0..581c7f396d8 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.50 2004/08/29 05:06:51 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.51 2004/09/16 20:17:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -291,6 +291,25 @@ GetMemoryChunkContext(void *pointer)
 	return header->context;
 }
 
+/*
+ * MemoryContextIsEmpty
+ *		Is a memory context empty of any allocated space?
+ */
+bool
+MemoryContextIsEmpty(MemoryContext context)
+{
+	AssertArg(MemoryContextIsValid(context));
+
+	/*
+	 * For now, we consider a memory context nonempty if it has any children;
+	 * perhaps this should be changed later.
+	 */
+	if (context->firstchild != NULL)
+		return false;
+	/* Otherwise use the type-specific inquiry */
+	return (*context->methods->is_empty) (context);
+}
+
 /*
  * MemoryContextStats
  *		Print statistics about the named context and all its descendants.
@@ -662,7 +681,6 @@ void
 pgport_pfree(void *pointer)
 {
 	pfree(pointer);
-	return;
 }
 
-#endif
+#endif /* WIN32 */
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index 1fc13af47f7..e57ee2aad27 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/memnodes.h,v 1.28 2004/08/29 04:13:07 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/memnodes.h,v 1.29 2004/09/16 20:17:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -43,6 +43,7 @@ typedef struct MemoryContextMethods
 	void		(*reset) (MemoryContext context);
 	void		(*delete) (MemoryContext context);
 	Size		(*get_chunk_space) (MemoryContext context, void *pointer);
+	bool		(*is_empty) (MemoryContext context);
 	void		(*stats) (MemoryContext context);
 #ifdef MEMORY_CONTEXT_CHECKING
 	void		(*check) (MemoryContext context);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 8cf7d8bc469..671cbbf1ef2 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.57 2004/08/29 04:13:11 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.58 2004/09/16 20:17:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -91,6 +91,7 @@ extern void MemoryContextDeleteChildren(MemoryContext context);
 extern void MemoryContextResetAndDeleteChildren(MemoryContext context);
 extern Size GetMemoryChunkSpace(void *pointer);
 extern MemoryContext GetMemoryChunkContext(void *pointer);
+extern bool MemoryContextIsEmpty(MemoryContext context);
 extern void MemoryContextStats(MemoryContext context);
 
 #ifdef MEMORY_CONTEXT_CHECKING
-- 
GitLab