From fdd13f156814f81732c188788ab1b7b14c59f4da Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 16 Oct 2004 18:57:26 +0000
Subject: [PATCH] Give the ResourceOwner mechanism full responsibility for
 releasing buffer pins at end of transaction, and reduce AtEOXact_Buffers to
 an Assert cross-check that this was done correctly.  When not
 USE_ASSERT_CHECKING, AtEOXact_Buffers is a complete no-op.  This gets rid of
 an O(NBuffers) bottleneck during transaction commit/abort, which recent
 testing has shown becomes significant above a few tens of thousands of shared
 buffers.

---
 src/backend/access/transam/xact.c     |  6 ++-
 src/backend/storage/buffer/bufmgr.c   | 75 ++++++++++++++++++++-------
 src/backend/storage/buffer/localbuf.c | 19 ++-----
 src/backend/storage/lmgr/proc.c       | 10 ++--
 src/backend/utils/resowner/resowner.c | 53 ++++++++-----------
 src/include/storage/bufmgr.h          |  4 +-
 6 files changed, 95 insertions(+), 72 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 321a86f30c..7a6fd3a132 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.191 2004/10/04 21:52:14 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.192 2004/10/16 18:57:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1528,6 +1528,9 @@ CommitTransaction(void)
 						 RESOURCE_RELEASE_BEFORE_LOCKS,
 						 true, true);
 
+	/* Check we've released all buffer pins */
+	AtEOXact_Buffers(true);
+
 	/*
 	 * Make catalog changes visible to all backends.  This has to happen
 	 * after relcache references are dropped (see comments for
@@ -1684,6 +1687,7 @@ AbortTransaction(void)
 	ResourceOwnerRelease(TopTransactionResourceOwner,
 						 RESOURCE_RELEASE_BEFORE_LOCKS,
 						 false, true);
+	AtEOXact_Buffers(false);
 	AtEOXact_Inval(false);
 	smgrDoPendingDeletes(false);
 	ResourceOwnerRelease(TopTransactionResourceOwner,
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index a8cf506688..b9d8fc3ad5 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/storage/buffer/bufmgr.c,v 1.179 2004/10/16 18:05:06 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/storage/buffer/bufmgr.c,v 1.180 2004/10/16 18:57:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -851,35 +851,45 @@ ResetBufferUsage(void)
 /*
  *		AtEOXact_Buffers - clean up at end of transaction.
  *
- *		During abort, we need to release any buffer pins we're holding
- *		(this cleans up in case ereport interrupted a routine that pins a
- *		buffer).  During commit, we shouldn't need to do that, but check
- *		anyway to see if anyone leaked a buffer reference count.
+ *		As of PostgreSQL 8.0, buffer pins should get released by the
+ *		ResourceOwner mechanism.  This routine is just a debugging
+ *		cross-check that no pins remain.
  */
 void
 AtEOXact_Buffers(bool isCommit)
 {
+#ifdef USE_ASSERT_CHECKING
 	int			i;
 
+	for (i = 0; i < NBuffers; i++)
+	{
+		Assert(PrivateRefCount[i] == 0);
+	}
+
+	AtEOXact_LocalBuffers(isCommit);
+#endif
+}
+
+/*
+ * Ensure we have released all shared-buffer locks and pins during backend exit
+ */
+void
+AtProcExit_Buffers(void)
+{
+	int			i;
+
+	AbortBufferIO();
+	UnlockBuffers();
+
 	for (i = 0; i < NBuffers; i++)
 	{
 		if (PrivateRefCount[i] != 0)
 		{
 			BufferDesc *buf = &(BufferDescriptors[i]);
 
-			if (isCommit)
-				elog(WARNING,
-					 "buffer refcount leak: [%03d] "
-				"(rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d)",
-					 i,
-					 buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
-					 buf->tag.rnode.relNode,
-					 buf->tag.blockNum, buf->flags,
-					 buf->refcount, PrivateRefCount[i]);
-
 			/*
-			 * We don't worry about updating the ResourceOwner structures;
-			 * resowner.c will clear them for itself.
+			 * We don't worry about updating ResourceOwner; if we even got
+			 * here, it suggests that ResourceOwners are messed up.
 			 */
 			PrivateRefCount[i] = 1;		/* make sure we release shared pin */
 			LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
@@ -888,8 +898,37 @@ AtEOXact_Buffers(bool isCommit)
 			Assert(PrivateRefCount[i] == 0);
 		}
 	}
+}
 
-	AtEOXact_LocalBuffers(isCommit);
+/*
+ * Helper routine to issue warnings when a buffer is unexpectedly pinned
+ */
+void
+PrintBufferLeakWarning(Buffer buffer)
+{
+	BufferDesc *buf;
+	int32		loccount;
+
+	Assert(BufferIsValid(buffer));
+	if (BufferIsLocal(buffer))
+	{
+		buf = &LocalBufferDescriptors[-buffer - 1];
+		loccount = LocalRefCount[-buffer - 1];
+	}
+	else
+	{
+		buf = &BufferDescriptors[buffer - 1];
+		loccount = PrivateRefCount[buffer - 1];
+	}
+
+	elog(WARNING,
+		 "buffer refcount leak: [%03d] "
+		 "(rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d)",
+		 buffer,
+		 buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
+		 buf->tag.rnode.relNode,
+		 buf->tag.blockNum, buf->flags,
+		 buf->refcount, loccount);
 }
 
 /*
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 6ccc18ddad..4df6befcac 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/storage/buffer/localbuf.c,v 1.59 2004/08/29 05:06:47 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/storage/buffer/localbuf.c,v 1.60 2004/10/16 18:57:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -232,23 +232,12 @@ InitLocalBuffer(void)
 void
 AtEOXact_LocalBuffers(bool isCommit)
 {
+#ifdef USE_ASSERT_CHECKING
 	int			i;
 
 	for (i = 0; i < NLocBuffer; i++)
 	{
-		if (LocalRefCount[i] != 0)
-		{
-			BufferDesc *buf = &(LocalBufferDescriptors[i]);
-
-			if (isCommit)
-				elog(WARNING,
-					 "local buffer leak: [%03d] (rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d)",
-					 i,
-					 buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
-				   buf->tag.rnode.relNode, buf->tag.blockNum, buf->flags,
-					 buf->refcount, LocalRefCount[i]);
-
-			LocalRefCount[i] = 0;
-		}
+		Assert(LocalRefCount[i] == 0);
 	}
+#endif
 }
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index ac04b5e804..e50ed2f08f 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.154 2004/09/29 15:15:55 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.155 2004/10/16 18:57:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -461,9 +461,7 @@ ProcKill(int code, Datum arg)
 	 * shutdown callback registered by the bufmgr ... but we must do this
 	 * *after* LWLockReleaseAll and *before* zapping MyProc.
 	 */
-	AbortBufferIO();
-	UnlockBuffers();
-	AtEOXact_Buffers(false);
+	AtProcExit_Buffers();
 
 	/* Get off any wait queue I might be on */
 	LockWaitCancel();
@@ -509,9 +507,7 @@ DummyProcKill(int code, Datum arg)
 	LWLockReleaseAll();
 
 	/* Release buffer locks and pins, too */
-	AbortBufferIO();
-	UnlockBuffers();
-	AtEOXact_Buffers(false);
+	AtProcExit_Buffers();
 
 	/* I can't be on regular lock queues, so needn't check */
 
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 4075e2c45e..293cb31a26 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.7 2004/08/30 02:54:40 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.8 2004/10/16 18:57:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -191,37 +191,30 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 
 	if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
 	{
-		/* Release buffer pins */
-		if (isTopLevel)
+		/*
+		 * Release buffer pins.  Note that ReleaseBuffer will
+		 * remove the buffer entry from my list, so I just have to
+		 * iterate till there are none.
+		 *
+		 * During a commit, there shouldn't be any remaining pins ---
+		 * that would indicate failure to clean up the executor correctly ---
+		 * so issue warnings.  In the abort case, just clean up quietly.
+		 *
+		 * XXX this is fairly inefficient due to multiple BufMgrLock
+		 * grabs if there are lots of buffers to be released, but we
+		 * don't expect many (indeed none in the success case) so it's
+		 * probably not worth optimizing.
+		 *
+		 * We are however careful to release back-to-front, so as to
+		 * avoid O(N^2) behavior in ResourceOwnerForgetBuffer().
+		 */
+		while (owner->nbuffers > 0)
 		{
-			/*
-			 * For a top-level xact we are going to release all buffers,
-			 * so just do a single bufmgr call at the top of the
-			 * recursion.
-			 */
-			if (owner == TopTransactionResourceOwner)
-				AtEOXact_Buffers(isCommit);
-			/* Mark object as owning no buffers, just for sanity */
-			owner->nbuffers = 0;
-		}
-		else
-		{
-			/*
-			 * Release buffers retail.	Note that ReleaseBuffer will
-			 * remove the buffer entry from my list, so I just have to
-			 * iterate till there are none.
-			 *
-			 * XXX this is fairly inefficient due to multiple BufMgrLock
-			 * grabs if there are lots of buffers to be released, but we
-			 * don't expect many (indeed none in the success case) so it's
-			 * probably not worth optimizing.
-			 *
-			 * We are however careful to release back-to-front, so as to
-			 * avoid O(N^2) behavior in ResourceOwnerForgetBuffer().
-			 */
-			while (owner->nbuffers > 0)
-				ReleaseBuffer(owner->buffers[owner->nbuffers - 1]);
+			if (isCommit)
+				PrintBufferLeakWarning(owner->buffers[owner->nbuffers - 1]);
+			ReleaseBuffer(owner->buffers[owner->nbuffers - 1]);
 		}
+
 		/* Release relcache references */
 		if (isTopLevel)
 		{
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 04bc09e22c..5064a1857f 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.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/storage/bufmgr.h,v 1.87 2004/10/15 22:40:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/storage/bufmgr.h,v 1.88 2004/10/16 18:57:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -122,6 +122,8 @@ extern void InitBufferPoolAccess(void);
 extern char *ShowBufferUsage(void);
 extern void ResetBufferUsage(void);
 extern void AtEOXact_Buffers(bool isCommit);
+extern void AtProcExit_Buffers(void);
+extern void PrintBufferLeakWarning(Buffer buffer);
 extern void FlushBufferPool(void);
 extern BlockNumber BufferGetBlockNumber(Buffer buffer);
 extern BlockNumber RelationGetNumberOfBlocks(Relation relation);